为何特定场景下我的HLSL数组无法接收修改?
雨水渲染着色器重叠雨滴折射问题
我正在开发一款雨水渲染着色器,在光线击中雨滴的循环中,有两段逻辑用于避免重叠雨滴间的折射:
- 第一段
if (inWater && dot(currentRay.direction, surfaceNormal) < 0)运行完全正常,raindropDepth能正确递增,触发后续流程。 - 第二段
if (inWater && dot(currentRay.direction, surfaceNormal) > 0 && currentRay.raindropDepth > 1)虽能执行,但无法将数据保存到数组中。调试时将rayDebug设为true后,遍历数组找不到对应射线,最终导致雨滴重叠区域出现黑圈——射线未被重置并重新从数组读取。
预期流程:射线进入雨滴后触发第一个if,raindropDepth 增至2;射线离开时触发第二个if,raindropDepth 递减,同时重置射线。但实际射线在此处失效。我是着色器新手,附上完整代码,问题出在第二个if语句。
float3 waterDropPos = WaterDrops[closestbjectIndex - startingIndex].xyz; float waterDropRadius = WaterDrops[closestObjectIndex - startingIndex].a; float fresnelAmount = 0.2; //Rain splits the ray in 2. One reflected and one refracted. int dropIndex = closestObjectIndex - startingIndex; bool inWater = currentRay.raindropDepth != 0; float3 hitLocation = currentRay.origin + currentRay.direction * closestT; // The point where the ray hits the sphere, which we'll use to calculate the surface normal and the reflection and refraction directions. float3 surfaceNormal = normalize(hitLocation - waterDropPos); //We'll need to flip this depending on if we're in the water bool inserted = false; //If we're in water, and we're entering water, we can actually just ignore this intersection. This would be the ray going through 2 drops overlapping, which wouldn't actually change the look. if (inWater && dot(currentRay.direction, surfaceNormal) < 0) { Ray continuedRay = CreateRay(hitLocation + currentRay.direction * 0.0001, currentRay.direction, currentRay.strength, currentRay.currentMedium, currentRay.depth, 0, currentRay.raindropDepth + 1); //This is the only situation I can think of where we won't increase the ray depth, because in all honesty both drops should merge into one anyway. for (int k = 0; k < MaxRays; k++) { if (rayOverwrites[k]) { //rays[j] = continuedRay; rayOrigins[k] = continuedRay.origin; rayDirections[k] = continuedRay.direction; rayStrengths[k] = continuedRay.strength; rayCurrentMediums[k] = continuedRay.currentMedium; rayDepths[k] = continuedRay.depth; rayOverwrites[k] = continuedRay.overwrite; rayRaindropDepths[k] = continuedRay.raindropDepth; rayDebugs[k] = false; inserted = true; break; } } if (inserted == false) { Result[id.xy] = float4(1.0, 0.0, 1.0, 1.0); // Magenta for this specific case, just to make sure it's working correctly. return; } continue; } //If our droplet depth is greater than 1, and we're exiting a water drop, we can also ignore the intersection, because this would be the ray exiting one drop and //immediately entering another overlapping drop, which again wouldn't actually change the look because the drops would merge. if (inWater && dot(currentRay.direction, surfaceNormal) > 0 && currentRay.raindropDepth > 1) { //Ray continuedRay = CreateRay(hitLocation + currentRay.direction * 0.0001, currentRay.direction, currentRay.strength, currentRay.currentMedium, currentRay.depth, 0, currentRay.raindropDepth - 1); for (int h = 0; h < MaxRays; h++) { if (rayOverwrites[h]) { //rays[j] = continuedRay; rayOrigins[h] = hitLocation + currentRay.direction * 0.0001; rayDirections[h] = currentRay.direction; rayStrengths[h] = currentRay.strength; rayCurrentMediums[h] = currentRay.currentMedium; rayDepths[h] = currentRay.depth; rayOverwrites[h] = false; rayRaindropDepths[h] = currentRay.raindropDepth - 1; rayDebugs[h] = true; inserted = true; break; } } if (inserted == false) { Result[id.xy] = float4(1.0, 0.0, 1.0, 1.0); // Magenta for this specific case, just to make sure it's working correctly. return; } continue; } if (currentRay.depth >= MaxBounces) { //Result[id.xy] = float4(1.0, 0.0, 0.0, 1.0); // Red for testing to make sure we're actually hitting the raindrop and not just skipping it because of the max bounces. //return; endColor += AmbientIntensity * LightColor * currentRay.strength; // If we've reached the max bounces, we can just add the light color modulated by the strength of the ray, and then end the ray. continue; }
问题定位与修复
核心错误分析
第二个if块存在两个关键问题:
rayOverwrites赋值逻辑不一致:第一段逻辑中是将rayOverwrites[k]设置为continuedRay.overwrite(即构造射线时传入的0/false),但第二段直接硬写rayOverwrites[h] = false,导致后续遍历数组时无法识别这个位置为可覆盖状态——刚设置为false就无法被选中,射线数据相当于“丢失”。- 未复用
CreateRay构造射线:第二段注释掉了CreateRay调用,手动赋值时虽参数看似正确,但破坏了和第一段一致的逻辑模式,容易出现参数遗漏或赋值错误。
修复后的代码
将第二段if块改为与第一段统一的逻辑,用 CreateRay 构造射线后再赋值数组:
//If our droplet depth is greater than 1, and we're exiting a water drop, we can also ignore the intersection, because this would be the ray exiting one drop and //immediately entering another overlapping drop, which again wouldn't actually change the look because the drops would merge. if (inWater && dot(currentRay.direction, surfaceNormal) > 0 && currentRay.raindropDepth > 1) { Ray continuedRay = CreateRay(hitLocation + currentRay.direction * 0.0001, currentRay.direction, currentRay.strength, currentRay.currentMedium, currentRay.depth, 0, currentRay.raindropDepth - 1); for (int h = 0; h < MaxRays; h++) { if (rayOverwrites[h]) { rayOrigins[h] = continuedRay.origin; rayDirections[h] = continuedRay.direction; rayStrengths[h] = continuedRay.strength; rayCurrentMediums[h] = continuedRay.currentMedium; rayDepths[h] = continuedRay.depth; rayOverwrites[h] = continuedRay.overwrite; // 和第一段保持一致的赋值逻辑 rayRaindropDepths[h] = continuedRay.raindropDepth; rayDebugs[h] = true; inserted = true; break; } } if (inserted == false) { Result[id.xy] = float4(1.0, 0.0, 1.0, 1.0); // Magenta for this specific case, just to make sure it's working correctly. return; } continue; }
额外调试建议
- 在第二个if块开头临时添加颜色标记(如
Result[id.xy] = float4(0,1,0,1); return;),确认该分支确实被触发。 - 检查
rayOverwrites数组的初始化逻辑,确保射线位置在需要被覆盖时处于true状态,否则无法找到可写入的位置。
内容的提问来源于stack exchange,提问作者Perrin Peterson
相关产品推荐
相关产品推荐

