Как реализовать преломление света во фрагментном шейдере?

Я работаю над трассировщиком лучей OpenGL, который способен загружать файлы obj и отслеживать их. Мое приложение загружает файл obj с помощью assimp, а затем отправляет все грани треугольника (с примитивными координатами и коэффициентами материала te) во фрагментный шейдер, используя объекты хранилища шейдеров. Основная структура собирается отобразить результаты в четырехугольник из фрагментного шейдера.

У меня проблемы с трассировкой лучей во фрагментном шейдере, но сначала давайте познакомимся с ней. Для рассеянного света использовался закон косинуса Ламберта, для зеркального света использовалась модель Фонга-Блинна. В случае полного отражения aweightпеременная используется, чтобы отраженный свет влиял и на другие объекты. Вес рассчитывается путем аппроксимации уравнения Френеля методом Шлика. На изображении ниже вы можете видеть, что плоскость работает как зеркало, отражающее изображение куба выше.

Я хотел бы, чтобы куб выглядел как стеклянный объект (например, стеклянная сфера), который также имеет эффекты преломления и отражения. Или хотя бы преломляет свет. На изображении выше вы можете увидеть эффект преломления куба, но он не так хорош, как должен быть. Я искал примеры, как это реализовать, но до сих пор я понимал, что уравнение Френеля необходимо использовать так же, как в части с отражением.

Вот фрагмент моего шейдера:

 vec3 Fresnel(vec3 F0, float cosTheta) {
    return F0 + (vec3(1, 1, 1) - F0) * pow(1-cosTheta, 5);
}

float schlickApprox(float Ni, float cosTheta){
    float F0=pow((1-Ni)/(1+Ni), 2);
    return F0 + (1 - F0) * pow((1 - cosTheta), 5);
}


vec3 trace(Ray ray){
    vec3 weight = vec3(1, 1, 1);
    const float epsilon = 0.0001f;
    vec3 outRadiance = vec3(0, 0, 0);
    int maxdepth=5;

    for (int i=0; i < maxdepth; i++){
        Hit hit=traverseBvhTree(ray);

        if (hit.t<0){ return weight * lights[0].La; }

        vec4 textColor = texture(texture1, vec2(hit.u, hit.v));
        Ray shadowRay;
        shadowRay.orig = hit.orig + hit.normal * epsilon;
        shadowRay.dir  = normalize(lights[0].direction);

        // Ambient Light
        outRadiance+= materials[hit.mat].Ka.xyz * lights[0].La*textColor.xyz * weight;


        // Diffuse light based on Lambert's cosine law
        float cosTheta = dot(hit.normal, normalize(lights[0].direction));
        if (cosTheta>0 && traverseBvhTree(shadowRay).t<0) {
            outRadiance +=lights[0].La * materials[hit.mat].Kd.xyz * cosTheta * weight;

            // Specular light based on Phong-Blinn model
            vec3 halfway = normalize(-ray.dir + lights[0].direction);
            float cosDelta = dot(hit.normal, halfway);

            if (cosDelta > 0){
                outRadiance +=weight * lights[0].Le * materials[hit.mat].Ks.xyz * pow(cosDelta, materials[hit.mat].shininess); }
        }

        float fresnel=schlickApprox(materials[hit.mat].Ni, cosTheta);

        // For refractive materials
        if (materials[hit.mat].Ni < 3)
        {

         /*this is the under contruction part.*/



            ray.orig = hit.orig - hit.normal*epsilon;
            ray.dir = refract(ray.dir, hit.normal, materials[hit.mat].Ni);
        }

        // If the refraction index is more than 15, treat the material as mirror.
        else if (materials[hit.mat].Ni >= 15) {
            weight *= fresnel;
            ray.orig=hit.orig+hit.normal*epsilon;
            ray.dir=reflect(ray.dir, hit.normal);
        }
    }
    return outRadiance;
}

Обновление 1

Я обновил метод трассировки в шейдере. Насколько я понимаю физику света, если есть материал, который отражает и преломляет свет, я должен рассматривать два случая в соответствии с этим.

  1. В этом случае отражения я добавил вес к расчету рассеянного света: weight *= fresnel
  2. В случае преломления света вес составляет weight*=1-fresnel.

Кроме того, я рассчитал ray.orig а также ray.dir связанных с случаями, и расчет рефракции происходит только тогда, когда это не случай полного внутреннего отражения (fresnel меньше 1).

Модифицированный метод трассировки:

