Unity_Shader高级篇_13_Unity Shader入门精要

第13章 使用深度和法线纹理
在12章中,屏幕后处理效果都只是在屏幕颜色图像上进行各种操作来实现的。然而,很多时候我们不仅需要当前屏幕的颜色信息,还希望得到深度和法线信息。例如,在进行边缘检测时,直接利用颜色信息会使检测到的边缘信息受物体纹理和法线纹理上进行边缘检测,这些图像不会受纹理和光照的影响,而仅仅保存了当前渲染物体的模型信息,通过这样的方式检测出的边缘更加可靠。
本章中,我们将学习如何在Unity中获取深度纹理和法线纹理来实现特定的屏幕后处理效果。在13.1节中,我们首先会学习如何在Unity中获取这两种纹理。在13.2节中,我们会利用深度纹理来计算摄影机的移动速度,实现摄影机的运动模糊效果。在13.3节中,我们会学习如何利用深度纹理来重建屏幕像素在世界空间中的位置,从而模拟屏幕雾效。13.4节会再次学习边缘检测的另一种实现,即利用深度和法线纹理进行边缘检测。

13.1 获取深度和法线纹理
获取原理:深度纹理实际就是一张渲染纹理,只不过它里面存储的像素值不是颜色值,而是一个高精度的深度值。由于被存储在一张纹理中,深度纹理里的深度值范围是[0,1],而且通常是非线性分布的。那么,这些深度值是从哪里得到的呢?要回答这个问题,我们需要回顾在第四章学过的顶点变换的过程。总体来说,这些深度值来自于顶点变换后得到的归一化的设备坐标(Normalized Device Coordinates,NDC)。
图13.1显示了4.6.7小节中给出的Unity中透视投影对顶点的变换过程。左图显示了投影变换前,即观察空间下视锥体的结构及相应的顶点位置,中间的图显示了应用透视裁剪矩阵后的变换结果,即顶点着色器阶段输出的顶点变换结果,最右侧的图则是底层硬件进行了透视出发后得到的归一化的设备坐标。需要注意的是,这里的投影过程是建立在Uinty对坐标系的假定上的,也就是说,我们针对的是观察空间为右手坐标系,使用列矩阵在矩阵右侧进行相乘,且变换到NDC后z分量范围将在[-1,1]之间的情况。而在类似DirectX这样的图形接口中,变换后z分量范围将在[0,1]之间。如果需要在其他图形接口下实现本章的类似效果,需要对一些计算参数做出相应变化。变换时使用的矩阵运算(4.6.7)
这里写图片描述
图13.2显示了在使用正交摄像机时投影变换的过程。同样,变换后会得到一个范围为[-1,1]的立方体。正交投影使用的变换矩阵是线性的。
这里写图片描述
在得到NDC后,深度纹理中的像素值就可以很方便地计算得到了,这些深度值就对应了NDC中顶点坐标的z分量的值。由于NDC中z分量的范围在[-1,1],为了让这些值能够存储在一张图像中,我们需要使用下面的公式对其进行映射:

d = 0.5·z(ndc) + 0.5

