ChuanXin 发表于 2023-3-6 11:07

Unity中深度值推导

关于Unity在透视投影的情况下深度值计算的剖析
变量说明


[*]f :far plane(远裁剪面)到eye(Camera point)的距离,正值
[*]n :near plane(近裁剪面)到eye(Camera point)的距离,正值
[*]z_{eye} :顶点在View space中的z分量
[*]z_{clip} :顶点在Homogeneous clip space中的z分量
[*]w_{clip} :顶点在Homogeneous clip space中的w分量
[*]z_{ndc} :顶点在NDC空间的z分量,也就是 \frac{z_{clip}}{w_{clip}}
[*]z_{depth} :顶点在DepthBuffer中的深度值
[*]z_{linear} :顶点在View space中在near到far之间的线性值,near是0,far是1
[*]z_{linear2} :顶点在View space中的eye到far之间是线性值,eye是0,far是1
View space

对应的变换是Camera.worldToCameraMatrix,也就是常说的viewMatrix,用于将世界空间中的点变换到View space(Camera space)中

[*]Unity用于计算的View space(Camera.worldToCameraMatrix)是OpenGL形式,是右手坐标系,Y-up,相机朝向负Z轴
[*]Unity用于展示的View space(在编辑器中的坐标系显示)是左手坐标系,Y-up,相机朝向正Z轴,虽然有区别,但是并不影响计算
[*]Shading过程中使用的viewMatrix是Camera.worldToCameraMatrix
[*]通过这个矩阵变换出来的有效 z_{eye} 是负值
Clip space

对应的变换是GL.GetGPUProjectionMatrix返回的值,也就是常说的projectionMatrix,用于将View space中的点变换到Clip space中,变换过程使用的是齐次坐标系(Homogeneous coordinate system),变换的结果 (x_{clip},y_{clip},z_{clip},w_{clip}) 是在Homogeneous clip space中,也是顶点着色器的输出结果,接着GPU会进行Clipping和Perspective division最后变换到NDC空间


OpenGL-like(OpenGL and OpenGL ES)和Direct3D-like(Direct3D, Metal and consoles)对Clip space的表达是不一样的,这会导致projectionMatrix会不一样,这也就是Unity提供GL.GetGPUProjectionMatrix的原因,GL.GetGPUProjectionMatrix会将Camera.projectionMatrix转换为当前GPU平台对应的表达

[*]Camera.projectionMatrix:Unity的投影矩阵计算是使用OpenGL形式
[*]GL.GetGPUProjectionMatrix:会根据当前GPU平台将Unity的投影矩阵转换为对应的投影矩阵
[*]Shading过程使用的projectionMatrix是来自GL.GetGPUProjectionMatrix
OpenGL-like

类似OpenGL平台下深度值的计算剖析,比如OpenGL and OpenGL ES
Clip space




[*]坐标系是左手系
[*]Y-up
[*]相机朝正Z方向
[*]和Viewing frustum的映射关系:

[*]x轴:left plane(-1) to right plane(1)
[*]y轴:bottom plane(-1) to top plane(1)
[*]z轴:front plane(-1) to back plane(1)

Projection Matrix

变换的结果为 (x_{clip},y_{clip},z_{clip},w_{clip}) ,其中:

[*]w_{clip}=-z_{eye}
[*]z_{clip}={{-\frac{f+n}{f-n}z_{eye}}-{\frac{2fn}{f-n}}}
深度值推导


[*]通过投影变换以及Perspective division最后得到的 z_{ndc}=\frac{z_{clip}}{w_{clip}} 是在(-1, 1)范围内
[*]最后写入DepthBuffer的值是映射到(0, 1)范围内的 z_{depth}=\frac{z_{ndc}+1}{2}
[*]前面View space介绍过 z_{eye} 是负值,所以计算 z_{linear} 和 z_{linear2} 的时候需要处理成正值进行计算

[*]z_{linear}=\frac{-z_{eye}-n}{f-n}
[*]z_{linear2}=\frac{-z_{eye}}f

