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

GLSL光线步进实现球面物体表面点计算问题排查

GLSL光线步进计算球面表面点的问题排查与优化

问题背景

我正在GLSL中实现光线步进(Ray Marching)以渲染球面物体并进行表面点可视化,但从特定角度观察物体时,表面位置计算会出现错误。期望球体在正对视角时能完全填充,但当前计算得到的表面位置无法提供预期交点,出现异常颜色或无交点情况。

附上片段着色器代码:

#version 400

out vec4 FragColor;
in vec3 color;
in vec3 Normal;
in vec3 CurrentPos;

uniform int sunCount;
uniform int planetCount;
uniform vec4 lightColor;
uniform vec3 lightPos[64];
uniform vec3 viewPos;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform vec3 sphereOrigin[64];
uniform float sphereRadius[64];

// SDF for spheres
float SDF(in vec3 p, in vec3 c, float r) {
    return length(p - c) - r;
}

vec3 calculateSurfacePos() {
    // Get normalized device coordinates (NDC) from gl_FragCoord
    vec2 uv_nds = (gl_FragCoord.xy / vec2(1366.0, 768.0)) * 2.0 - 1.0;
    
    vec4 uv_clip = vec4(uv_nds, -1.0, 1.0);
    vec4 uv_eye = inverse(projectionMatrix) * uv_clip;
    uv_eye = vec4(uv_eye.x, uv_eye.y, -1.0, 0.0);
    vec3 uv_wor = (inverse(viewMatrix) * uv_eye).xyz;
    uv_wor = normalize(uv_wor);

    highp float t = 0.0;
    highp vec3 ro = viewPos;
    highp vec3 rd = uv_wor;

    for (int i = 0; i < 256; i++) {
        highp vec3 p = ro + rd * t;
        float dist = 1e10;

        for (int j = 0; j < planetCount; j++) {
            dist = min(dist, SDF(p, sphereOrigin[j], sphereRadius[j]));
        }

        if (dist < 0.001) return p;
        if (t > 100.) break;

        t += dist;
    }
    return vec3(0.0);
}

void main() {
    vec3 surfacePos = calculateSurfacePos();
    FragColor = vec4(surfacePos, 1.0);
}

我的疑问:

  • 从裁剪空间到眼空间再到世界空间的转换逻辑是否存在错误?
  • 光线步进有哪些优化方案或最佳实践?
  • 如何更有效地调试交点以确保SDF返回正确距离?

解答

1. 坐标转换逻辑的问题排查

你的光线方向计算存在两处关键错误:

  • 硬编码屏幕分辨率:vec2(1366.0, 768.0)会导致窗口大小变化时坐标映射错误,应该通过uniform传递屏幕分辨率,正确的NDC计算应为:
    uniform vec2 viewSize;
    vec2 uv_nds = (gl_FragCoord.xy / viewSize) * 2.0 - 1.0;
    
  • 眼空间到世界空间的转换错误:你跳过了透视除法步骤,导致光线方向被错误缩放,正确流程如下:
    vec4 uv_clip = vec4(uv_nds, -1.0, 1.0);
    vec4 uv_eye = inverse(projectionMatrix) * uv_clip;
    uv_eye /= uv_eye.w; // 执行透视除法,将裁剪空间转换为眼空间
    uv_eye.w = 0.0; // 标记为方向向量(无位置偏移)
    vec3 uv_wor = (inverse(viewMatrix) * uv_eye).xyz;
    uv_wor = normalize(uv_wor);
    
    这是视角变化时交点计算错误的核心原因。

2. 光线步进的优化方案与最佳实践

  • 自适应距离阈值:固定的0.001阈值在近距离物体上可能精度不足,远距离则浪费性能,可根据当前t值动态调整:
    float threshold = max(0.001, t * 1e-4);
    if (dist < threshold) return p;
    
  • 包围盒提前检查:在步进前计算光线与球体包围盒的交点,若光线完全在所有球体外部,直接返回空值减少循环次数:
    float closestT = 1e10;
    for (int j = 0; j < planetCount; j++) {
        vec3 oc = ro - sphereOrigin[j];
        float b = dot(oc, rd);
        float c = dot(oc, oc) - sphereRadius[j] * sphereRadius[j];
        float discriminant = b*b - c;
        if (discriminant > 0.0) {
            float t0 = -b - sqrt(discriminant);
            if (t0 > 0.0 && t0 < closestT) closestT = t0;
        }
    }
    if (closestT == 1e10) return vec3(0.0);
    t = max(t, closestT); // 直接跳到第一个可能的交点附近
    
  • 精简迭代次数:将最大迭代次数设置为64-128次(256次过于保守),同时添加最小步进距离判断防止无限循环:
    if (dist < 1e-6) break;
    

3. 调试SDF与交点的有效方法

  • 可视化SDF距离:将SDF返回的距离值映射为颜色输出,直观观察距离场分布:
    // 替换main函数中的输出
    FragColor = vec4(vec3(dist * 0.1), 1.0);
    
  • 标记步进点:每隔N次步进输出一个颜色标记,观察光线走向是否正确:
    if (i % 10 == 0) {
        FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        return;
    }
    
  • 验证光线方向:直接输出光线方向rd的RGB值,检查屏幕不同位置的方向是否符合预期:
    FragColor = vec4(normalize(rd) * 0.5 + 0.5, 1.0);
    
  • 单物体测试:暂时屏蔽多球体循环,只计算单个球体的SDF,排除多物体合并导致的错误。

内容的提问来源于stack exchange,提问作者Sengeki

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 21:35:55