《Unity Shader 入门精要》 第十三章 使用深度法线和纹理 笔记

使用深度法线和纹理

获取深度和法线纹理

原理

深度纹理
  • 就是一张渲染纹理,存储的像素值是高精度的深度值。
  • 范围是[0,1],通常是非线性分布的。
  • 深度值来自于顶点变换后得到的归一化的设备坐标
  • 顶点转换到齐次裁剪空间后,z坐标就是它的深度值。但是z分量的范围是[-1,1]所以我们为了能将其存储在图像中,使用:
    d = 0.5*z + 0.5
    将其变为[0,1]范围中。
  • Unity中深度纹理可以来自于真正的深度缓存,也可以由单独一个Pass渲染获得,这取决于渲染路径和硬件。
  • 使用延迟渲染路径可以直接从G-buffer中获得深度信息。
  • 当无法直接获取深度缓存时,深度和法线纹理是通过一个单独的Pass渲染获得。
  • 具体实现是,Unity会使用着色器替换技术选择渲染类型为Opaque的物体,判断它使用的渲染队列是否小于2500(包括Background、Geometry和AlphaTest),如果满足条件就把他渲染到深度和法线纹理中。
  • 在Unity中,我们可以选择让一个摄像机生成一张深度纹理或是一张深度+法线纹理。
  • 选择前者时,Unity会直接获得深度缓存或者按之前讲到的着色器替换技术,使用物体投射阴影时使用的Pass来获取深度纹理。如果shader中不包含这样的pass,那么物体就不会出现在深度纹理中。深度纹理的精度通常是24位或者16位,这取决于深度缓存的精度。
  • 如果选择生成一张深度+法线纹理,Unity会创建一张和屏幕分辨率相同、精度为32位的纹理,其中观察空间下的法线信息会被编码进纹理的R和G通道,深度信息会被编码进B和A通道。法线信息在延迟渲染路径下很容易获取,将法线和深度缓存合并即可。在前向渲染中,默认情况下不会创建法线缓存,因此Unity底层使用一个单独的Pass把整个场景再次渲染一遍来完成。这个Pass被包含在Unity内置的一个Unity Shader中,可以在builtin_shaders-xxx/DefaultResources/Camera-DepthNormalTexture.shader文件中找到这个用于渲染深度和法线信息的Pass。

如何获取

  • 获取深度纹理十分简单,在脚本中设置摄像机的depthTextureMode来完成:
camera.depthTextureMode = DepthTextureMode.Depth;
  • 之后在shader中声明_CameraDepthTexture变量来访问它。
  • 同理,获取深度+法线纹理,我们只需要在代码中设置:
camera.depthTextureMode = DepthTextureMode.DepthNormals;
  • 之后在shader中声明_CameraDepthNormalTexture变量来访问它。
  • 通常情况下,我们直接使用tex2D对纹理进行采样即可,但在某些平台上(例如PS3和PS2),我们需要一些特殊处理。Unity为我们提供了一个统一的宏,SAMPLE_DEPTH_TEXTURE来处理这些平台差异。
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
  • i.uv是一个float2类型的变量,对应了当前像素的纹理坐标。
  • 类似的宏还有SAMPLE_DEPTH_TEXTURE_PROJ,同样接受两个参数,第二个参数是一个float3或者float4类型的纹理坐标,前两个分量是uv坐标,会使用前两个分量除以第三个分量再进行采样,如果提供第四个分量,会再进行一次比较,通常用于阴影的实现中。
    例如:
float d = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture,UNITY_PROJ_COORD(i.srcPos));
  • i.srcPos是顶点着色器中通过调用ComputerScreenPos(o.pos)得到的屏幕坐标。
  • 取样得到的z分量是非线性的,我们需要把它转换到线性空间中,例如视角空间。
  • 齐次裁剪空间转换到视角空间的方法可以由视角空间到齐次裁剪空间的公式逆推得到。
  • Unity提供了两个辅助函数来为我们进行上述的计算过过程:LinearEyeDepth和Linear01Depth。
  • LinearEyeDepth负责把采样获得的深度值转换到观察空间下的深度值
  • Linear01Depth则会返回一个范围在[0,1]的线性深度值。
  • 两个函数使用了内置变量_ZBufferParams变量来得到远近裁剪平面的距离。
  • 如果我们需要获得深度+法线纹理,可以tex2D函数对_CameraDepthNormalTexture进行采样。
  • Unity提供了辅助函数DecodeDeothNormal对采样结果进行解码:
