在 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
如果三角形由A
、B
和C
三个点定义,则三角形上的点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
定义。
该平面由具有三个点 PA
、PB
和 PC
.
的三角形定义
平面的法向量可以通过三角形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;
}
我目前正在编写一个三角形相交的光线追踪器。我将网格导出为 .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
如果三角形由A
、B
和C
三个点定义,则三角形上的点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
定义。
该平面由具有三个点 PA
、PB
和 PC
.
平面的法向量可以通过三角形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;
}