貼花效果測試

貼花效果,就和名字的直接意思類似,把一張圖貼到另一個(gè)物體上顯示,經(jīng)常被用于表現(xiàn)一些重復(fù)出現(xiàn)的圖案,比如彈孔,涂鴉,污漬等。效果圖:

常規(guī)貼花實(shí)現(xiàn)

Unity官方提供了一個(gè)工程,這個(gè)工程主要是用來說明CommandBuffer是怎么使用的,其中有貼花的一些展示,主要是用CommandBuffer在Deferred渲染路徑下實(shí)現(xiàn)貼花效果。使用CommandBuffer是因?yàn)樾枰袯uiltinRenderTextureType.GBuffer2中存儲的法線信息傳給Shader,而這次測試主要為了驗(yàn)證原理,不使用法線信息,所以可以不用CommandBuffer(即使使用法線信息也可以在Shader中通過_CameraDepthNormalsTexture結(jié)合DecodeDepthNormal方法來獲取到法線信息)。原工程通過 cam.AddCommandBuffer (CameraEvent.BeforeLighting, buf); 這句實(shí)現(xiàn)把CommandBuffer插入到延遲渲染的光照計(jì)算Pass前面,也可以去掉不用。所以原工程的C#代碼基本可以不使用,在Forward渲染路徑下,完全在Shader中實(shí)現(xiàn)貼花效果。之前看文檔說如果Shader中使用深度圖的話需要在C#代碼中設(shè)置相機(jī)的depthTextureMode,即 mainCam.depthTextureMode = DepthTextureMode.Depth;,但是我試了下不寫這行代碼在Shader中也可以正常使用深度圖,有知道原因的同學(xué)可以告訴我下哈。

貼花效果的原理是建立一個(gè)立方體物體作為貼花物體(也有使用球體的),在貼花物體和被貼花的物體相交的XZ平面計(jì)算UV,顯示貼花圖案。具體的邏輯如下:

1. 在頂點(diǎn)著色器中記錄頂點(diǎn)在視空間的坐標(biāo)(即相機(jī)到該點(diǎn)的方向向量,因?yàn)橄鄼C(jī)在視空間的原點(diǎn))

2. 在片元著色器中根據(jù)遠(yuǎn)裁切平面的距離(_ProjectionParams.z)和深度值重建片元在視空間的坐標(biāo)

3. 再根據(jù)unity_CameraToWorld和unity_WorldToObject矩陣計(jì)算出片元在模型空間的坐標(biāo)

4. 把模型空間坐標(biāo)的xz分量映射到 [0,1] 區(qū)間,作為UV去讀取貼花圖案

代碼截圖:

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

1.計(jì)算視空間方向時(shí)要乘以 float3(-1,-1,1),這一點(diǎn)沒有想明白,試了其他值效果不對

2. i.ray = i.ray * (_ProjectionParams.z / i.ray.z); 是為了求在當(dāng)前ray方向上延伸到攝像機(jī)遠(yuǎn)平面位置的向量,這張圖能清晰地說明問題,圖片出自 這篇文章


3. o.screenUV = ComputeScreenPos (o.pos);?計(jì)算結(jié)果的xy分量到片元著色器中需要除以w分量才能使用,除以w后xy分量在[0,1]區(qū)間,用來作為UV去讀取_CameraDepthTexture。為什么在frag除以w可以參考?文章

4. clip (float3(0.5,0.5,0.5) - abs(opos.xyz))?的意思是剔除在物體外的片元,opos為轉(zhuǎn)換到模型空間下的坐標(biāo),該模型是一個(gè)立方體,其模型空間坐標(biāo)范圍是 [-0.5, 0.5]。

5. depth = Linear01Depth (depth);?是為了得到線性的深度值,為了?float4 vpos = float4(i.ray * depth,1)?計(jì)算時(shí)能夠得到正確的向量。SAMPLE_DEPTH_TEXTURE?方法取得的深度值是非線性的。參考文章。

6. float2 texUV = opos.xz + 0.5;?把坐標(biāo)映射到 [0, 1] 區(qū)間,這里使用xz坐標(biāo),因?yàn)橘N花要顯示在xz平面上。

效果圖:

考慮y方向偏移的貼花

在擺弄貼花物體時(shí)發(fā)現(xiàn)在拐角和邊緣處顯示效果不對,出現(xiàn)圖片邊緣被clamp的效果,如圖:

原因在于在計(jì)算紋理坐標(biāo)時(shí) (float2 texUV = opos.xz + 0.5;)沒有考慮y方向的變化,導(dǎo)致在邊緣處的片元xz坐標(biāo)都一樣,和clamp對紋理坐標(biāo)的處理一樣。這種情況可以通過把貼花旋轉(zhuǎn)一定的角度來消除,像這樣(x軸旋轉(zhuǎn)了-50度):



也可以通過使用法線圖來確定模型空間坐標(biāo)在y方向上的偏差,使這部分偏差參與到UV的計(jì)算中,主要步驟有:

1. 求出視空間的深度值和法線(通過_CameraDepthNormalsTexture屬性和DecodeDepthNormal方法)

2. 把法線轉(zhuǎn)換到模型空間

3. 把模型空間法線和模型空間Up方向(float3(0,1,0))點(diǎn)乘,求出垂直方向上的偏移程度,

4. 把偏移程度加入到UV的計(jì)算中

Shader代碼:

C#中需要加上 mainCam.depthTextureMode = DepthTextureMode.DepthNormals;, 這樣可以在Shader中使用 _CameraDepthNormalsTexture 屬性,結(jié)合 DecodeDepthNormal 方法可以獲取到視空間中的深度值和法線。官方文檔。

效果圖:

效果圖中Scene窗口部分的貼花看起來很扭曲,非常不對,Game窗口的部分變化不大,但是也能看到有一些鋸齒存在,關(guān)于這個(gè)問題我查了一些文章,大概猜測是深度值精度問題導(dǎo)致,_CameraDepthNormalsTexture 中的RG通道用來存儲法線信息(16位),BA通道用來存儲深度值(16位),而 _CameraDepthTexture 中32位都用來存儲深度,所以通過 _CameraDepthTexture 讀取的深度值比通過 _CameraDepthNormalsTexture 讀取的精確度更高。

但是y方向偏移的方法也有一些問題,比如要通過貼花物體的y坐標(biāo)來控制垂直部分紋理顯示的多少,還有在計(jì)算decalUV時(shí)要考慮到X和Z兩個(gè)方向坐標(biāo)對y偏移的計(jì)算,上述例子的代碼中為了簡便只給Z方向上考慮了Y的偏移,可以在Shader中設(shè)置一個(gè)Enum,在場景同學(xué)擺放貼花時(shí)根據(jù)擺放位置來控制具體在哪個(gè)方向上考慮Y偏移,那么這樣一來其實(shí)也可以直接用第一種常規(guī)方式來實(shí)現(xiàn),反正都需要人工干預(yù),而且第一種方式還少進(jìn)行了一次矩陣乘法和點(diǎn)乘。

HDRP中的貼花效果

原本在默認(rèn)管線中工作正常的Shader在導(dǎo)入到使用了HDRP的工程中后效果變的很錯(cuò)亂,大概是這樣:

直觀感覺應(yīng)該是深度值的原因?qū)е轮亟ㄊ澜缱鴺?biāo)時(shí)出現(xiàn)了錯(cuò)誤,在用Frame Debug查看具體的渲染過程后發(fā)現(xiàn)深度圖是這樣的:

在HDRP中深度圖的存儲有點(diǎn)類似于一個(gè)mipmap的圖集,里面存儲了不同分辨率的多張深度圖,所以使用 SAMPLE_DEPTH_TEXTURE 方法去獲取深度值時(shí)得到的數(shù)據(jù)是錯(cuò)誤的。那么現(xiàn)在面對問題就變成了 “如何在HDRP中正確的獲得深度值”。這個(gè) 提問 里也遇到了同樣的問題,回答中的解決方案是使用 ShaderVariables.hlsl 文件中的 SampleCameraDepth 方法,于是我就按照這個(gè)方法去做了,在Shader中引用了這個(gè)hlsl文件:

在frag中增加了?float depth = SampleCameraDepth(uv);,然后出現(xiàn)報(bào)錯(cuò):

把pass中的CGPROGRAM?和?ENDCG?替換成?HLSLPROGRAM?和?ENDHLSL,報(bào)錯(cuò)變成了:

按照報(bào)錯(cuò)提示又修改了UnityShaderUtilities.cginc文件中的宏,這次又報(bào)錯(cuò):

連 fixed4 類型都要未識別了??到此我覺著應(yīng)該是走錯(cuò)了方向,如何在HLSLPROGRAM和ENDHLSL中正確的寫代碼可能是另一個(gè)話題了。而在Shader中如何使用.hlsl文件中的方法,或者更具體的在HDRP中怎么獲取深度值,還需要再繼續(xù)研究下,目前搜了一大堆文章和網(wǎng)頁并沒有確切的答案。既然使用自己編寫的Shader實(shí)現(xiàn)貼花這條路卡住了,那么現(xiàn)在應(yīng)該換一種方式,即使用Unity HDRP中自帶的貼花組件,Decal Projector Component。

HDRP自帶的貼花組件

HDRP中新增的貼花組件讓貼花效果的實(shí)現(xiàn)變的非常方便,新建一個(gè)空物體,然后把?Decal Projector Component?組件添加到物體上,指定上貼花圖案就可以顯示出貼花,還可以使用法線圖使貼花產(chǎn)生凹凸感,使用遮罩圖用來控制法線生效的區(qū)域。

DBufferRender

在Frame Debug中查看渲染過程,發(fā)現(xiàn)Decal的渲染在 DBufferRender 中進(jìn)行,最終顯示到屏幕上的每個(gè)Decal都會對應(yīng)在 DBufferRender 對應(yīng)一個(gè) Draw Mesh 事件,其使用的Shader是 HDRenderPipeline/Decal,使用的pass是 DBufferProjector_S。

