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

Chai3D直接体绘制边界网格边缘伪影问题排查求助

问题:Chai3D直接体绘制中承载体边界网格边缘遮挡失效伪影

我在Chai3D中使用直接体绘制(Direct Volume Rendering)时遇到伪影问题:承载体的边界网格边缘始终可见,即使有其他物体位于其前方遮挡。

边界伪影

以下是核心Shader代码:

uniform vec3 uMinCorner;
uniform vec3 uMaxCorner;
uniform vec3 uTextureScale;
uniform vec3 uGradientDelta;
uniform sampler3D uVolume;
uniform sampler1D uColorLUT;
uniform float uIsosurface;
uniform float uOpacityThreshold;
uniform float uOpticalDensityFactor;
uniform float uResolution;

varying vec4 vPosition;

vec3 dx = vec3(uGradientDelta.x, 0.0, 0.0);
vec3 dy = vec3(0.0, uGradientDelta.y, 0.0);
vec3 dz = vec3(0.0, 0.0, uGradientDelta.z);

float entry(vec3 e1, vec3 d)
{
    float t = distance(uMinCorner, uMaxCorner);
    vec3 a = (uMinCorner - e1) / d;
    vec3 b = (uMaxCorner - e1) / d;
    vec3 u = min(a, b);
    return max(max(-t, u.x), max(u.y, u.z));
}

vec3 gradient(vec3 tc)
{
    vec3 nabla = vec3(
        texture3D(uVolume, tc + dx).r - texture3D(uVolume, tc - dx).r,
        texture3D(uVolume, tc + dy).r - texture3D(uVolume, tc - dy).r,
        texture3D(uVolume, tc + dz).r - texture3D(uVolume, tc - dz).r
    );
    return (nabla / uGradientDelta) * uTextureScale;
}

vec3 shade(vec3 p, vec3 v, vec3 n)
{
    vec4 lp = gl_ModelViewMatrixInverse * gl_LightSource[0].position;
    vec3 l = normalize(lp.xyz - p * lp.w);
    vec3 h = normalize(l + v);
    float cos_i = max(dot(n, l), 0.0);
    float cos_h = max(dot(n, h), 0.0);
    vec3 Ia = gl_FrontLightProduct[0].ambient.rgb;
    vec3 Id = gl_FrontLightProduct[0].diffuse.rgb * cos_i;
    vec3 Is = gl_FrontLightProduct[0].specular.rgb * pow(cos_h, gl_FrontMaterial.shininess);
    return (Ia + Id + Is);
}

void main(void)
{
    vec4 camera = gl_ModelViewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0);
    vec3 raydir = normalize(vPosition.xyz - camera.xyz);
    float t_entry = entry(vPosition.xyz, raydir);
    t_entry = max(t_entry, -distance(camera.xyz, vPosition.xyz));
    float t_step = distance(uMinCorner, uMaxCorner) / uResolution;
    vec3 tc_step = uTextureScale * (t_step * raydir);
    vec4 sum = vec4(0.0);
    vec3 tc = gl_TexCoord[0].stp + t_entry * tc_step / t_step;
    
    for (float t = t_entry; t < 0.0; t += t_step, tc += tc_step)
    {
        float intensity = texture3D(uVolume, tc).r;
        vec4 colour = texture1D(uColorLUT, intensity);
        if (colour.a < 0.001) continue;
        vec3 nabla = gradient(tc);
        vec3 position = vPosition.xyz + t * raydir;
        vec3 normal = -normalize(nabla);
        vec3 view = -raydir;
        vec3 shaded = shade(position, view, normal);
        colour.rgb *= shaded;
        float Tr = exp(-colour.a * uOpticalDensityFactor);
        colour.rgb *= 1.0 - Tr;
        colour.a = 1.0 - Tr;
        sum += (1.0 - sum.a) * colour;
        if (sum.a > uOpacityThreshold)
        {
            vec4 clip = gl_ModelViewProjectionMatrix * vec4(position, 1.0);
            gl_FragDepth = (gl_DepthRange.diff * clip.z / clip.w + gl_DepthRange.near + gl_DepthRange.far) * 0.5;
            break;
        }
    }
    
    gl_FragColor = sum;
}

渲染流程核心代码(来自renderSceneGraph):

//-----------------------------------------------------------------------
// Init
//-----------------------------------------------------------------------

glEnable(GL_LIGHTING);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);

//-----------------------------------------------------------------------
// Render bounding box, frame, collision detector. (opaque components)
//-----------------------------------------------------------------------
if (SECTION_RENDER_OPAQUE_PARTS_ONLY(a_options) && (!a_options.m_rendering_shadow))
{
    // disable lighting
    glDisable(GL_LIGHTING);

    // render boundary box
    if (m_showBoundaryBox)
    {
        // set size on lines
        glLineWidth(1.0);

        // set color of boundary box
        glColor4fv(s_boundaryBoxColor.getData());

        // draw box line
        cDrawWireBox(m_boundaryBoxMin(0) , m_boundaryBoxMax(0) ,
                     m_boundaryBoxMin(1) , m_boundaryBoxMax(1) ,
                     m_boundaryBoxMin(2) , m_boundaryBoxMax(2) );
    }

    // render collision tree
    if (m_showCollisionDetector && (m_collisionDetector != NULL))
    {
        m_collisionDetector->render(a_options);
    }

    // enable lighting
    glEnable(GL_LIGHTING);
}

// render frame
if (m_showFrame && (a_options.m_single_pass_only || a_options.m_render_opaque_objects_only))
{
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_INDEX_ARRAY);
    glDisableClientState(GL_EDGE_FLAG_ARRAY);
    glDisable(GL_COLOR_MATERIAL);

    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glColor4f(1.0,1.0,1.0,1.0);

    // set rendering properties
    glPolygonMode(GL_FRONT, GL_FILL);
        
    // draw frame
    cDrawFrame(m_frameSize, m_frameThicknessScale);
}

