unity-shader-深度图及其应用_unity深度图-程序员宅基地

技术标签: unity  shader  深度图  Unity3d  depth  Unity3D-Shader  Unity3D .  


title: unity-shader-深度图及其应用
categories: Unity3d-Shader
tags: [unity, shader, 深度图, depth]
date: 2019-05-13 20:44:51
comments: false

unity-shader-深度图及其应用


前篇


开启渲染 深度图

需要 Camera 组件的 depthTextureMode 中的深度位设置为1, 也就是用 运算 DepthTextureMode.Depth

using UnityEngine;

public class DepthTextureTest : MonoBehaviour {
    
    void OnEnable() {
    
        GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;
    }

    void OnDisable() {
    
        GetComponent<Camera>().depthTextureMode &= ~DepthTextureMode.Depth;
    }
}

然后在 shader 中定义 uniform 变量 , 类型时 sampler2D, 命名必须是 _CameraDepthTexture

sampler2D _CameraDepthTexture;

// 采样深度图, 用模型的 屏幕坐标([0, 1]区间) 去 采样 场景深度图 获取场景 深度值 (非线性的 [0, 1] 区间)
float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos));

深度图

深度图的使用时比较耗费批次的, 场景中的 不透明 ( opaque ) 物体需要渲染两遍, 第一遍 ( 也就是多出来的那一倍 ) 渲染是为了的得到深度纹理.

所以 深度图 不等于 深度缓存

  • 深度图是提前把 不透明 ( opaque ) 物体全都渲染了一遍从而得到的
  • 深度缓存 是在渲染每一个物体是根据给定的 深度比较条件 从而写进去的值.
  • 注意: 通过增加 深度写入pass 的方式, 也不能将该物体写入到 深度图 中, 因为pass只在正常渲染下起作用.
    参考: [透明 ( transparent ) 物体写入深度的姿势](#透明 ( transparent ) 物体写入深度的姿势)

深度纹理并非深度缓冲中的数据,而是通过特定Pass获得

深度图 不等于 深度缓存

Unity4.X和Unity5.X版本的实现方式不太一样,Unity4.X通过"RenderType" 标签通过Camera Shader 替换获取,Unity5通过ShadowCaster Pass获取,Unity5官方文档描述:

Depth texture is rendered using the same shader passes as used for shadow caster rendering (ShadowCaster pass type). So by extension, if a shader does not support shadow casting (i.e. there’s no shadow caster pass in the shader or any of the fallbacks), then objects using that shader will not show up in the depth texture.
Make your shader fallback to some other shader that has a shadow casting pass, or If you’re using surface shaders, adding an addshadow directive will make them generate a shadow pass too.
Note that only “opaque” objects (that which have their materials and shaders setup to use render queue <= 2500) are rendered into the depth texture.

对于自身带有 ShadowCaster Pass 或者 FallBack 中含有,并且 Render Queue 小于等于 2500 的渲染对象才会出现在深度纹理中,详细的测试可以参考:【Unity Shader】Shadow Caster、RenderType和_CameraDepthTexture

深度图里存放了**[0,1]范围的非线性分布的深度值,这些深度值来自NDC坐标。
在延迟渲染中,深度值默认已经渲染到G-buffer;而在前向渲染中,你需要去
申请**,以便Unity在背后利用Shader Replacement将RenderType为Opaque、渲染队列小于等于2500并且有ShadowCaster Pass的物体的深度值渲染到深度图中。

实际测试

即使关了 深度写入 ZWrite Off , 深度图 还是有这个对象的深度信息. (使用 framedebugger 调试)


注意

需要注意的是 能量场 ( 透明渲染 ) _CameraDepthTexture 中只保存了场景中不透明物体的深度信息,因此这个时候无法从CameraDepthTexture 中获取 能量场 的深度信息,所以要在 vert 中计算顶点的深度,这里我利用了 COMPUTE_EYEDEPTH 这个内置的宏。在之后的 frag 内就可以很方便的获取场景和能量场当前片元的深度了。
参考: http://www.php361.com/index.php?c=index&a=view&id=5257

//vert
o.screenPos = ComputeScreenPos(o.vertex);
COMPUTE_EYEDEPTH(o.screenPos.z);

//frag
float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
float partZ = i.screenPos.z;
相交判断

以上面 能量场 (ForceField) 为例, ForceField 丢到透明度队列渲染, 则该物体不会再就不会写入到深度图中, 从 Frame Debug 中可以看出

所以可以用 vert 计算出 深度值, 与 深度图 中的值 ( 也就是场景中 不透明 物体的z值 ) 相比较, 小于某个阈值时可以定义为相交.

//vert
o.screenPos = ComputeScreenPos(clipPos); // ComputeScreenPos 的参数是剪裁空间下的位置
COMPUTE_EYEDEPTH(o.screenPos.z); // 计算出 顶点位置 视空间 的 z 值, 保存到 o.screenPos.z 

//frag
float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)); // 用模型的 屏幕坐标([0, 1]区间) 去 采样 场景深度图 获取场景 深度值
float sceneZ = LinearEyeDepth(depth); // 把 深度值 转换到 视空间
float partZ = i.screenPos.z;

