在 webgl 和 opengles 中渲染带有反射的三角形网格的问题

Problem in rendering triangle mesh with reflections in webgl and opengles

我目前正在编写一个三角形相交的光线追踪器。我将网格导出为 .raw 格式只是为了拥有每个顶点的坐标。 而且我注意到一件事,当我旋转相机时,它会显示出遮挡的网格。

i post 我的片段着色器下面的三角形交集函数

bool hitTriangle(vec3 orig,vec3 dir,vec3 a,vec3 b,vec3 c,out vec3 uvt,out vec3 triangleNormal){
   float eps=1e-8;
   vec3 ab=b-a;
   vec3 ac=c-a;

   triangleNormal = normalize(cross(ab,ac));
   vec3 n=cross(dir,ac);

   float det=dot(ab,n);
   // if the determinant is negative the triangle is backfacing
   // if the determinant is close to 0, the ray misses the triangl
   if(det<=eps){ return false;}
   
   vec3 ao=orig-a;
   float u=dot(ao,n)/det;
   if(u<0.0 || u>1.0){ return false;}
    
   vec3 e=cross(ao,ab);
   float v=dot(dir,e)/det;
   if(v<0.0||u+v>1.0){ return false;}

   float t= dot(ac,e)/det;
   uvt = vec3(u,v,t);
   return true;
}

在它下面是 hitScene 函数,我在其中遍历所有网格并检查交叉点

bool hitScene(Ray R_, out vec3 hitPos, out vec3 normal, out Material material, Sphere lightSource){  // na thimithw na thesw to isShpere false stin trace synartisi
        
    vec4 a = vec4(0.0), b = vec4(0.0), c = vec4(0.0);
    float mindist = -1000.;
    bool weHitSomething = false;
    vec3 hitPos1 = vec3(0.),triangleNormal = vec3(0.,0.,0.), sphereNormal;
    

    //here we chck all the mesh if we hit a triangle if the mesh and we keep the closest hitpoint
    for (int i = 0; i < vertsCount; i += 3) {
       
        a = texelFetch(uMeshData, ivec2(i, 0), 0);
        b = texelFetchOffset(uMeshData, ivec2(i, 0), 0, ivec2(1, 0));
        c = texelFetchOffset(uMeshData, ivec2(i, 0), 0, ivec2(2, 0));

        vec3 uvt;
        bool isHit = hitTriangle(R_.orig,R_.dir, a.xyz,b.xyz,c.xyz,uvt, triangleNormal);
        if (isHit) {
            vec3 intersect = R_.orig + R_.dir*uvt.z;
            float z = intersect.z;
            if (z>mindist) {
                hitPos1 = intersect;
                
                mindist = z;
                weHitSomething = true;
                material.type = METAL;
                material.albedo = vec3(.0, .8, .8);
                normal = triangleNormal;
                hitPos = hitPos1;
            }
        }      
    }      
    
    return weHitSomething;
}

下面是遍历跟踪的 Trace 函数

//Trace is the main function of the max bounces
vec3 Trace(out Ray ray, Sphere lightSource){

    vec3 hitPos, normal;
    bool isShpere;
    Material material;
    vec3 color = vec3(1.);
    vec3 attenuation = vec3(1.);
    vec3 light = vec3(1.,1.,1.), shadow = vec3(1.,1.,1.);

    //this if for every ray to bounce 4 times.(hopefully)
    for(int i=0; i< MAX_BOUNCES; i++){
        
        // we check if we hit something
        if(hitScene(ray, hitPos, normal, material, lightSource)){
  
            //we calculate the new direction
            vec3 direction = normalize(reflect(ray.dir, normal));
                
            ray = Ray(hitPos, direction); 
             
            color *= material.albedo * attenuation*hitPos;
            attenuation *= material.albedo;    
        }

        else{
            color = attenuation;
        }
    }

    return color;
}

然后我得到了类似的东西: 如您所见,我得到了反射,但我也在球体内部看到了立方体。这是否意味着我未通过射线-三角形相交测试?或者最小距离不正确??

问题的根源可能是什么?

这不会解决您所有的问题,但它是答案的一部分。

在您的代码中存在对 Barycentric coordinates 的基本误解。重心坐标不是坐标,而是 [0, 1] 范围内的 3 个值。将一个重心坐标想象成 3 个权重,对三角形的 3 个角点进行加权。

一个重心坐标的3个刻度之和为1:

b.x + b.x + b.z == 1

如果三角形由ABC三个点定义,则三角形上的点X,由重心坐标定义可以通过公式计算:

X =  A * b.x + B * b.y + C * b.z