[*]根据投影矩阵关于深度值的计算如下
\begin{aligned} w_{clip}&=-z_{eye}\\ z_{clip}&={{-\frac{f+n}{f-n}z_{eye}}-{\frac{2fn}{f-n}}}\\ z_{ndc}&=\frac{z_{clip}}{w_{clip}}\\ &=\frac{{-\frac{f+n}{f-n}z_{eye}}-{\frac{2fn}{f-n}}}{-z_{eye}}\\ &=\frac{f+n}{f-n}+{\frac{2fn}{(f-n)z_{eye}}}\\ z_{depth}&=\frac{z_{ndc}+1}{2}\\ &=\frac{f}{f-n}+{\frac{fn}{(f-n)z_{eye}}}\\ -z_{eye}&=\frac{2fn}{(f+n)-z_{ndc}(f-n)}\\ &=\frac{fn}{f-z_{depth}(f-n)}\\ &=\frac{1}{\frac{1}n+z_{depth}\frac{n-f}{fn}}\\ z_{linear}&=\frac{-z_{eye}-n}{f-n}\\ &=\frac{1}{(1-\frac{f}{n})+\frac{f}{nz_{depth}}}\\ z_{linear2}&=\frac{-z_{eye}}f\\ &=\frac1{z_{depth}(1-\frac{f}n)+\frac{f}n} \end{aligned}
Direct3D-like

类似Direct3D-like平台下深度值的计算剖析,比如Direct3D, Metal and consoles,这些平台下还需要考略Reversed Z的情况
Traditional direction

没有Reversed z情况
Clip space




[*]坐标系是左手系
[*]Y-up
[*]相机朝正Z方向
[*]和Viewing frustum的映射关系:

[*]x轴:left plane(-1) to right plane(1)
[*]y轴:bottom plane(-1) to top plane(1)
[*]z轴:front plane(0) to back plane(1)

Projection Matrix

和典型的Direct3D下的变换会有不一样,主要是Unity中View space的表达是OpenGL形式的,是右手坐标系,变换的结果为 (x_{clip},y_{clip},z_{clip},w_{clip}) ,其中:

[*]w_{clip}=-z_{eye}
[*]z_{clip}={{\frac{f}{n-f}z_{eye}}+{\frac{fn}{n-f}}}={{-\frac{f}{f-n}z_{eye}}-{\frac{fn}{f-n}}}
深度值推导


[*]通过投影变换以及Perspective division最后得到的 z_{ndc}=\frac{z_{clip}}{w_{clip}} 是在(0, 1)范围内
[*]最后写入DepthBuffer的值是映射到(0, 1)范围内的 z_{depth}=z_{ndc}
[*]前面View space介绍过 z_{eye} 是负值,所以计算 z_{linear} 和 z_{linear2} 的时候需要处理成正值进行计算

[*]z_{linear}=\frac{-z_{eye}-n}{f-n}
[*]z_{linear2}=\frac{-z_{eye}}f

[*]根据投影矩阵关于深度值的计算如下
\begin{aligned} w_{clip}&=-z_{eye}\\ z_{clip}&=-{{\frac{f}{f-n}z_{eye}}-{\frac{fn}{f-n}}}\\ z_{ndc}&=\frac{z_{clip}}{w_{clip}}\\ &=\frac{-{\frac{f}{f-n}z_{eye}}-{\frac{fn}{f-n}}}{-z_{eye}}\\ &=\frac{f}{f-n}+{\frac{fn}{(f-n)z_{eye}}}\\ z_{depth}&={z_{ndc}}\\ -z_{eye}&=\frac{fn}{f-z_{ndc}(f-n)}\\ &=\frac{1}{\frac1n+z_{ndc}\frac{n-f}{fn}}\\ &=\frac{1}{\frac1n+z_{depth}\frac{n-f}{fn}}\\ z_{linear}&=\frac{-z_{eye}-n}{f-n}\\ &=\frac{1}{(1-\frac{f}{n})+\frac{f}{nz_{depth}}}\\ z_{linear2}&=\frac{-z_{eye}}f\\ &=\frac1{z_{depth}(1-\frac{f}n)+\frac{f}n} \end{aligned}