該pass也用到了_CameraDepthTexture,為了搞清楚這個(gè)Shader是怎么成功獲取深度值的,我查看了 HDRenderPipeline/Decal 的代碼,在跳轉(zhuǎn)了一系列文件后終于找到了對應(yīng)的vert 和 frag方法,都在 ShaderPassDBuffer.hlsl 文件中。

可以看到雖然使用的方法和一些宏變了,但是大致思路還是一樣的,也是根據(jù)深度值還原世界坐標(biāo),再到模型空間坐標(biāo)(這個(gè)方法里叫Decal Space,用 positionDS變量表示)。

float depth = LOAD_TEXTURE2D(_CameraDepthTexture, input.positionSS.xy).x; 這句話是用來獲取深度值的,終于看到了在HLSL中獲取深度的方法了,但是很遺憾我在自定義的Shader中使用這行代碼時(shí)又發(fā)生了一系列目前還不能解決的報(bào)錯(cuò),我的感覺是使用HLSL文件和常見的cginc文件的差異還是很大的,并不像想象的那么無縫銜接。

DBuffer Normal

在 DBufferRender 事件后面是 DBuffer Normal,其中只有一個(gè) Draw Procedural 子事件,使用的Shader是 Hidden/HDRenderPipeline/Material/Decal/DecalNormalBuffer,同樣的,查看Shader源碼。

這個(gè)Shader文件還是比較友好的,少量的幾個(gè)#include文件,vert和frag方法也都在當(dāng)前文件里,而不是跳轉(zhuǎn)到其他的包含文件中,vert方法中不是常規(guī)的 UnityObjectToClipPos 操作,而是獲取 全屏三角形的頂點(diǎn)位置和紋理坐標(biāo),感覺像是一個(gè)屏幕后處理類似的操作,但是全屏的話不應(yīng)該是兩個(gè)三角形4個(gè)頂點(diǎn)嗎,在Frame Debug中查看是3個(gè)頂點(diǎn),這就有點(diǎn)搞不懂了。

frag中主要操作是:

1. 從GBuffer中獲取法線信息

2. 把GBuffer的法線和貼花組件中指定的法線圖的法線疊加一下,這樣貼花的法線就可以影響物體表面的表現(xiàn)了

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


總結(jié):

1. 在使用默認(rèn)渲染管線的工程中,可以使用自定義的Shader來實(shí)現(xiàn)貼花。

2. 在使用HDRP的工程中使用HDRP自帶的 Decal Projector Component。以后如果研究明白了正確獲取深度值的方法后可以嘗試使用自定義Shader。

3. 目前關(guān)于DBuffer相關(guān)的資料很少,基本沒有查到什么可用的信息,建議可以通過查看SRP的源碼,包括CoreRP,LWRP,HDRP中的C#代碼,Shader文件以及HLSL文件,源碼地址。

4. 鑒于貼花在物體拐角和邊緣處表現(xiàn)的不是很好,建議的使用方式是在離線時(shí)布置好貼花的位置旋轉(zhuǎn)和縮放,這樣可以根據(jù)不同物體的旋轉(zhuǎn)縮放等條件來調(diào)整貼花物體,來達(dá)到良好的表現(xiàn)。盡量避免在運(yùn)行時(shí)動(dòng)態(tài)生成,或者只在有限的場景條件里動(dòng)態(tài)生成,比如平地,墻面之類,以減少不確定性以及避免出現(xiàn)預(yù)期以外的奇怪效果。


參考鏈接:

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載自VR設(shè)計(jì)云課堂[http://m.itdecent.cn/u/c7ffdc4b379e]Unity S...
    水月凡閱讀 1,186評論 0 0
  • <轉(zhuǎn)>我也忘了轉(zhuǎn)自哪里,抱歉,感謝原作者 什么是Shader Shader(著色器)是一段能夠針對3D對象進(jìn)行操作...
    星易乾川閱讀 5,867評論 1 16
  • 轉(zhuǎn)載注明出處:點(diǎn)擊打開鏈接 Shader(著色器)是一段能夠針對3D對象進(jìn)行操作、并被GPU所執(zhí)行的程序。Shad...
    游戲開發(fā)小Y閱讀 3,707評論 0 4
  • 我們現(xiàn)實(shí)網(wǎng)絡(luò)無處不在,我們被龐大的虛擬網(wǎng)絡(luò)包圍,但我們卻對它是怎樣把我們的信息傳遞并實(shí)現(xiàn)通信的,我們并沒有了解過,...
    JOKER_HAN閱讀 500評論 0 0
  • 當(dāng)前在“大眾創(chuàng)業(yè),萬眾創(chuàng)新”雙經(jīng)濟(jì)引擎的推動(dòng)下,中小企業(yè)如雨后春筍般出現(xiàn)、成長。 商業(yè)運(yùn)作的基礎(chǔ)是資金,企業(yè)需要N...
    眾樂樂娛樂法閱讀 282評論 0 1

友情鏈接更多精彩內(nèi)容