//-----------------------------------------------------------------------
// Render graphical representation of object
//-----------------------------------------------------------------------
if (m_showEnabled)
{
    // set polygon and face mode
    glPolygonMode(GL_FRONT_AND_BACK, m_triangleMode);

    // initialize line width
    glLineWidth(1.0f);

    /////////////////////////////////////////////////////////////////////
    // CREATING SHADOW DEPTH MAP
    /////////////////////////////////////////////////////////////////////
    if (a_options.m_creating_shadow_map)
    {
        glEnable(GL_CULL_FACE);
        glCullFace(GL_FRONT);

        // render object
        render(a_options);
        glDisable(GL_CULL_FACE);
    }

    /////////////////////////////////////////////////////////////////////
    // SINGLE PASS RENDERING
    /////////////////////////////////////////////////////////////////////
    else if (a_options.m_single_pass_only)
    {
        if (m_cullingEnabled)
        {
            glEnable(GL_CULL_FACE);
            glCullFace(GL_BACK);
        }
        else
        {
            glDisable(GL_CULL_FACE);
        }

        if (m_useTransparency)
        {
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glDepthMask(GL_FALSE);
        }
        else
        {
            glDisable(GL_BLEND);
            glDepthMask(GL_TRUE);
        }

        // render object
        render(a_options);

        // disable blending
        glDisable(GL_BLEND);
        glDepthMask(GL_TRUE);
    }


    /////////////////////////////////////////////////////////////////////
    // MULTI PASS RENDERING
    /////////////////////////////////////////////////////////////////////
    else
    {
        // opaque objects
        if (a_options.m_render_opaque_objects_only)
        {
            if (m_cullingEnabled)
            {
                glEnable(GL_CULL_FACE);
                glCullFace(GL_BACK);
            }
            else
            {
                glDisable(GL_CULL_FACE);
            }

            render(a_options);
        }

        // render transparent back triangles
        if (a_options.m_render_transparent_back_faces_only)
        {
            if (m_useTransparency)
            {
                glEnable(GL_BLEND);
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                glDepthMask(GL_FALSE);
            }
            else
            {
                glDisable(GL_BLEND);
                glDepthMask(GL_TRUE);
            }

            glEnable(GL_CULL_FACE);
            glCullFace(GL_FRONT);
            
            render(a_options);

            // disable blending
            glDisable(GL_BLEND);
            glDepthMask(GL_TRUE);
        }

        // render transparent front triangles
        if (a_options.m_render_transparent_front_faces_only)
        {
            if (m_useTransparency)
            {
                glEnable(GL_BLEND);
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                glDepthMask(GL_FALSE);
            }
            else
            {
                glDisable(GL_BLEND);
                glDepthMask(GL_TRUE);
            }

            glEnable(GL_CULL_FACE);
            glCullFace(GL_BACK);

            render(a_options);
    
            // disable blending
            glDisable(GL_BLEND);
            glDepthMask(GL_TRUE);
        }
    }
}

我已尝试单通道和多通道渲染,但问题依然存在。


原因分析与解决方案

1. 深度缓冲区处理不完整

Shader中仅在体素不透明度达到阈值时手动设置gl_FragDepth,但遍历完所有体素仍未达阈值的片段,会使用边界网格的插值深度而非体绘制最远采样点的深度。这导致边界网格深度覆盖实际体素深度,穿透前方遮挡物显示。

修复:在Shader末尾统一设置深度值:

// 在gl_FragColor = sum;之前添加
if (sum.a <= uOpacityThreshold) {
    vec3 finalPosition = vPosition.xyz + t * raydir;
    vec4 clip = gl_ModelViewProjectionMatrix * vec4(finalPosition, 1.0);
    gl_FragDepth = (gl_DepthRange.diff * clip.z / clip.w + gl_DepthRange.near + gl_DepthRange.far) * 0.5;
}

2. 边界网格深度写入优先级错误

边界网格在不透明阶段渲染且默认开启深度写入,会先占据深度缓冲区。后续体绘制片段因深度测试通过显示,但前方遮挡物的深度会被边界网格挡住,导致伪影。

修复:渲染边界网格时禁用深度写入:

// render boundary box
if (m_showBoundaryBox)
{
    glDepthMask(GL_FALSE); // 禁用深度写入
    glLineWidth(1.0);
    glColor4fv(s_boundaryBoxColor.getData());
    cDrawWireBox(m_boundaryBoxMin(0) , m_boundaryBoxMax(0) ,
                 m_boundaryBoxMin(1) , m_boundaryBoxMax(1) ,
                 m_boundaryBoxMin(2) , m_boundaryBoxMax(2) );
    glDepthMask(GL_TRUE); // 恢复深度写入
}

3. 射线入口计算精度问题

原entry函数的除法和极值计算存在精度丢失,可能导致射线起始位置超出包围盒,引发边界网格片段被错误纳入计算。

优化:使用更稳健的AABB相交算法:

float entry(vec3 e1, vec3 d)
{
    vec3 invDir = 1.0 / d;
    vec3 tMin = (uMinCorner - e1) * invDir;
    vec3 tMax = (uMaxCorner - e1) * invDir;
    vec3 tNear = min(tMin, tMax);
    vec3 tFar = max(tMin, tMax);
    
    float tEntry = max(max(tNear.x, tNear.y), tNear.z);
    float tExit = min(min(tFar.x, tFar.y), tFar.z);
    
    return (tEntry < tExit && tExit > 0.0) ? tEntry : -distance(uMinCorner, uMaxCorner);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 20:40:54