贴花效果测试

贴花效果,就和名字的直接意思类似,把一张图贴到另一个物体上显示,经常被用于表现一些重复出现的图案,比如弹孔,涂鸦,污渍等。效果图:

常规贴花实现

Unity官方提供了一个工程,这个工程主要是用来说明CommandBuffer是怎么使用的,其中有贴花的一些展示,主要是用CommandBuffer在Deferred渲染路径下实现贴花效果。使用CommandBuffer是因为需要把BuiltinRenderTextureType.GBuffer2中存储的法线信息传给Shader,而这次测试主要为了验证原理,不使用法线信息,所以可以不用CommandBuffer(即使使用法线信息也可以在Shader中通过_CameraDepthNormalsTexture结合DecodeDepthNormal方法来获取到法线信息)。原工程通过 cam.AddCommandBuffer (CameraEvent.BeforeLighting, buf); 这句实现把CommandBuffer插入到延迟渲染的光照计算Pass前面,也可以去掉不用。所以原工程的C#代码基本可以不使用,在Forward渲染路径下,完全在Shader中实现贴花效果。之前看文档说如果Shader中使用深度图的话需要在C#代码中设置相机的depthTextureMode,即 mainCam.depthTextureMode = DepthTextureMode.Depth;,但是我试了下不写这行代码在Shader中也可以正常使用深度图,有知道原因的同学可以告诉我下哈。

贴花效果的原理是建立一个立方体物体作为贴花物体(也有使用球体的),在贴花物体和被贴花的物体相交的XZ平面计算UV,显示贴花图案。具体的逻辑如下:

1. 在顶点着色器中记录顶点在视空间的坐标(即相机到该点的方向向量,因为相机在视空间的原点)

2. 在片元着色器中根据远裁切平面的距离(_ProjectionParams.z)和深度值重建片元在视空间的坐标

3. 再根据unity_CameraToWorld和unity_WorldToObject矩阵计算出片元在模型空间的坐标

4. 把模型空间坐标的xz分量映射到 [0,1] 区间,作为UV去读取贴花图案

代码截图:

代码中需要注意的一些地方:

1.计算视空间方向时要乘以 float3(-1,-1,1),这一点没有想明白,试了其他值效果不对

2. i.ray = i.ray * (_ProjectionParams.z / i.ray.z); 是为了求在当前ray方向上延伸到摄像机远平面位置的向量,这张图能清晰地说明问题,图片出自 这篇文章


3. o.screenUV = ComputeScreenPos (o.pos); 计算结果的xy分量到片元着色器中需要除以w分量才能使用,除以w后xy分量在[0,1]区间,用来作为UV去读取_CameraDepthTexture。为什么在frag除以w可以参考 文章

4. clip (float3(0.5,0.5,0.5) - abs(opos.xyz)) 的意思是剔除在物体外的片元,opos为转换到模型空间下的坐标,该模型是一个立方体,其模型空间坐标范围是 [-0.5, 0.5]。

5. depth = Linear01Depth (depth); 是为了得到线性的深度值,为了 float4 vpos = float4(i.ray * depth,1) 计算时能够得到正确的向量。SAMPLE_DEPTH_TEXTURE 方法取得的深度值是非线性的。参考文章

6. float2 texUV = opos.xz + 0.5; 把坐标映射到 [0, 1] 区间,这里使用xz坐标,因为贴花要显示在xz平面上。

效果图:

考虑y方向偏移的贴花

在摆弄贴花物体时发现在拐角和边缘处显示效果不对,出现图片边缘被clamp的效果,如图:

原因在于在计算纹理坐标时 (float2 texUV = opos.xz + 0.5;)没有考虑y方向的变化,导致在边缘处的片元xz坐标都一样,和clamp对纹理坐标的处理一样。这种情况可以通过把贴花旋转一定的角度来消除,像这样(x轴旋转了-50度):



也可以通过使用法线图来确定模型空间坐标在y方向上的偏差,使这部分偏差参与到UV的计算中,主要步骤有:

1. 求出视空间的深度值和法线(通过_CameraDepthNormalsTexture属性和DecodeDepthNormal方法)

2. 把法线转换到模型空间

3. 把模型空间法线和模型空间Up方向(float3(0,1,0))点乘,求出垂直方向上的偏移程度,

4. 把偏移程度加入到UV的计算中

Shader代码:

C#中需要加上 mainCam.depthTextureMode = DepthTextureMode.DepthNormals;, 这样可以在Shader中使用 _CameraDepthNormalsTexture 属性,结合 DecodeDepthNormal 方法可以获取到视空间中的深度值和法线。官方文档

效果图:

效果图中Scene窗口部分的贴花看起来很扭曲,非常不对,Game窗口的部分变化不大,但是也能看到有一些锯齿存在,关于这个问题我查了一些文章,大概猜测是深度值精度问题导致,_CameraDepthNormalsTexture 中的RG通道用来存储法线信息(16位),BA通道用来存储深度值(16位),而 _CameraDepthTexture 中32位都用来存储深度,所以通过 _CameraDepthTexture 读取的深度值比通过 _CameraDepthNormalsTexture 读取的精确度更高。

但是y方向偏移的方法也有一些问题,比如要通过贴花物体的y坐标来控制垂直部分纹理显示的多少,还有在计算decalUV时要考虑到X和Z两个方向坐标对y偏移的计算,上述例子的代码中为了简便只给Z方向上考虑了Y的偏移,可以在Shader中设置一个Enum,在场景同学摆放贴花时根据摆放位置来控制具体在哪个方向上考虑Y偏移,那么这样一来其实也可以直接用第一种常规方式来实现,反正都需要人工干预,而且第一种方式还少进行了一次矩阵乘法和点乘。

HDRP中的贴花效果

原本在默认管线中工作正常的Shader在导入到使用了HDRP的工程中后效果变的很错乱,大概是这样:

直观感觉应该是深度值的原因导致重建世界坐标时出现了错误,在用Frame Debug查看具体的渲染过程后发现深度图是这样的:

在HDRP中深度图的存储有点类似于一个mipmap的图集,里面存储了不同分辨率的多张深度图,所以使用 SAMPLE_DEPTH_TEXTURE 方法去获取深度值时得到的数据是错误的。那么现在面对问题就变成了 “如何在HDRP中正确的获得深度值”。这个 提问 里也遇到了同样的问题,回答中的解决方案是使用 ShaderVariables.hlsl 文件中的 SampleCameraDepth 方法,于是我就按照这个方法去做了,在Shader中引用了这个hlsl文件:

在frag中增加了 float depth = SampleCameraDepth(uv);,然后出现报错:

把pass中的CGPROGRAM 和 ENDCG 替换成 HLSLPROGRAM 和 ENDHLSL,报错变成了:

按照报错提示又修改了UnityShaderUtilities.cginc文件中的宏,这次又报错:

连 fixed4 类型都要未识别了??到此我觉着应该是走错了方向,如何在HLSLPROGRAM和ENDHLSL中正确的写代码可能是另一个话题了。而在Shader中如何使用.hlsl文件中的方法,或者更具体的在HDRP中怎么获取深度值,还需要再继续研究下,目前搜了一大堆文章和网页并没有确切的答案。既然使用自己编写的Shader实现贴花这条路卡住了,那么现在应该换一种方式,即使用Unity HDRP中自带的贴花组件,Decal Projector Component。

HDRP自带的贴花组件

HDRP中新增的贴花组件让贴花效果的实现变的非常方便,新建一个空物体,然后把 Decal Projector Component 组件添加到物体上,指定上贴花图案就可以显示出贴花,还可以使用法线图使贴花产生凹凸感,使用遮罩图用来控制法线生效的区域。

DBufferRender

在Frame Debug中查看渲染过程,发现Decal的渲染在 DBufferRender 中进行,最终显示到屏幕上的每个Decal都会对应在 DBufferRender 对应一个 Draw Mesh 事件,其使用的Shader是 HDRenderPipeline/Decal,使用的pass是 DBufferProjector_S。

该pass也用到了_CameraDepthTexture,为了搞清楚这个Shader是怎么成功获取深度值的,我查看了 HDRenderPipeline/Decal 的代码,在跳转了一系列文件后终于找到了对应的vert 和 frag方法,都在 ShaderPassDBuffer.hlsl 文件中。

可以看到虽然使用的方法和一些宏变了,但是大致思路还是一样的,也是根据深度值还原世界坐标,再到模型空间坐标(这个方法里叫Decal Space,用 positionDS变量表示)。