其中d对应了深度纹理中的像素值,z(ndc)对应了NDC坐标中的z分量的值。
在Unity中,深度纹理可以直接来自于真正的深度缓存,也可以是由一个单独的Pass渲染而得,这取决于使用的渲染路径和硬件。通常来将,当使用延迟渲染路径(包括遗留的渲染路径)时,深度纹理理所当然可以访问到,因为延迟渲染会把这些信息渲染到G-buffer中。而当无法直接获取深度缓存时,深度和法线纹理是通过一个单独的Pass渲染而得的。具体实现是,Unity会使用着色器替代(Shader Replacement)技术选择那些渲染类型(即SubShader的RenderType标签)为Opaque的物体,判断它们使用的渲染队列是否小于等于2500(内置的Background、Geometry和AlphaTest渲染队列均在此范围内),如果满足条件,就把它渲染到深度和法线纹理中。因此,要想让物体能够出现在深度和法线纹理中,就必须在Shader中设置正确的RenderType标签。
在Unity中,我们选择让一个摄影机生成一张深度纹理或是一张深度+法线纹理。选择前者,Unity会直接获取深度缓存或是按之前讲到的着色器替代技术,选取需要的不透明物体,并使用它投射阴影时使用的Pass(即LightMode被设置为ShadowCaster的Pass(9.4))来得到深度纹理。如果Shader中不包含这样一个Pass,那么这个物体就不会出现在深度纹理中(当然,它也不能向其他物体投射阴影)。深度纹理的精度通常是24位或16位,这取决于使用的深度缓存的精度。如果选择后者,Unity会创建一张和屏幕分辨率相同、精度为32位(每个通道为8位)的纹理,其中观察空间下的法线信息会被编码进纹理的R和G通道,而深度信息会被编码进B和A通道。法线信息的获取再延迟渲染中是可以非常容易就得到的,Unity只需要合并深度和法线缓存即可。而在前向渲染中,默认情况下是不会创建法线缓存的,因此Unity底层使用了一个单独的Pass把整个场景再次渲染一遍来完成。这个Pass被包含在Unity内置的一个Unity Shader中,我们可以在内置的builtin_shaders-xxx/DefaultResources/Camera-DepthNormalTexture.shader文件中找到这个用于渲染深度和法线信息的Pass。
如何获取

camera.depthTextureMode = DepthTextureMode.Depth;

一旦设置好了上面的摄像机模式后,我们就可以在Shader中通过声明_CameraDepthTexture变量来访问它。
同理,如果想要获取深度+法线纹理,我们只需要在代码中这样设置:

camera.depthTextureMode = DepthTextureMode.DepthNormals;

然后在Shader中通过声明_CameraDepthNormalsTexture变量来访问它。
我们还可以组合这些模式,让一个摄像机同时产生一张深度和深度+法线纹理:

camera.depthTextureMode |= DepthTextureMode.Depth;
camera.depthTextureMode |= DepthTextureMode.DepthNormals;

在Unity5中,我们还可以在摄像机的Camera组件上看到当前摄像机是否渲染深度或深度+法线纹理。当在Shader中访问到深度深度纹理_CameraDepthTexture后,我们就可以使用当前像素的纹理坐标对它进行采样。绝大多下,我们直接使用tex2D函数采样即可,但在某些平台(例如PS3和PSP2)上,我们需要一些特殊处理;Unity为我们提供了一个统一 的宏(SAMPLE_DEPTH+FEXTURE).用来处理这些由于平台差异造成的的 。而我们只需要在Shader在中使用SAMPLR_DEPTH_ETXTURE宏对深度纹理进行采样,例如:

// 在这个像素点获得深度缓冲值。
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);

其中,i.uv是一个float2类型的变量,对应了当前像素的纹理坐标。类似的宏还有SAMPLE_DEPTH_TEXTURE_PROJ(预计样本)和SAMPLE_DEPTH_TEXTURE_LOD(具有LOD级别的样本)。SAMPLE_DEPTH_TEXTURE_PROJ宏同样接受两个参数——深度纹理和一个float3或float4类型的纹理坐标,它的内部使用了tex2Dproj这样的函数进行投影纹理采样,纹理坐标的前两个分量首先会除以最后一个分量,再进行纹理采样。如果提供了第四个分量,还会进行一次比较,通常用于阴影的实现中。SAMPLE_DEPTH_TEXTURE_PROJ的第二个参数通常是由顶点着色器输出插值而得到屏幕坐标:

float d = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture,UNITY_PROJ_COORD(i.scrPos));

