深度法线纹理
深度平台差异
裁剪空间深度值
裁剪空间坐标(也称为投影后空间坐标)在 Direct3D 类和 OpenGL 类平台之间有所不同:
Direct3D :裁剪空间深度 Z值为 $[1,0]$。
OpenGL:裁剪空间深度 Z 值为 $[-1, 1]$。
在着色器代码内,可使用内置宏
UNITY_NEAR_CLIP_VALUE
来获取基于平台的近平面值。UNITY_NEAR_CLIP_VALUE
定义为近剪裁平面的值。 Direct3D 为 1.0,OpenGL 为–1.0在脚本代码内,使用
GL.GetGPUProjectionMatrix
将 Unity 的坐标系(遵循 OpenGL 类约定)转换为 Direct3D 类坐标(如果这是平台所期望的)。
深度 (Z) 方向
[!hint] 现代平台 :使用了 [[06 深度测试#^9bb785|Reversed direction技术]],相比传统平台翻转了 Z 值
DirectX 11,DirectX12,PS4,Xbox One,和 Metal:
- 裁剪空间的 Z 值范围是 $[near,0]$(near 表示近平面距离,在远平面处减小到 0.0)。
- NDC 的 Z 值范围为 $[1,0]$,对应 ZBuffer 的取值范围也为 $[1,0]$
- Unity 定义了
UNITY_REVERSED_Z
宏定义,用于判断是否是使用翻转 z 方向的平台
[!quote] 传统平台
- 在旧版 Direct3D 类平台上,范围是 $[0,far]$(表示在近平面处为 0.0,在远平面处增加到远平面距离)。
- 对应 NDC 的 Z 值值范围为 $[0,1]$
- 在 OpenGL 类平台上,裁剪空间的 Z 值范围是 $[-near,far]$。
- 对应 NDC 的 Z 值值范围为 $[-1,1]$。
- 由于深度值应该是 0~1 的数,所以 Unity 对其将其转换为 $[0,1]$ 存入 ZBuffer
[!summary] Unity 深度纹理和 NDC 的深度的关系
- 以 Unity OpenGL 平台为例, NDC 的的取值范围为 $[-1, 1]$ ,而深度纹理的取值范围为 $[0,1]$,两者的关系为 $d = Z_{ndc} * 0.5 + 0.5$。
- 以 Unity DX 平台为例, NDC 经过 Reverse-Z 的取值范围为 $[1, 0]$ ,深度纹理的取值范围为 $[1,0]$,两者的关系其实也符合为 $d = Z_{ndc} * 0.5 + 0.5$。
- 我们可以进行跨平台处理: [[#跨平台采样深度纹理]],让所有平台的 ZBuffer 范围都是 $[0,1]$ 或 $[1,0]$
跨平台采样深度纹理
我们做东西肯定要考虑跨平台,前面提到了不同平台生成的深度图是不同的,如 DirctX 近到远是 1 到 0,OpenGL 近到远是 0 到 1,那么怎么统一采样的值呢?根据前面的介绍我们知道 DirctX 等平台之所以是 1 到 0 是因为 unity 为其做了反转,那么我们再把它们转回来不就得了么。而对于这些进行了深度反转的平台,unity 都定义了名为 UNITY_REVERSED_Z 的宏,
如果想要各个平台 Zbuffer 都是 $[0,1]$:
1 | float depth = tex2D(_CameraDepthTexture, uvSS).r; |
如果想要各平台 Zbuffer 都是 $[1,0]$:
1 |
|
使用裁剪空间
如果要手动使用裁剪空间 (Z) 深度,则可能还需要使用以下宏来抽象化平台差异:
1 | float clipSpaceRange01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(rawClipSpace); |
注意:此宏不会改变 OpenGL 或 OpenGL ES 平台上的裁剪空间,因此在这些平台上,此宏返回“-near”1(近平面)到 far(远平面)之间的值。
投影矩阵
如果处于深度 (Z) 发生反转的平台上,则 GL.GetGPUProjectionMatrix()
返回一个还原了 z 的矩阵。但是,如果要手动从投影矩阵中进行合成(例如,对于自定义阴影或深度渲染),您需要通过脚本按需自行还原深度 (Z) 方向。
以下是执行此操作的示例:
1 | var shadowProjection = Matrix4x4.Ortho(...); //阴影摄像机投影矩阵 |
深度 (Z) 方向检查工具
- 使用
SystemInfo.usesReversedZBuffer
可确认所在平台是否使用反转深度 (Z)。
理论与推导
[!note] 深度纹理
Depth Texture = 深度纹理 = 深度图
上面保存了深度缓冲区的值,是非线性深度,使用时要先转换成线性深度
[!note] 约定
本节采用 OpenGL 标准进行推导
- 列向量
- 模型空间、世界空间、观察空间是右手坐标系,而裁剪空间与 NDC 是左手坐标系
- Camera 的 LookAt (Forward)方向为 $- z$ 轴方向
- NDC 空间范围 $[-1,1]^3$
- OpenGL 使用距离值表示 $n、f$。$n$ 被映射到 $-1$,$f$ 被映射到 $1$
深度是指像素到摄像机的距离,观察空间的深度为线性深度,NDC 空间的深度为非线性深度。
当我们想要精确表达物体的深度差异或者重建像素世界坐标位置,就需要使用将非线性深度转化为线性深度。
非线性深度
假如在 MV 变换后,观察空间(View Space) 下的某个点对应的齐次坐标为 $(x,y,z,1)$,那么经过透视投影变换和 GPU 裁剪后转换到齐次裁剪空间(Clip Space),变换过程如下:(该变换同样适用于 Unity,Unity 与 OpenGL 投影矩阵相同)
![[02 空间变换#^bgahra]]
我们只关注深度,即 $\displaystyle z’=-(\frac{f+n}{f-n})z-\frac{2fn}{f-n}$
然后进行齐次除法转换到NDC 空间
$$ z’’=\frac{z’}{w’}=(\frac{f+n}{f-n})+\frac{2fn}{(f-n)z}\tag{1}$$
$z’’$ 值范围为 $[-1,1]$ ,而 ZBuffer 中存储的值应该为 $[0,1]$,所以我们将 NDC 空间深度值($z’’$ ) 的范围转换到 $[0,1]$:($NonLinearDepth$ 与 $\displaystyle \frac{1}{z}$ 相关,是非线性的,即非线性深度)
$$NonLinearDepth = z’’\times0.5+0.5=\frac f{f-n}+\frac{2fn}{(f-n)\color\red{z}}\tag{2}$$
带入 $z=n, z=f$ 可得近平面 $NonLinearDepth$ 为 $1$,远平面 $NonLinearDepth$ 为 $0$
范围转换前后对比,横轴为 z 值。可以看出靠近摄像机的十个单位占了 90%的深度缓冲区精度,故离摄像机越远的值精度越低
线性深度
观察空间的深度为线性深度
线性深度受 far 的影响
线性深度分为两种:
- $LinearEyeDepth$:观察空间下的线性深度值,取值范围$[n, f]$
- $Linear01Depth$:把线性深度归一化到$[0,1]$,我们通常会使用这个线性深度
$$
\begin{aligned}&LinearEyeDepth=-Pview.z\\&Linear01Depth=\frac{-Pview.z-n}{f-n}or\frac{-Pview.z}{f}\end{aligned}
$$
由上一节方程(1)(2)可得:
$$ \begin{cases}z’’=(\frac{f+n}{f-n})+\frac{2fn}{(f-n)z} \
z’’ = NonLinearDepth\times2-1\end{cases}$$
联立可以求出
$$z=\frac1{(\frac{f-n}{fn}*NonlinearDepth-\frac1n)}$$
由于世界空间以 $-Z$ 为正方向,所以求深度需要取反得到正数:
$$LinearEyeDepth=\frac{1}{(\frac{n-f}{fn}*NonlinearDepth+\frac1n)}$$
然后将 $LinearEyeDepth$ 除以 $f$ 即可得到归一化的线性深度 $Linear01Depth$
$$
\begin{aligned}Linear01Depth&=(\frac{1}{(\frac{n-f}{fn}*NonlinearDepth+\frac{1}{n})}\text{-n})/(f\text{-n})\\or&=\frac{1}{(\frac{n-f}{n}*NonlinearDepth+\frac{f}{n})}\end{aligned}
$$
曲线对比图
深度纹理重建像素的世界空间坐标
使用 VP 逆矩阵重建
设 NDC 空间上的点 $P_{ndc}$ 映射到屏幕空间上为点 $P(x, y)$,$P (x, y)$ 点对应的屏幕 uv 为 $(u, v)$。
设 ${P.x=u}{Width}, {P.y=v}{Height}$
⭐1. 由屏幕空间转换到 NDC 空间
从 NDC 空间到屏幕空间,点 P 相对于左下角坐标的比例是不变的,可以列出等式:
$P_{ndc}. z$ 是 NDC 空间的深度,由前文, $P_{ndc}.z=2*NonlinearDepth-1$
则:
$$
\begin{array}{lcr}P_{ndc}.x=2u-1\ P_{ndc}.y=2v-1\ P_{ndc}.z=2*NonlinearDepth-1\ P_{ndc}.w=1.0\end{array}
$$
⭐2. 由 NDC 空间转换到齐次裁剪空间
由齐次除法可知 $\displaystyle \frac{Pclip}{Pclip.w}=Pndc$,则
$$\displaystyle P_{clip}=P_{ndc}*P_{clip}.w \tag{1}$$
⭐3. 由齐次裁剪空间转换到世界空间
因为 $P_{clip}$ 是由 $P_{world}$ 经过 $VP$ 矩阵变换得来,我们将 $VP$ 矩阵写作 $M$ ,则 $MP_{world} = P_{clip}$,带入(1)
$$P_{world}=M^{-1}P_{clip}=M^{-1}P_{ndc}*P_{clip}.w\tag{2}$$
因为 $P_{world}=(x,y,z,1)$ ,我们将其 $w$ 分量分量带入(2)
$$P_{world}.w=({M^{-1}}P_{ndc}).w*P_{clip}.w=1$$
$$P_{clip}.w={\frac1{(M^{-1}P_{ndc}).w}}\tag{3}$$
将(3)带入(2)即可得出世界空间坐标:
$$P_{world}=\frac{M^{-1}P_{ndc}}{(M^{-1}P_{ndc}).w}$$
代码实现红线标记处即可:
1 | //1 脚本获取VP逆矩阵 |
使用摄像机射线构建
使用 VP 逆矩阵的方法需要在片元着色器中进行矩阵乘法,通常会影响性能。本节介绍的方法性能更好。
首先对图像空间下的视锥体射线(从摄像机出发,指向图像上某点的射线)进行插值,这条射线存储了该像素在世界空间下到摄像机的方向信息。然后把该射线和线性化后的观察空间下的深度相乘,再加上摄像机的世界位置,就可以得到该像素在世界空间下的位置。
Unity3D Shader系列之深度纹理重建世界坐标_textrue3d unity 切片重建
在某些情况下,我们需要屏幕后处理阶段得到像素点对应的世界坐标。如下图,我们在屏幕后处理阶段,想要知道屏幕空间中 $A1$ 点对应的世界坐标 $A$ 点。那么 $A$ 点该怎么求呢?
@ 已知条件:
- $O$ 点的世界坐标(即相机的世界坐标):
_WorldSpaceCameraPos
- $OD$ 的长度(观察空间的线性深度值
linearEyeDepth
) :可以采样深度纹理得到 - 透视相机的各项参数:
- 近、远裁剪平面的值
- 视口角度 $FOV$
- 横纵比 $Aspect$
- $O$ 点的世界坐标(即相机的世界坐标):
! $A$ 点的世界坐标 = $O$ 点的世界坐标 + $\overrightarrow{OA}$,我们要做的就是求 $\overrightarrow{OA}$
% 主要步骤如下:
- 求 $\mathrm{\overrightarrow{OLT},\overrightarrow{OLB},\overrightarrow{ORT},\overrightarrow{ORB}}$( $\overrightarrow {OLT}$ 即从相机指向 $LT$ 的向量)
- 当顶点为 $LT$ 点(即屏幕左上角)时将 $\overrightarrow {OLT}$ 向量的值放置在顶点着色器输出结构体中
- 当顶点为 $LB$ 点(即在屏幕左下角)时将 $\overrightarrow {OLB}$ 放置在顶点着色器输出结构体中
- 当顶点为 $RT$ 点(即在屏幕右上角)时将 $\overrightarrow {ORT}$ 放置在顶点着色器输出结构体中
- 当顶点为 $RB$ 点(即在屏幕右下角)时将 $\overrightarrow {ORB}$ 放置在顶点着色器输出结构体中
- 利用 GPU 硬件的插值(顶点着色器的输出结构体会在三角形遍历阶段进行重心坐标插值,然后将插值后的值传递给片元着色器使用),得到 $\overrightarrow {OA1}$
- 利用三角形的相似关系,可以得到 $\overrightarrow {OA}$
步骤 1
- @ 求 $\mathrm{\overrightarrow{OLT},\overrightarrow{OLB},\overrightarrow{ORT},\overrightarrow{ORB}}$
为了方便计算,我们可以先计算两个向量——$toTop$ 和 $toRight$, 它们是起点位于近裁剪平面中心、分别指向摄像机正上方和正右方的向量。它们的计算公式如下:
$$
halfHeight=Near\times\tan\biggl(\frac{FOV}2\biggr)
$$
$$
to Top = camera.up \times halfHeight
$$
$$
toRight =camera.right \times halfHeight \cdot aspect
$$
$camera.up$ 是单位向量,指向摄像机正上方,只是用来确定向量方向
得到这两个辅助向量后,就可以计算 4 个角(图中的 TL、TR、BL、BR)相对于摄像机的方向了,只需要简单的向量运算:
$$
\begin{gathered}
\overrightarrow{OLT}=camera.forward \cdot Near+to Top-to Right\
\overrightarrow{OLB}=camera.forward·Near-toTop-toRight \
\overrightarrow{ORT}=camera.forward·Near+toTop+toRight \
\overrightarrow{ORB}=camera,forward\cdot Near-toTop+toRight
\end{gathered}
$$
注意这四个向量不仅包含了方向信息,它们的模对应了四个点到摄像机的距离。
步骤 2
- @ 利用 GPU 硬件的插值,得到 $\overrightarrow {OA1}$
这一步是这种方法的核心。只有真正理解了这一步,才可以说是真正理解了这种方法。
我们先要明白什么是屏幕后处理。相机渲染完场景中所有物体后会得到一张渲染纹理,但是我们不直接把这张渲染纹理显示在屏幕上,而是额外对这张渲染纹理的每一个像素点进行处理一遍(这个过程就叫做屏幕后处理),然后将屏幕后处理的结果递到屏幕上。
屏幕后处理一般是通过额外渲染一个与屏幕大小相同的矩形网格来实现的。该网格只有 2 个三角面,共 4 个顶点,如下图。对每个像素的额外处理则会放到片元着色器中,具体处理的是哪一个像素用 uv 坐标来得到。
我们知道,在渲染流水线中,GPU 会在三角形设置阶段对顶点着色器输出结构体中的值进行重心坐标插值,然后再传递给片元着色器,就像下图这样。
也就是说,我们在步骤 1 中传递的 $\mathrm{\overrightarrow{OLT},\overrightarrow{OLB},\overrightarrow{ORT},\overrightarrow{ORB}}$ 经过 GPU 硬件的插值后,在片元着色器中将会得到(方向和长度通过重心坐标插值都能得到)。
这一步根本不用写代码,GPU 硬件已经实现了。
步骤 3
- @ 利用三角形的相似关系,可以得到 $\overrightarrow {OA}$
我们得到的线性深度值 linearEyeDepth
并非是摄像机的欧氏距离,而是在 $z$ 方向的距离。
如图,以世界空间中的的 $A$ 点为例
$|OA|$ 是 A 点到摄像机的距离
$|OD|$ 为 A 点在观察空间的线性深度
$|OD1|$ 是相机的近裁剪平面距离
$\overrightarrow {OA1}$ 在第二步插值得到 ,用图中公式即可求得 $\overrightarrow {OA}$
代码(待验证)
首先在 C #中传递需要用到的向量 。(这里是用 RendererFeature 写的后处理)
1 | public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { |
在 Shader 中,进行还原。
1 | // 根据线性深度值和屏幕UV,还原世界空间下,相机到顶点的位置偏移向量 |
Unity 深度法线纹理
获取深度纹理
Unity 深度纹理存储了高精度的深度值,范围是 $[1,0]$,是非线性深度。
@ 1 首先要开启 URP Asset ->Depth Texture 并设置 Depth Texture Mode 为 Force Prepass 或 Depth Priming Mode 设置为 Auto 或Force
⭐方法二:RenderFeature 的 SetupRenderPasses 中设置 ConfigureInput,这样就可以采样到深度纹理了。1
m_renderPass.ConfigureInput(ScriptableRenderPassInput.Depth);
@ 2 采样深度纹理,计算线性深度
_CameraDepthTexture
:深度纹理_ZBufferParams
:用于线性化 Z 缓冲区值。x
是 (1-near/far),y
是 (far/near),z
是 (x/far),w
是 (y/far)。LinearViewDepth
: 把深度纹理的采样结果转换成观察空间下的深度值,返回范围在 $[near, far]$ 的线性深度值Linear01Depth
:返回范围在 $[0,1]$ 的线性深度值
1 | //声明深度纹理 |
1 | //内部声明了深度纹理_CameraDepthTexture |
- @ 3 深度图内对象添加 pass
要想对象在深度纹理中显示,对象的 shader 需要加一个 pass:
可以直接添加 Lit 内置的 pass,这种方法会打破 SRPBatcher 的,可以自己写一个来匹配 SRPBatcher 条件fold title:Lit-DepthOnly 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42Pass
{
Name "DepthOnly"
Tags
{
"LightMode" = "DepthOnly"
}
// -------------------------------------
// Render State Commands
ZWrite On
ColorMask R
Cull[_Cull]
HLSLPROGRAM
// -------------------------------------
// Shader Stages
// -------------------------------------
// Material Keywords
// -------------------------------------
// Unity defined keywords
//--------------------------------------
// GPU Instancing
// -------------------------------------
// Includes
ENDHLSL
}
1 | //景深Pass |
获取深度+法线纹理
- @ 1 开启深度法线纹理
方法一:(不推荐,消耗大)首先添加 SSAO RenderFeature,Source->Depth Normals这里也可单选 depth,来实现获取深度纹理
⭐方法二:RenderFeature 的 SetupRenderPasses 中设置 ConfigureInput,这样就可以采样到法线纹理了。
1 | m_renderPass.ConfigureInput(ScriptableRenderPassInput.Normal); |
@ 2 采样深度+法线纹理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//在shader脚本中定义
TEXTURE2D(_CameraDepthTexture); SAMPLER(sampler_CameraDepthTexture);
TEXTURE2D(_CameraNormalsTexture); SAMPLER(sampler_CameraNormalsTexture);
//或者直接引用,内部声明了纹理和采样器
//⭐深度使用方法和上节一样
//⭐法线使用方法:
//屏幕空间uv
float2 ScreenUV = GetNormalizedScreenSpaceUV(i.positionCS);
//采样即可
float3 normal = SampleSceneNormals(ScreenUV);@ 3 法线图内对象添加 pass
要想对象在法线纹理中显示,对象的 shader 需要加一个 pass:
可以直接添加 Lit 内置的 pass,这种方法会打破 SRPBatcher 的,可以自己写一个来匹配 SRPBatcher 条件fold title:DepthNormals 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49// This pass is used when drawing to a _CameraNormalsTexture texture
Pass
{
Name "DepthNormals"
Tags
{
"LightMode" = "DepthNormals"
}
// -------------------------------------
// Render State Commands
ZWrite On
Cull[_Cull]
HLSLPROGRAM
// -------------------------------------
// Shader Stages
// -------------------------------------
// Material Keywords
// -------------------------------------
// Unity defined keywords
// -------------------------------------
// Universal Pipeline keywords
//--------------------------------------
// GPU Instancing
// -------------------------------------
// Includes
ENDHLSL
}
1 | //深度法线Pass |
深度纹理重建像素的世界空间位置
代码
[[03 深度法线纹理#^hl5tio|原理手算]]
运用内置 API 更方便!
1 |
|
步骤
包含文件:DeclareDepthTexture. hlsl 文件包含用于对摄像机深度纹理进行采样的实用程序:
SampleSceneDepth
返回[0, 1]
范围内的 $Z$ 值。title:包含文件 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//包含如下:已经声明了相机深度纹理,我们只需要传入屏幕空间uv调用采样函数
TEXTURE2D_X_FLOAT(_CameraDepthTexture);
SAMPLER(sampler_CameraDepthTexture);
float SampleSceneDepth(float2 uv)
{
return SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, UnityStereoTransformScreenSpaceTex(uv)).r;
}
float LoadSceneDepth(uint2 uv)
{
return LOAD_TEXTURE2D_X(_CameraDepthTexture, uv).r;
}在片元着色器中计算用于采样深度纹理的屏幕空间 UV 坐标,像素位置除以渲染目标分辨率
_ScaledScreenParams
。_ScaledScreenParams.xy
属性会考虑渲染目标的任何缩放,例如动态分辨率。title:用深度纹理和屏幕空间uv重建像素的世界空间位置 1
2
3//屏幕空间uv
float2 ScreenUV = GetNormalizedScreenSpaceUV(i.positionCS);
//float2 ScreenUV = i.positionCS.xy / _ScaledScreenParams.xy; 等价在片元着色器中,使用
SampleSceneDepth
函数对深度缓冲区进行采样。title:从深度纹理中采样深度 1
2
3
4
5
6
7
8
9
// 具有 REVERSED_Z 的平台(如 D3D)的情况。
//返回[1,0]的深度值
real depth = SampleSceneDepth(ScreenUV);
// 没有 REVERSED_Z 的平台(如 OpenGL)的情况。
// 调整 Z 以匹配 OpenGL 的 NDC([-1, 1])
real depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(ScreenUV));用像素的 UV 和 Z 坐标重建世界空间位置。
title:重建世界空间位置 1
float3 rebuildPosWS = ComputeWorldSpacePosition(ScreenUV, depth, UNITY_MATRIX_I_VP);
ComputeWorldSpacePosition
:根据屏幕空间 UV 和深度 ($Z$) 值计算世界空间位置UNITY_MATRIX_I_VP
是一个逆观察投影矩阵,可将点从裁剪空间变换为世界空间。对于未渲染几何图形的区域,深度缓冲区可能没有任何有效值。以下代码会在这些区域绘制黑色。
1
2
3
4
5
6
7
8//在远裁剪面附近将颜色设置为黑色。
if(depth < 0.0001)
return half4(0,0,0,1);
if(depth > 0.9999)
return half4(0,0,0,1);不同的平台对远裁剪面使用不同的 Z 值(0 == far,或 1 == far)。
UNITY_REVERSED_Z
常量让代码可以正确处理所有平台。对象要在重建的世界坐标中显示,需要添加深度法线纹理pass
深度纹理重建法线
在了解如何用深度还原位置信息之后还原法线就非常容易了,其实对于一个着色点,只需要求出他的上下左右的位置信息,然后利用叉乘来近似计算该点的法线即可,伪代码如下
1 | vec3 P = GetViewPos(v2f_TexCoords); |
求最小的变换向量是为了让法线的变换根据平滑一点,如果觉得采样太多也只需要采用x,y方向各一个点即可。
应用
深度相交高亮
世界空间位置差
做交接边的思路:通过深度重建世界坐标,
要求深度图只渲染不透明物体,这样深度图记录的是 B 点的深度,进而重建出 B 点的世界坐标。
1 | float3 posDistance = saturate(distance(rebuildPosWS, i.positionWS) / _MaxDepth); |
观察空间深度差
1 | float DepthDifference = saturate(linearEyeDepth - i.positionCS.w / _MaxDepth); |
护盾/能量场
1 | //深度交接出白边 |
全局雾效
思路是让雾的浓度随着深度值的增大而增大,然后进行的原图颜色和雾颜色的插值:
1 | fixed4 frag (v2f i) : SV_Target |
Fog 场景
扫描线
深度扫线
深度图中存的是深度值,减去一个对应的扫描线深度(整个深度图都的深度都减去这个值)。这样小于等于这个扫描线深度的部分值小于等于 0:
取绝对值,扫描线位置就会变黑。加一个 saturate 防止过曝
除以
_LineWidth
,_LineWidth
<1 时候越小,扫描线越细最后 lerp 混合原图:
1
2
3float linearEyeDepth = LinearEyeDepth(depth,_ZBufferParams);
float v = saturate(abs(linearEyeDepth-_ScanDepth)/_LineWidth);
return lerp(_ScanLineColor_,color,v);
重建世界坐标画线
重建的世界坐标取小数+取余数,即可
得到世界坐标后,因为我们的坐标轴取值范围是从 -∞到 +∞,而颜色的范围只是 0-1 之间,如果对世界坐标使用 frac 取小数就可以得到只在 0-0.99 的值了
1 | return float4(frac(rebuildPosWS),1); |
取余数
对于在 0-1 之间均匀变换的我们想得到它的边界位置,所以直接来个 step 函数,我们只取 0.98 到 1.0 之间的值为 1,其他的值为 0,我们把它输出出来。是不是有那味了,线框就直接出来了。
但是这个线框的颜色红蓝绿(为什么是红蓝绿?是因为它对应 XYZ 三个轴向)很乱而且不好看,我们想要自由控制颜色,我们定义三个颜色,去分别控制 XYZ 方向的线框颜色,并把它输出。
重建世界坐标扫线
1 | //x方向 |
水淹
利用上面提到的第二种重建世界空间坐标的方法得到世界空间坐标,判断该坐标的 Y 值是否在给定阈值下,如果是则混合原图颜色和水的颜色:
1 | float3 rebuildPosWS = ComputeWorldSpacePosition(ScreenUV, depth, UNITY_MATRIX_I_VP); |