inline void DecodeDepthNormal(float4 enc,out float depth,out float3 normal){
    depth = DecodeFloatRG(enc.zw);
    normal = DecodeViewNormalStereo(enc);
}
  • 第一个参数是对深度法线纹理的采样结果,这个结果是Unity对深度法线信息进行了编码后的结果,xy分量存储的是视角空间下的法线信息,而深度信息被编码进了zw分量。调用辅助函数后就可以得到解码后的深度和法线信息了。这个深度值是范围在[0,1]的线性深度值(与单独的深度纹理中存储的深度值不同),得到的法线则是视角空间下的法线方向。同样,我们也可以通过调用DecodeFloatRG和DecodeViewNormalStereo来解码深度+法线纹理中的深度和法线信息。

查看深度和法线纹理

  • 在设置了摄像机渲染深度和法线纹理后可以在Frame Debug中查看。
  • 单独深度纹理展示的是非线性空间下的深度值;深度加法线纹理展示的是编码以后的结果。
  • 我们可以编写shader来显示线性空间或者解码后的结果。

再谈运动模糊

  • 运动模糊更广泛的技术是使用速度映射图。速度映射图存储了每个像素的速度,然后使用这个速度来决定模糊的大小和方向。
  • 速度缓冲生成的方法很多。一种是把场景中所有物体速度渲染到一张纹理中。这种方法缺点是在于需要修改场景中所有物体的shader代码,使其添加计算速度的代码并输出到一个渲染纹理中。
  • 《GPU Gems3》中介绍了一种生成速度映射图的方法。这种方法利用深度纹理在片元着色器中为每个像素计算其在世界空间下的位置,通过使用当前视角*投影矩阵的逆矩阵对NDC下的顶点坐标进行变换得到的。用相同方法得到前一帧中该位置的NDC坐标。然后对其求差值,生成该像素的速度。优点是在屏幕后处理时完成了整个效果的模拟;缺点是需要在片元着色器中进行两次矩阵乘法的操作,对性能有所影响。

全局雾效

  • 雾效是游戏里经常使用的一种效果。Unity内置的雾效可以产生基于距离的线性或指数雾效。想要在自己编写的顶点/片元着色器中实现这些雾效,我们需要在Shader中添加#pragma multi_compile_fog指令,同时还需要使用相关内置宏,例如UNITY_FOG_COORDS、UNITY_TRANSFER_FOG 和 UNITY_APPLY_FOG等。
  • 这种方法的缺点在于,我们不仅需要为场景中的所有物体添加相关渲染代码,而且能够给实现的效果也非常有限。
  • 我们将学习一种基于屏幕后处理的全局雾效。这种方法可以方便地模拟各种雾效。
  • 基于屏幕后处理的全局雾效的关键是,根据深度纹理来重建每个像素在世界空间下的位置。
  • 重建方法:(比运动模糊中的更效率)对图像空间下的视锥体射线(从摄像机出发,指向图像上某点的射线)进行插值,这条射线存储了该像素在世界空间下到摄像机的方向信息。然后我们把该射线和线性化后的视角空间下的深度值相乘,再加上摄像机的世界位置,就可以得到该像素在世界空间下的位置。当我们得到世界坐标后,就可以使用各个公式来模拟全局雾效了。

再谈边缘检测

  • 之前我们使用的是Sobel算子,根据颜色来进行边缘检测实现描边效果。但是会得到许多我们不希望得到的边缘线。
  • 我们将要使用深度和法线纹理进行边缘检测。
  • 本节会使用Roberts算子进行边缘检测。
  • 通过纹理采样,获取比较当前片元的左上与右下、左下与右上的片元的法线与深度值的相差大小,如果高于某个阈值,则认为这是一条边。这样的方法相对基于颜色的边缘检测更加精确。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值