这意味着 hitScene 中的交集 distance 的计算必须按如下方式调整:

vec3 intersect = a.xyz * uvt.x + b.xyz * uvt.y + c.xyz * uvt.z;

到射线原点的距离是从原点到射线方向交点的向量的长度。
一般来说,我会这样计算:

float z = dot(intersect - R_.orig, normalize(R_.dir));

此外,计算重心坐标的算法看起来很奇怪。我非常确定它必须是 uvt = vec3(t, u, v) 而不是 uvt = vec3(u, v, t);。但这必须进一步调查。

三角形与射线的交点可以计算如下:

光线由点 R0 和方向 D 定义。
该平面由具有三个点 PAPBPC.

的三角形定义

平面的法向量可以通过三角形2条边的叉积计算得到:

N  =  normalize( cross(PC-PA, PB-PA)

R0到平面的法线距离n为:

n  =  | R0 - PA | * cos(alpha)  =  dot(PA - R0, N)

由此得出交点X到射线R0原点的距离d为:

d  =  n / cos(beta)  =  n / dot(D, N)

交点X是:

X  =  R0 + D * d  =  R0 + D * dot(PA - R0, N) / dot(D, N)

将 应用于您的代码,我建议使用以下函数 hitTriangle:

  • 如果光线在光线原点(orig之后在光线的正方向(dir)与三角形基元相交,则return值为true ).
  • 输出N是由三角形定义的平面的法向量。
  • 输出uvt是三角形的Barycentric coordinates
  • 输出x是三角形上的交点
  • 而输出dist是射线原点(orig)到交点x.
  • 的距离

注意,光线方向 (dir) 必须归一化。只有函数 returns true 时,输出值才有意义。

bool hitTriangle(
    vec3 orig, vec3 dir, vec3 a, vec3 b, vec3 c,
    out vec3 uvt, out vec3 N, out vec3 x, out float dist) {

    float eps=1e-8;

    vec3 ab = b - a;
    vec3 ac = c - a;

    N = normalize(cross(ab, ac));

    dist = dot(a - orig, N) / dot(dir, N);
    x    = orig + dir * dist;

    vec3 ax = x - a;

    float d00 = dot(ab, ab);
    float d01 = dot(ab, ac);
    float d11 = dot(ac, ac);
    float d20 = dot(ax, ab);
    float d21 = dot(ax, ac);

    float denom = d00 * d11 - d01 * d01; // determinant

    // if the determinant is negative the triangle is backfacing
    // if the determinant is close to 0, the ray misses the triangl
    if ( denom <= eps )
        return false;

    uvt.y = (d11 * d20 - d01 * d21) / denom;
    if ( uvt.y < 0.0 || uvt.y > 1.0 )
        return false;

    uvt.z = (d00 * d21 - d01 * d20) / denom;
    if ( uvt.z < 0.0 || uvt.z > 1.0 )
        return false;

    uvt.x = 1.0 - uvt.y - uvt.z;
    if ( uvt.x < 0.0 || uvt.x > 1.0 )
        return false;

    return true;
}

在函数hitScene中使用命中测试(hitTriangle)如下:

bool hitScene(Ray R_, out vec3 hitPos, out vec3 normal, out Material material, Sphere lightSource){  // na thimithw na thesw to isShpere false stin trace synartisi

    vec4 a = vec4(0.0), b = vec4(0.0), c = vec4(0.0);
    float mindist = 1000.;
    bool weHitSomething = false;
    vec3 hitPos1 = vec3(0.),triangleNormal = vec3(0.,0.,0.), sphereNormal;

    vec3 ray_dir = normalize(R_.dir);

    //here we chck all the mesh if we hit a triangle if the mesh and we keep the closest hitpoint
    for (int i = 0; i < vertsCount; i += 3) {

        a = texelFetch(uMeshData, ivec2(i, 0), 0);
        b = texelFetchOffset(uMeshData, ivec2(i, 0), 0, ivec2(1, 0));
        c = texelFetchOffset(uMeshData, ivec2(i, 0), 0, ivec2(2, 0));

        vec3 uvt;
        vec3 intersect;
        float z;
        bool isHit = hitTriangle(R_.orig, ray_dir, a.xyz, b.xyz, c.xyz, uvt, triangleNormal, intersect, z);
        if (isHit)
        {
            if (z < mindist && z > 0.001)
            {
                hitPos1 = intersect;

                mindist = z;
                weHitSomething = true;
                material.type = METAL;
                material.albedo = vec3(.0, .8, .8);
                normal = triangleNormal;
                hitPos = hitPos1;
            }
        }
    }

    return weHitSomething;
}