// 两者相减就是深度的差异diff,再用1 - diff就得到了一个“相交程度”。
float diff = sceneZ - partZ; // 两个值都在同一空间下, 就可以做比较了
float intersect = (1 - diff) * _IntersectPower;
相关宏
#	define COMPUTE_EYEDEPTH(o) o = -UnityObjectToViewPos( v.vertex ).z # 计算出 视空间 的 z 值
#   define SAMPLE_DEPTH_TEXTURE_PROJ(sampler, uv) (tex2Dproj(sampler, uv).r)
#	define UNITY_PROJ_COORD(a) a

LinearEyeDepth 与 Linear01Depth 的使用场景

两者的共同点是 参数都是 用从 深度图 中采样出来的 深度值.

//vert
o.screenPos = ComputeScreenPos(clipPos); // ComputeScreenPos 的参数是剪裁空间下的位置

//frag
float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos));
  • LinearEyeDepth

    可以用在 物体a 需要与场景的 深度值 作比较, 进而判断出是否相交. 此时需要保证 物体a 不会渲染都深度图中, 不然不能做比较. 可以参考 [深度图 不等于 深度缓存](#深度图 不等于 深度缓存)

    比较的两个值需要转换到 同一空间 下 (一般就是是 视空间) 才能进行比较, 具体代码可以参考 相交判断

  • Linear01Depth

    可以用在 控制的值 (材质球暴露出来设置) 与 深度值 作比较, 所以要把 深度值 约束在 [0, 1] 区间内就得用到这个函数. 控制的值 就可以在 [0,1] 区间与 深度比较了.

    例如扫描线效果

    float4 frag_depth(v2f_img i) : SV_Target {
        float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
        float lnr01Depth = Linear01Depth(depth);
        fixed4 screenTexture = tex2D(_MainTex, i.uv);
    
        float near = smoothstep(_ScanValue, lnr01Depth, _ScanValue - _ScanLineWidth); // _ScanValue 就是 控制值, 在 [0, 1] 区间
        float far = smoothstep(_ScanValue, lnr01Depth, _ScanValue + _ScanLineWidth);
        fixed4 emissionClr = _ScanLineColor * (near + far);
    
        return screenTexture + emissionClr;
    }
    

    后处理直接绘制 深度图

    float4 frag_depth(v2f_img i) : SV_Target {
        float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
        float lnr01Depth = Linear01Depth(depth);
    
        return fixed4(lnr01Depth, lnr01Depth, lnr01Depth, 1);
    }
    


透明 ( transparent ) 物体写入深度的姿势

参考: Unity3D - Shader - 开启深度写入的半透明效果 - https://blog.csdn.net/biezhihua/article/details/78690574

正常来说 透明物体 是不会写入深度的, 但是可以通过增加多一个 pass 只用来写入深度, 但不输出颜色.

注意: 即使通过增加 深度写入pass 的方式, 也不能将该物体写入到 深度图 中, 因为pass只在正常渲染下起作用.

Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}

// 仅仅是把模型的深度信息写入深度缓冲中
// 从而剔除模型中被自身遮挡的片元
Pass {
    // 开启深度写入
    ZWrite On
    // 用于设置颜色通道的写掩码(Wirte Mask)
    // ColorMask RGB|A|0|(R/G/B/A组合)
    // 当为0时意味着该Pass不写入任何颜色通道,也就不会输出任何颜色
    ColorMask 0
}

实测一下, 将透明物体的渲染设为 1000, 让它比 opaque 更快渲染

  • 有深度写入pass的结果. 可以正常遮挡后面渲染的 胶囊体 , 同时和天空盒混合了

  • 没有深度写入pass的结果, 不能遮挡 胶囊体

将透明物体的渲染设为 3000, 让它比 opaque 更后渲染, 就正常可以正常混合到 胶囊体, 应为颜色缓存区已经有了胶囊体的颜色了.

踩坑
  • 如果代码没错,而看到的是全黑的,那么应该就是摄像机的Far Clip Plane设得太大。

深度值 推到 世界坐标位置

  • Unity3D 片元NDC空间z值(ZBuffer)转View空间z值,公式推导 - https://blog.csdn.net/u012149999/article/details/78678901

  • 全面认识Depth - 这里有关于Depth的一切 - https://zhuanlan.zhihu.com/p/25095708

  • shader 推导流程, 可以参照 运动模糊

    //使用宏和纹理坐标对深度纹理进行采样,得到深度值
    float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
    
    //构建当前像素的NDC坐标,xy坐标由像素的纹理坐标映射而来,z坐标由深度值d映射而来
    float4 H = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, d * 2 - 1, 1);
    //使用 当前帧的视角 * 投影矩阵 的逆矩阵 对H进行变换
    float4 D = mul(_CurrentViewProjectionInverseMartix, H);
    //把结果除以它的w分量,得到该像素世界空间下的坐标
    float4 worldPos = D / D.w;
    

为什么要除以 分量w ? 参考说明 剪裁坐标

因为经过了 投影矩阵, 分量w 就不在为1, 所以要求得具体的世界坐标, 就需要 除以 分量w 的影响


深度偏移

Offset 修改深度偏移值

	Pass
		{
			Name "FORWARD" 
			Tags { "LightMode" = "ForwardBase" }

			ZWrite On
			Offset 3000,0
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yangxuan0261/article/details/90182143

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法