光线追踪Fragment Shader反射与弹跳实现黑屏问题排查求助
光线追踪反射弹跳黑屏问题排查指南
嘿,咱们一步步拆解你的光线追踪反射弹跳黑屏问题,结合你提供的GLSL代码,给你几个核心排查方向和修复思路:
一、先修正hitScene的命中距离判断逻辑
你的hitScene里用mindist = -1000.记录最近命中,这是核心错误之一:
- 光线的
t值(对应三角形命中的uvt.z)应该是正数(代表光线前进方向的有效命中),初始mindist应该设为极大正数(比如1e10),然后找最小的t值(因为t越小,命中点离相机越近)。你现在取z更大的点,完全搞反了距离逻辑——相机在z=6位置,光线向-z方向发射,z值越小的点离相机越近,选z大的点会导致取到最远甚至反向的无效命中。 - 三角形命中时,必须检查
uvt.z > 0,跳过光线反方向的无效命中;同时要修正法向量朝向,确保法向量和光线方向的点积为负(朝向相机),否则反射方向会完全错误。
二、修复Trace函数的颜色初始化与循环逻辑
- 初始
color = vec3(0.)会导致第一次命中时,color *= ...始终为0,永远没有颜色输出。应该把初始color设为vec3(1.)(代表初始光线的能量),或者第一次命中时直接赋值而非乘法。 - 反射光线的起点必须偏移极小距离(比如
hitPos + normal * 1e-4),否则会和原命中点发生自相交,导致光线立刻命中自身,出现黑屏或闪烁。 - 循环里的
else { color = attenuation; }逻辑错误,应该在hitScene返回false时,直接返回当前衰减值乘背景色(比如天空蓝)并跳出循环,而非每次循环都覆盖颜色。
三、修正材质计算的物理逻辑
- 漫反射材质(
LAMB)不能用镜面反射reflect,应该生成半球内的随机散射方向(需要实现GLSL随机函数),否则漫反射会变成镜面反射,既不符合物理逻辑,也会导致光线无法正确到达光源。 - 金属材质的颜色计算要分离光照贡献和能量衰减:金属的颜色是
attenuation * material.albedo乘光照,而反射光线的能量衰减是attenuation * material.albedo,避免初始0值导致的无输出。
四、阴影计算的距离判断修正
calcShadow里比较isHitMesh.z > isHitLightDir.z是错误的,z值不能代表实际距离,应该计算光线到命中点的欧氏距离(length(isHitMesh - shadowRay.orig)和length(isHitLightDir - shadowRay.orig)),比较哪个距离更近,才能正确判断是否处于阴影中。
关键代码修正示例(核心片段)
修正后的hitScene核心逻辑
bool hitScene(Ray R_, out vec3 hitPos, out vec3 normal, out Material material, Sphere sphere, out bool isSphere, Sphere lightSource){ vec4 a = vec4(0.0), b = vec4(0.0), c = vec4(0.0); float closestT = 1e10; // 初始化为极大值,寻找最近的有效命中t值 bool weHitSomething = false; vec3 hitPos1 = vec3(0.); vec3 triangleNormal = vec3(0.,0.,0.); vec3 sphereNormal; // 遍历网格三角形 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 && uvt.z > 0. && uvt.z < closestT) { hitPos1 = R_.orig + R_.dir*uvt.z; closestT = uvt.z; // 修正法向量朝向,确保朝向相机 normal = dot(R_.dir, triangleNormal) > 0. ? -triangleNormal : triangleNormal; } } // 检测地板球体 vec3 hit; bool isHitSphere = hitSphere(R_.orig,R_.dir,sphere.center,sphere.radius,hit); if(isHitSphere){ float t = length(hit - R_.orig); if(t > 0. && t < closestT){ closestT = t; sphereNormal = normalize(hit - sphere.center); normal = dot(R_.dir, sphereNormal) > 0. ? -sphereNormal : sphereNormal; hitPos = hit; weHitSomething = true; material.type = LAMB; material.albedo = vec3(0., 0.9, 0.9); isSphere = true; } } // 优先使用网格的近命中 if(closestT != 1e10 && hitPos1 != vec3(0.)){ hitPos = hitPos1; weHitSomething = true; material.type = METAL; material.albedo = vec3(0.9, 0.9, 0.9); isSphere = false; } return weHitSomething; }
修正后的Trace核心逻辑
// 补充一个简单的GLSL随机函数(需要在代码顶部定义) float rand() { return fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453); } vec3 Trace(Ray ray, Sphere floor, Sphere lightSource){ vec3 hitPos, normal; bool isSphere; Material material; vec3 color = vec3(1.); // 初始光线能量为1 vec3 attenuation = vec3(1.); for(int i=0; i< MAX_BOUNCES; i++){ if(hitScene(ray, hitPos, normal, material, floor, isSphere, lightSource)){ // 计算光照和阴影贡献 vec3 lightDir = normalize(lightSource.center - hitPos); float diffuse = clamp(dot(lightDir, normal), 0., 1.); vec3 shadow = calcShadow(lightSource, hitPos); vec3 lightContribution = diffuse * shadow; if (material.type == METAL) { vec3 direction = reflect(ray.dir, normal); // 偏移起点避免自相交 ray = Ray(hitPos + normal * 1e-4, direction); color *= material.albedo * lightContribution; attenuation *= material.albedo; // 能量过低时提前终止循环 if(dot(attenuation, attenuation) < 1e-3) break; } else if (material.type == LAMB) { // 漫反射:生成半球随机方向 vec3 randomDir = normalize(normal + vec3(rand()-0.5, rand()-0.5, rand()-0.5)); ray = Ray(hitPos + normal * 1e-4, randomDir); color *= material.albedo * lightContribution; attenuation *= material.albedo; if(dot(attenuation, attenuation) < 1e-3) break; } } else { // 未命中场景,返回天空背景色 color *= vec3(0.5, 0.7, 1.0); break; } } return color; }
额外调试技巧
- 可视化命中结果:临时修改
Trace函数返回hitPos或normal,看屏幕是否有正确的颜色输出,判断是hitScene未命中还是后续颜色计算错误。 - 打印关键变量:如果你的环境支持,可以用
fragColor = vec4(normalize(normal), 1.0)输出法向量,验证法向量朝向是否正确。
内容的提问来源于stack exchange,提问作者Thodoris Koutsis
相关产品推荐
相关产品推荐