vec3 trace(Ray ray){
    vec3 weight = vec3(1, 1, 1);
    const float epsilon = 0.0001f;
    vec3 outRadiance = vec3(0, 0, 0);
    int maxdepth=3;

    for (int i=0; i < maxdepth; i++){
        Hit hit=traverseBvhTree(ray);

        if (hit.t<0){ return weight * lights[0].La; }

        vec4 textColor = texture(texture1, vec2(hit.u, hit.v));
        Ray shadowRay;
        shadowRay.orig = hit.orig + hit.normal * epsilon;
        shadowRay.dir  = normalize(lights[0].direction);

        // Ambient Light
        outRadiance+= materials[hit.mat].Ka.xyz * lights[0].La*textColor.xyz * weight;


        // Diffuse light based on Lambert's cosine law
        float cosTheta = dot(hit.normal, normalize(lights[0].direction));
        if (cosTheta>0 && traverseBvhTree(shadowRay).t<0) {
            outRadiance +=lights[0].La * materials[hit.mat].Kd.xyz * cosTheta * weight;

            // Specular light based on Phong-Blinn model
            vec3 halfway = normalize(-ray.dir + lights[0].direction);
            float cosDelta = dot(hit.normal, halfway);

            if (cosDelta > 0){
                outRadiance +=weight * lights[0].Le * materials[hit.mat].Ks.xyz * pow(cosDelta, materials[hit.mat].shininess); }
        }

        float fresnel=schlickApprox(materials[hit.mat].Ni, cosTheta);

        // For refractive/reflective  materials
        if (materials[hit.mat].Ni < 7)
        {
            bool outside = dot(ray.dir, hit.normal) < 0;

            // compute refraction if it is not a case of total internal reflection
            if (fresnel < 1) {

                ray.orig = outside ? hit.orig-hit.normal*epsilon : hit.orig+hit.normal*epsilon;
                ray.dir = refract(ray.dir, hit.normal,materials[hit.mat].Ni);
                weight *= 1-fresnel;
                continue;

            }
            // compute reflection
            ray.orig= outside ? hit.orig+hit.normal*epsilon : hit.orig-hit.normal*epsilon;
            ray.dir= reflect(ray.dir, hit.normal);
            weight *= fresnel;
            continue;

        }
        // If the refraction index is more than 15, treat the material as mirror: total reflection
        else if (materials[hit.mat].Ni >= 7) {
            weight *= fresnel;
            ray.orig=hit.orig+hit.normal*epsilon;
            ray.dir=reflect(ray.dir, hit.normal);
        }
    }
    return outRadiance;
}

Вот снимок, связанный с обновлением. Думаю, немного лучше.


Обновление 2:

Я нашел итеративный алгоритм, который использует стек для визуализации преломляющих и отраженных лучей в opengl: он находится на странице 68.

Я модифицировал свой фраг-шейдер в соответствии с этим. Почти нормально, за исключением полностью черных задних граней. Фотографии прилагаются.

Вот метод трассировки моего фраг-шейдера:

vec3 trace(Ray ray){
    vec3 color;
    float epsilon=0.001;
    Stack stack[8];// max depth
    int stackSize = 0;// current depth
    int bounceCount = 0;
    vec3 coeff = vec3(1, 1, 1);
    bool continueLoop = true;

    while (continueLoop){
        Hit hit = traverseBvhTree(ray);
        if (hit.t>0){

            bounceCount++;
            //----------------------------------------------------------------------------------------------------------------
            Ray shadowRay;
            shadowRay.orig = hit.orig + hit.normal * epsilon;
            shadowRay.dir  = normalize(lights[0].direction);

            color+= materials[hit.mat].Ka.xyz * lights[0].La * coeff;
            // Diffuse light
            float cosTheta = dot(hit.normal, normalize(lights[0].direction));// Lambert-féle cosinus törvény alapján.
            if (cosTheta>0 && traverseBvhTree(shadowRay).t<0) {
                color +=lights[0].La * materials[hit.mat].Kd.xyz * cosTheta * coeff;
                vec3 halfway = normalize(-ray.dir + lights[0].direction);
                float cosDelta = dot(hit.normal, halfway);

                // Specular light
                if (cosDelta > 0){
                    color +=coeff * lights[0].Le * materials[hit.mat].Ks.xyz * pow(cosDelta, materials[hit.mat].shininess); }
            }
            //---------------------------------------------------------------------------------------------------------------
              if (materials[hit.mat].indicator > 3.0 && bounceCount <=2){

                  float eta = 1.0/materials[hit.mat].Ni;
                  Ray refractedRay;
                  refractedRay.dir = dot(ray.dir, hit.normal) <= 0.0 ? refract(ray.dir, hit.normal, eta) : refract(ray.dir, -hit.normal, 1.0/eta);
                  bool totalInternalReflection = length(refractedRay.dir) < epsilon;
                  if(!totalInternalReflection){

                      refractedRay.orig = hit.orig + hit.normal*epsilon*sign(dot(ray.dir, hit.normal));
                      refractedRay.dir = normalize(refractedRay.dir);

                          stack[stackSize].coeff = coeff *(1 - schlickApprox(materials[hit.mat].Ni, dot(ray.dir, hit.normal)));
                          stack[stackSize].depth = bounceCount;
                          stack[stackSize++].ray = refractedRay;

                  }

                  else{

                      ray.dir = reflect(ray.dir, -hit.normal);
                      ray.orig = hit.orig - hit.normal*epsilon;
                  }
              }

            else if (materials[hit.mat].indicator == 0){
                coeff *= schlickApprox(materials[hit.mat].Ni, dot(-ray.dir, hit.normal));
                ray.orig=hit.orig+hit.normal*epsilon;
                ray.dir=reflect(ray.dir, hit.normal);
            }


            else { //Diffuse Material
                continueLoop=false;
            }
        }

        else {
            color+= coeff * lights[0].La;
            continueLoop=false;
        }

        if (!continueLoop && stackSize > 0){
            ray = stack[stackSize--].ray;
            bounceCount = stack[stackSize].depth;
            coeff = stack[stackSize].coeff;
            continueLoop = true;
        }
    }
    return color;
}

0 ответов

Другие вопросы по тегам