其中,i.scrPos是在着色器中通过调用ComputeScreenPos(o.pos)得到的屏幕坐标。上述这些宏的定义,在内置的HLSLSupport.cginc文件中找到。
当通过纹理采样得到深度值后,这些深度值往往是非线性的,这种非线性来自于透视投影使用的剪裁矩阵。然而,在我们的计算过程中通常是需要线性的深度值,也就是说,我们需要把投影后的深度值变换到线性空间下,例如视角空间下的深度值。实际上,我们是倒推顶点变换的过程即可。
Unity提供了两个辅助函数(LinearEyeDepth和Linear01Depth)。LinearEyeDepth负责把深度纹理的采样结果转换到视角空间下的深度值。而Linear01Depth则会返回一个范围在[0,1]的线性深度值。这两个函数内部使用了内置的_ZBufferParams变量来得到远近裁剪平面的距离。
如果我们需要获取深度+法线纹理,可以直接使用tex2D函数对_CameraDepthNormalsTexture进行采样,得到里面存储的深度和法线信息。Unity提供了辅助函数来为我们对这个采样结果进行解码,从而得到深度值和法线方向。这个函数是DecodeDepthNormal,它在UnityCG.cginc里被定义:

inline void DecodeDepthNormal (float4 enc, out float depth, out float3 normal)
{
     depth = DecodeFloatRG(enc.zw);
     normal = DecodeViewNormalStereo(enc);
}

DecodeDepthNormal 的第一个参数是对深度+法线纹理的采样结果,这个采样结果是Unity对深度和法线信息编码后的结果,它的xy分量存储的是视角空间下的法线信息,而深度信息被编码进了zw分量。通过调用DecodeDepthNormal 函数对采样结果解码后,我们就可以得到解码后的深度值和法线。这个深度值是范围在[0,1]的线性深度值(这与单独的深度纹理中存储的深度值不同),而得到的法线则是视角空间下的法线方向。同样,我们也可以通过调用DecodeFloatRG和DecodeViewNormalStereo来解码深度+法线纹理中的深度和法线信息。

查看深度和法线纹理
利用帧调试器(Frame Debugger)。
下图中左边列表看到的是深度纹理,右侧为深度+法线纹理。如果当前摄像机需要生成深度和法线纹理,帧调试器的面板中就会出现相应的渲染事件。只要单击对应的事件就可以查看得到的深度和法线纹理。
这里写图片描述
使用帧调试器查看到的深度纹理是非线性空间的深度值,而深度+法线纹理都是由Unity

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Shader是一种用于渲染图形的程序,它可以控制对象的表面颜色、纹理、透明度、反射等属性,从而实现特殊的视觉效果。对于游戏开发者来说,掌握Shader编写技巧是非常重要的。 以下是关于Unity Shader入门精要: 1. ShaderLab语言 ShaderLab是Unity中用于编写Shader的语言,它是一种基于标记的语言,类似于HTML。ShaderLab可以用于定义Shader的属性、子着色器、渲染状态等信息。 2. CG语言 CG语言是Unity中用于编写Shader的主要语言,它是一种类似于C语言的语言,可以进行数学运算、向量计算、流程控制等操作。CG语言可以在ShaderLab中嵌入,用于实现Shader的具体逻辑。 3. Unity的渲染管线 Unity的渲染管线包括顶点着色器、片元着色器、几何着色器等组件,每个组件都有不同的作用。顶点着色器用于对对象的顶点进行变换,片元着色器用于计算每个像素的颜色,几何着色器用于处理几何图形的变形和细节等。 4. 模板和纹理 在Shader中,我们可以使用纹理来给对象添加图案或者贴图,也可以使用模板来控制对象的透明度、反射等属性。纹理可以通过内置函数tex2D()来获取,模板可以通过内置函数clip()来实现裁剪。 5. Shader的实现 Shader的实现需要注意以下几点: - 在ShaderLab中定义Shader的属性、子着色器、渲染状态等信息。 - 在CG语言中实现Shader的具体逻辑,包括顶点着色器、片元着色器等内容。 - 使用纹理和模板来实现特定的视觉效果。 - 在对象上应用Shader,通过调整Shader的属性来达到不同的效果。 以上是关于Unity Shader入门精要,希望对你有所帮助。如果你想更深入地了解Shader的编写技巧,可以参考官方文档或者相关教程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值