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

极简球体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空间的深度偏移量替代手动调试值,步骤如下:

  1. 传递视图空间的球心与半径
    在顶点着色器中,将球心和半径转换到视图空间后传递给片段着色器:

    // 顶点着色器新增输出
    out vec3 viewSphereCenter;
    out float viewSphereRadius;
    
    void main() {
        // ...原有代码...
        viewSphereCenter = (viewMatrix * vec4(sphereCenter.xyz, 1.0)).xyz;
        viewSphereRadius = radius; // 假设radius是世界空间半径,若视图空间有缩放需同步调整
    }
    
  2. 计算片段对应的球体表面深度
    在片段着色器中,通过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);
    }
    

    这种方式完全不需要手动调参,精度满足粒子系统需求。

二、补充代码出错的原因及修正

错误点分析

  1. 深度差值符号错误:视图空间中,球体最近点(朝向相机)的Z值比球心更接近0(离相机更近),因此aD2(最近点NDC Z)大于aD1(球心NDC Z),用aD1 - aD2会得到负数,导致偏移方向反向,把球体推向相机。
  2. 深度映射公式错误:你使用的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 13:55:23