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
相关产品推荐
相关产品推荐

