You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何特定场景下我的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块存在两个关键问题:

  1. rayOverwrites 赋值逻辑不一致:第一段逻辑中是将 rayOverwrites[k] 设置为 continuedRay.overwrite(即构造射线时传入的0/false),但第二段直接硬写 rayOverwrites[h] = false,导致后续遍历数组时无法识别这个位置为可覆盖状态——刚设置为false就无法被选中,射线数据相当于“丢失”。
  2. 未复用 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 02:04:52