float depth = LOAD_TEXTURE2D(_CameraDepthTexture, input.positionSS.xy).x; 这句话是用来获取深度值的,终于看到了在HLSL中获取深度的方法了,但是很遗憾我在自定义的Shader中使用这行代码时又发生了一系列目前还不能解决的报错,我的感觉是使用HLSL文件和常见的cginc文件的差异还是很大的,并不像想象的那么无缝衔接。

DBuffer Normal

在 DBufferRender 事件后面是 DBuffer Normal,其中只有一个 Draw Procedural 子事件,使用的Shader是 Hidden/HDRenderPipeline/Material/Decal/DecalNormalBuffer,同样的,查看Shader源码。

这个Shader文件还是比较友好的,少量的几个#include文件,vert和frag方法也都在当前文件里,而不是跳转到其他的包含文件中,vert方法中不是常规的 UnityObjectToClipPos 操作,而是获取 全屏三角形的顶点位置和纹理坐标,感觉像是一个屏幕后处理类似的操作,但是全屏的话不应该是两个三角形4个顶点吗,在Frame Debug中查看是3个顶点,这就有点搞不懂了。

frag中主要操作是:

1. 从GBuffer中获取法线信息

2. 把GBuffer的法线和贴花组件中指定的法线图的法线叠加一下,这样贴花的法线就可以影响物体表面的表现了

3. 最后把修改后的法线再Encode到GBuffer中


总结:

1. 在使用默认渲染管线的工程中,可以使用自定义的Shader来实现贴花。

2. 在使用HDRP的工程中使用HDRP自带的 Decal Projector Component。以后如果研究明白了正确获取深度值的方法后可以尝试使用自定义Shader。

3. 目前关于DBuffer相关的资料很少,基本没有查到什么可用的信息,建议可以通过查看SRP的源码,包括CoreRP,LWRP,HDRP中的C#代码,Shader文件以及HLSL文件,源码地址

4. 鉴于贴花在物体拐角和边缘处表现的不是很好,建议的使用方式是在离线时布置好贴花的位置旋转和缩放,这样可以根据不同物体的旋转缩放等条件来调整贴花物体,来达到良好的表现。尽量避免在运行时动态生成,或者只在有限的场景条件里动态生成,比如平地,墙面之类,以减少不确定性以及避免出现预期以外的奇怪效果。


参考链接:

https://blog.csdn.net/NotMz/article/details/78712346

https://forum.unity.com/threads/camera-depth-texture-sampling-with-2018-3-and-hdrp-4-x-mip-map-issue.594160/

https://forum.unity.com/threads/decodedepthnormal-linear01depth-lineareyedepth-explanations.608452/

https://forum.unity.com/threads/hdrp-how-to-render-anything-custom.592093/

https://docs.unity3d.com/Manual/SL-CameraDepthTexture.html

https://docs.unity3d.com/Manual/SL-DepthTextures.html

https://docs.unity3d.com/Manual/SL-DepthTextures.html

https://github.com/Unity-Technologies/ScriptableRenderPipeline

https://forum.unity.com/threads/accessing-depth-rendertexture-in-hdrp-and-pass-it-to-compute-shaders.539003/

https://docs.unity3d.com/Manual/SL-ShaderPrograms.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,100评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,862评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,993评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,309评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,303评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,421评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,830评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,501评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,689评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,506评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,564评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,286评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,826评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,875评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,114评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,705评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,269评论 2 341

推荐阅读更多精彩内容

  • 转载自VR设计云课堂[https://www.jianshu.com/u/c7ffdc4b379e]Unity S...
    水月凡阅读 1,004评论 0 0
  • <转>我也忘了转自哪里,抱歉,感谢原作者 什么是Shader Shader(着色器)是一段能够针对3D对象进行操作...
    星易乾川阅读 5,572评论 1 16
  • 转载注明出处:点击打开链接 Shader(着色器)是一段能够针对3D对象进行操作、并被GPU所执行的程序。Shad...
    游戏开发小Y阅读 3,317评论 0 4
  • 我们现实网络无处不在,我们被庞大的虚拟网络包围,但我们却对它是怎样把我们的信息传递并实现通信的,我们并没有了解过,...
    JOKER_HAN阅读 361评论 0 0
  • 当前在“大众创业,万众创新”双经济引擎的推动下,中小企业如雨后春笋般出现、成长。 商业运作的基础是资金,企业需要N...
    众乐乐娱乐法阅读 213评论 0 1