[*]z_{linear} 和 z_{linear2} 和前面的OpenGL-like的计算结果一致
Reversed direction

是Reversed z的情况,目前Unity也仅仅在Direct3d-like的平台上支持Reversed z
Clip space


[*]坐标系是左手系
[*]Y-up
[*]相机朝负Z方向
[*]和Viewing frustum的映射关系:

[*]x轴:left plane(-1) to right plane(1)
[*]y轴:bottom plane(-1) to top plane(1)
[*]z轴:front plane(1) to back plane(0)

Projection Matrix

和典型的Direct3D下的变换会有不一样,主要是Unity中View space的表达是OpenGL形式的,是右手坐标系,同时还进行了Reversed z的变换,变换的结果为 (x_{clip},y_{clip},z_{clip},w_{clip}) ,其中:

[*]w_{clip}=-z_{eye}
[*]z_{clip}={{\frac{f}{f-n}z_{eye}}+{\frac{fn}{f-n}}}
深度值推导


[*]通过投影变换以及Perspective division最后得到的 z_{ndc}=\frac{z_{clip}}{w_{clip}} 是在(1, 0)范围内
[*]最后写入DepthBuffer的值是映射到(1, 0)范围内的 z_{depth}=z_{ndc}
[*]前面View space介绍过 z_{eye} 是负值,所以计算 z_{linear} 和 z_{linear2} 的时候需要处理成正值进行计算

[*]z_{linear}=\frac{-z_{eye}-n}{f-n}
[*]z_{linear2}=\frac{-z_{eye}}f

[*]根据投影矩阵关于深度值的计算如下
\begin{aligned} w_{clip}&=-z_{eye}\\ z_{clip}&={{\frac{n}{f-n}z_{eye}}+{\frac{fn}{f-n}}}\\ z_{ndc}&=\frac{z_{clip}}{w_{clip}}\\ &=\frac{{\frac{n}{f-n}z_{eye}}+{\frac{fn}{f-n}}}{-z_{eye}}\\ &=-\frac{n}{f-n}-{\frac{fn}{(f-n)z_{eye}}}\\ z_{depth}&={z_{ndc}}\\ -z_{eye}&=\frac{fn}{n+z_{ndc}(f-n)}\\ &=\frac{1}{\frac1f+z_{ndc}\frac{f-n}{fn}}\\ &=\frac{1}{\frac1f+z_{depth}\frac{f-n}{fn}}\\ z_{linear}&=\frac{-z_{eye}-n}{f-n}\\ &=\frac{1}{(1-\frac{f}{n})+\frac{f}{n(1-z_{depth})}}\\ z_{linear2}&=\frac{-z_{eye}}f\\ &=\frac1{z_{depth}(\frac{f}n-1)+1} \end{aligned}

[*]z_{linear} 和 z_{linear2} 和前面的OpenGL-like的计算结果有变化
Unity SRP中的计算

ScriptableRenderer中_ZBufferParams的计算

函数SetPerCameraShaderVariables中关于_ZBufferParams的计算
// From <http://www.humus.name/temp/Linearize%20depth.txt>
// But as depth component textures on OpenGL always return in 0..1 range (as in D3D), we have to use
// the same constants for both D3D and OpenGL here.
// OpenGL would be this:
// zc0 = (1.0 - far / near) / 2.0;
// zc1 = (1.0 + far / near) / 2.0;
// D3D is this:
float zc0 = 1.0f - far * invNear;
float zc1 = far * invNear;

Vector4 zBufferParams = new Vector4(zc0, zc1, zc0 * invFar, zc1 * invFar);

if (SystemInfo.usesReversedZBuffer)
{
    zBufferParams.y += zBufferParams.x;
    zBufferParams.x = -zBufferParams.x;
    zBufferParams.w += zBufferParams.z;
    zBufferParams.z = -zBufferParams.z;
}
Traditional direction

