极简球体Impostor:如何计算球体的Z深度半径?
问题描述
我正尝试编写一款高效的极简球体Impostor——该球体无需着色,仅需修正gl_FragDepth即可。现有代码已接近实现,核心代码如下:
顶点着色器
out_XYZ=float4(sphereCenter.xyz,1.)*sceneComboMatrix; switch(gl_VertexID) { case 0: out_XYZ.xy+=vec2(radius,-radius); out_UV=float2(1.,-1.); break; case 1: out_XYZ.xy+=vec2(-radius,-radius); out_UV=float2(-1.,-1.); break; case 2: out_XYZ.xy+=vec2(radius,radius); out_UV=float2(1.,1.); break; case 3: out_XYZ.xy+=vec2(-radius,radius); out_UV=float2(-1.,1.); break; }
片段着色器
float aLen2=dot(in_UV,in_UV); float aL=inversesqrt(aLen2); if (aL<1.) discard; out_Color=float4(1.,0.,0.,1.); float magicNumber=.0031f; gl_FragDepth=gl_FragCoord.z-(sin(1.-(1./aL))*(3.14/2.)*magicNumber);
当前代码可作为概念验证,但需将手动调试的magicNumber替换为球体在Z深度中的半径值。请问如何计算球体半径对应的Z深度值?
注:无需超高精度,用于粒子允许轻微错误,但需比当前效果更准确。
补充问题
我参考资料编写了计算球体最近点与中心点Z深度差的代码,但结果会将球体过度推向相机。为何该代码无法正确计算Z空间中球体中心与近点的深度差?代码如下:
vec4 aSPos=vec4(spherePos.xyz,1.)*sceneComboMatrix; float aD1=aSPos.z/aSPos.w; aSPos=vec4(spherePos.xyz+(normalTowardCamera.xyz*sphereRadius),1.)*sceneComboMatrix; float aD2=aSPos.z/aSPos.w; radiusLengthInZ=((gl_DepthRange.diff*(aD1-aD2))+gl_DepthRange.near+gl_DepthRange.far)/2.0;
解决方案
一、替换magicNumber的正确思路
核心是用球体NDC空间的深度偏移量替代手动调试值,步骤如下:
传递视图空间的球心与半径
在顶点着色器中,将球心和半径转换到视图空间后传递给片段着色器:// 顶点着色器新增输出 out vec3 viewSphereCenter; out float viewSphereRadius; void main() { // ...原有代码... viewSphereCenter = (viewMatrix * vec4(sphereCenter.xyz, 1.0)).xyz; viewSphereRadius = radius; // 假设radius是世界空间半径,若视图空间有缩放需同步调整 }计算片段对应的球体表面深度
在片段着色器中,通过UV得到球体表面的视图空间法线,进而计算表面点的深度:in vec3 viewSphereCenter; in float viewSphereRadius; in vec2 in_UV; void main() { float aLen2 = dot(in_UV, in_UV); if (aLen2 > 1.0) discard; // 超出范围直接丢弃 // 计算球体表面的视图空间法线 vec3 viewNormal = normalize(vec3(in_UV.x, in_UV.y, sqrt(1.0 - aLen2))); // 得到视图空间的表面点位置 vec3 viewSurfacePos = viewSphereCenter + viewNormal * viewSphereRadius; // 转换到裁剪空间,计算NDC Z值 vec4 clipPos = projectionMatrix * vec4(viewSurfacePos, 1.0); float ndcZ = clipPos.z / clipPos.w; // 映射到gl_DepthRange定义的深度范围 float surfaceDepth = gl_DepthRange.near + (gl_DepthRange.far - gl_DepthRange.near) * (ndcZ + 1.0) / 2.0; gl_FragDepth = surfaceDepth; out_Color = float4(1.0, 0.0, 0.0, 1.0); }这种方式完全不需要手动调参,精度满足粒子系统需求。
二、补充代码出错的原因及修正
错误点分析
- 深度差值符号错误:视图空间中,球体最近点(朝向相机)的Z值比球心更接近0(离相机更近),因此
aD2(最近点NDC Z)大于aD1(球心NDC Z),用aD1 - aD2会得到负数,导致偏移方向反向,把球体推向相机。 - 深度映射公式错误:你使用的NDC到窗口深度的转换公式不符合OpenGL标准逻辑,导致计算出的深度差值完全错误。
修正后的代码
// 转换球心到视图空间 vec3 viewSphereCenter = (viewMatrix * vec4(spherePos.xyz, 1.0)).xyz; // 视图空间中,相机朝向是-Z方向,最近点是球心向+Z方向偏移半径 vec3 viewNearPoint = viewSphereCenter + vec3(0.0, 0.0, 1.0) * sphereRadius; // 计算球心和最近点的NDC Z值 vec4 clipCenter = projectionMatrix * vec4(viewSphereCenter, 1.0); float ndcCenterZ = clipCenter.z / clipCenter.w; vec4 clipNear = projectionMatrix * vec4(viewNearPoint, 1.0); float ndcNearZ = clipNear.z / clipNear.w; // 转换到窗口空间深度,计算差值 float centerDepth = gl_DepthRange.near + (gl_DepthRange.far - gl_DepthRange.near) * (ndcCenterZ + 1.0) / 2.0; float nearDepth = gl_DepthRange.near + (gl_DepthRange.far - gl_DepthRange.near) * (ndcNearZ + 1.0) / 2.0; float radiusLengthInZ = centerDepth - nearDepth; // 最近点深度更小,差值为正
内容的提问来源于stack exchange,提问作者KiraHoneybee
相关产品推荐
相关产品推荐