非Reversed z情况

[*]\_ZBufferParams.x = 1-\frac{f}{n}
[*]\_ZBufferParams.y = \frac{f}{n}
[*]\_ZBufferParams.z = \frac{n-f}{fn}
[*]\_ZBufferParams.w = \frac{1}{n}
Reversed direction

Reversed z情况

[*]\_ZBufferParams.x = \frac{f}{n}-1
[*]\_ZBufferParams.y = 1
[*]\_ZBufferParams.z = \frac{f-n}{fn}
[*]\_ZBufferParams.w = \frac{1}{f}
Common.hlsl中关于LinearDepth的计算

// ----------------------------------------------------------------------------
// Depth encoding/decoding
// ----------------------------------------------------------------------------

// Z buffer to linear 0..1 depth (0 at near plane, 1 at far plane).
// Does NOT correctly handle oblique view frustums.
// Does NOT work with orthographic projection.
// zBufferParam = { (f-n)/n, 1, (f-n)/n*f, 1/f }
float Linear01DepthFromNear(float depth, float4 zBufferParam)
{
    return 1.0 / (zBufferParam.x + zBufferParam.y / depth);
}

// Z buffer to linear 0..1 depth (0 at camera position, 1 at far plane).
// Does NOT work with orthographic projections.
// Does NOT correctly handle oblique view frustums.
// zBufferParam = { (f-n)/n, 1, (f-n)/n*f, 1/f }
float Linear01Depth(float depth, float4 zBufferParam)
{
    return 1.0 / (zBufferParam.x * depth + zBufferParam.y);
}

// Z buffer to linear depth.
// Does NOT correctly handle oblique view frustums.
// Does NOT work with orthographic projection.
// zBufferParam = { (f-n)/n, 1, (f-n)/n*f, 1/f }
float LinearEyeDepth(float depth, float4 zBufferParam)
{
    return 1.0 / (zBufferParam.z * depth + zBufferParam.w);
}

[*]Linear01DepthFromNear就是 z_{linear}
[*]Linear01Depth就是 z_{linear2}
[*]LinearEyeDepth就是 -z_{eye}
Traditional direction


[*]Linear01DepthFromNear等于 \frac1{(1-\frac{f}n)+\frac{f}{nz_{depth}}} ,这个值和前面的推导的结果一致,在OpenGL-like和Direct3D-like上都是这个结果
[*]Linear01Depth等于 \frac1{(1-\frac{f}n)z_{depth}+\frac{f}{n}} ,这个值和前面推导的结果一致,在OpenGL-like和Direct3D-like上都是这个结果
[*]LinearEyeDepth等于 \frac1{\frac{n-f}{fn}z_{depth}+\frac{1}{n}} ,这个值和前面推导的结果一致,在OpenGL-like和Direct3D-like上都是这个结果
Reversed direction


[*]Linear01DepthFromNear等于 \frac1{(\frac{f}n-1)+\frac{1}{z_{depth}}} ,这个值和前面的推导的结果不一致,推导的结果是 \frac1{(1-\frac{f}n)+\frac{f}{n(1-z_{depth})}} ,从Reversed direction情况下的_ZBufferParams是无法计算出这个值的,这应该是错误的,所以大家不要使用这个函数
[*]Linear01Depth等于 \frac1{(\frac{f}n-1)z_{depth}+1} ,这个值和前面推导的结果一致,在Direct3D-like上是这个结果,目前也仅仅Direct3D-like支持Reversed z,所以不需要考虑OpenGL-like的情况
[*]LinearEyeDepth等于 \frac1{\frac{f-n}{fn}z_{depth}+\frac{1}{f}} ,这个值和前面推导的结果一致,在Direct3D-like上是这个结果,目前也仅仅Direct3D-like支持Reversed z,所以不需要考虑OpenGL-like的情况
页: [1]
查看完整版本: Unity中深度值推导