一個(gè)Bug
今天QA報(bào)了一個(gè)渲染相關(guān)的bug:一個(gè)用了 扭曲 效果的翅膀特效在場(chǎng)景相機(jī)下顯示正常辕坝,但是在UI相機(jī)上卻有問題,截圖如下:
扭曲背景 上下顛倒 了崭别。
Bug的修正
這里用的 扭曲shader 是我們的美術(shù)同學(xué)從他們前項(xiàng)目搬過來的垦细,代碼很簡(jiǎn)單:
- 用 GrabPass 抓取當(dāng)前屏幕做為扭曲背景浅碾。
- 添加 UV擾動(dòng) 后再采樣屏幕背景,即可達(dá)到扭曲效果从祝。
問題是襟己,這里采樣 GrabTexture 的時(shí)候用的是 screenUV 而非 grabUV,代碼如下:
頂點(diǎn)著色器:
o.screenPos = ComputeScreenPos (o.pos);
像素著色器:
float2 sceneUVs = (i.screenPos.xy / i.screenPos.w) + (_Value * diffuseTex.a * float2(diffuseTex.r, diffuseTex.g) * i.vertexColor.a);
half4 sceneColor = tex2D(_GrabTexture, sceneUVs);
修正這個(gè)問題很簡(jiǎn)單牍陌,把 ComputeScreenPos 換成 ComputeGrabScreenPos 即可擎浴,修正后的代碼如下:
頂點(diǎn)著色器:
o.screenPos = ComputeGrabScreenPos (o.pos);
調(diào)整完之后就正常了,如下圖:
關(guān)于ComputeScreenPos和ComputeGrabScreenPos的差別
修正容易毒涧,但是搞清楚 ComputeScreenPos 和 ComputeGrabScreenPos 的差別卻要費(fèi)一些功夫贮预。
我們看一下相關(guān)代碼:
inline float4 ComputeNonStereoScreenPos(float4 pos) {
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
o.zw = pos.zw;
return o;
}
inline float4 ComputeScreenPos(float4 pos) {
float4 o = ComputeNonStereoScreenPos(pos);
#if defined(UNITY_SINGLE_PASS_STEREO)
o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
return o;
}
inline float4 ComputeGrabScreenPos (float4 pos) {
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*scale) + o.w;
#ifdef UNITY_SINGLE_PASS_STEREO
o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
o.zw = pos.zw;
return o;
}
通過分析可以發(fā)現(xiàn),這兩個(gè)函數(shù)的主要差別就是 UNITY_UV_STARTS_AT_TOP 和 _ProjectionParams.x 的差別链嘀。
我們知道 RenderTexture 的紋理坐標(biāo)在 Direct3D-like 平臺(tái)和 OpenGL-like 平臺(tái)存在差異:
- Direct3D-like平臺(tái)萌狂,UNITY_UV_STARTS_AT_TOP = 1,紋理坐標(biāo)0在頂部怀泊,并往下增長(zhǎng)茫藏。
- OpenGL-like平臺(tái),UNITY_UV_STARTS_AT_TOP = 0霹琼,紋理坐標(biāo)0在底部务傲,并往上增長(zhǎng)凉当。
當(dāng)渲染到紋理的時(shí),Unity遵從 OpenGL-like 平臺(tái)的標(biāo)準(zhǔn)售葡。
當(dāng)工作在 Direct3D-like 平臺(tái)時(shí)看杭,為了兼容這個(gè)平臺(tái)差異,Unity會(huì) 翻轉(zhuǎn)投影矩陣 從而翻轉(zhuǎn) RenderTexture挟伙,這樣既遵從了 OpenGL-like 平臺(tái)的約定楼雹,又可以獲取正確的采樣結(jié)果。
_ProjectionParams.x 標(biāo)識(shí)了投影矩陣是否經(jīng)過翻轉(zhuǎn)尖阔。
- _ProjectionParams.x = 1表示沒有翻轉(zhuǎn)贮缅。
- _ProjectionParams.x = -1表示翻轉(zhuǎn)。
那么介却,是不是 Direct3D-like 平臺(tái)下 RenderTexture 一定會(huì)進(jìn)行翻轉(zhuǎn)操作呢谴供?如果沒有翻轉(zhuǎn),而Unity又采用了 OpenGL-like 平臺(tái)的約定齿坷,這種情況要怎么處理呢桂肌?
事實(shí)上,Unity在一些情況下確實(shí)不會(huì)翻轉(zhuǎn) RenderTexture永淌,它的幫助文檔 Platform-specific rendering differences 這一章節(jié)列舉了 Direct3D-like 平臺(tái)下不翻轉(zhuǎn) RenderTexture 的幾種情況:
- Image Effects + 抗鋸齒
- GrabPass
對(duì)于 GrabPass崎场,Unity文檔做了特別說明:在 Direct3D-like 平臺(tái)下,GrabPass 不會(huì)進(jìn)行 RenderTexture 的翻轉(zhuǎn)操作仰禀,因此我們需要在shader中手工翻轉(zhuǎn)uv以獲取正確的采樣結(jié)果照雁。
ComputeGrabScreenPos 這里只需要判斷 UNITY_UV_STARTS_AT_TOP 的取值:
- 如果是 Direct3D-like 平臺(tái)(UNITY_UV_STARTS_AT_TOP = 1),我們就需要手工翻轉(zhuǎn)uv答恶。
- 如果是 OpenGL-like 平臺(tái)(UNITY_UV_STARTS_AT_TOP = 0)饺蚊,則無需翻轉(zhuǎn)uv。
inline float4 ComputeGrabScreenPos (float4 pos) {
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*scale) + o.w;
#ifdef UNITY_SINGLE_PASS_STEREO
o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
o.zw = pos.zw;
return o;
}
Direct3D-like 平臺(tái)下悬嗓,如果我們用 _ProjectionParams.x 來判斷是否需要手工翻轉(zhuǎn)uv就錯(cuò)了污呼,因?yàn)?RenderTexture 并未發(fā)生翻轉(zhuǎn),此時(shí) _ProjectionParams.x = 1包竹。
關(guān)于投影矩陣的翻轉(zhuǎn)
bug是修正了燕酷,也知道了原因:對(duì)于GrabPass,我們應(yīng)該用 UNITY_UV_STARTS_AT_TOP 而非 _ProjectionParams.x 去判斷是否要手工進(jìn)行uv翻轉(zhuǎn)周瞎。
但是還有一個(gè)疑問沒有解開:前文說了苗缩,這個(gè) 扭曲 特效在場(chǎng)景相機(jī)下工作正常,在UI相機(jī)下才有問題声诸,這又是為什么呢酱讶?
說到相機(jī),我們游戲內(nèi)一共3個(gè)相機(jī)彼乌,渲染順序如下:
場(chǎng)景相機(jī)(開后處理) --> UI相機(jī)1(關(guān)后處理) --> UI相機(jī)2(關(guān)后處理)
出現(xiàn)問題的相機(jī)是 UI相機(jī)2泻肯,此時(shí)我們并沒有開 抗鋸齒渊迁,并且 場(chǎng)景相機(jī) 處于 關(guān)閉 狀態(tài)。
如果我們打開 場(chǎng)景相機(jī)灶挟,或者把 UI相機(jī)1 的后處理打開琉朽,又或者把 UI相機(jī)2 的后處理打開,這些情況下這個(gè)bug都不會(huì)出現(xiàn)稚铣。
似乎 多相機(jī) 以及 Image Effect的開關(guān) 也會(huì)影響 _ProjectionParams.x 的設(shè)值箱叁。
可惜的是,Unity文檔對(duì) 投影矩陣的翻轉(zhuǎn) 語(yǔ)焉不詳榛泛,只是告訴你 _ProjectionParams.x = -1 即代表了翻轉(zhuǎn):
x is 1.0 (or –1.0 if currently rendering with a flipped projection matrix)
沒有源碼的情況下蝌蹂,何時(shí)翻轉(zhuǎn)投影矩陣就比較難說清楚了噩斟。
不過我們只需要記得:
- _ProjectionParams.x = -1 代表翻轉(zhuǎn)了投影矩陣曹锨,在計(jì)算 屏幕坐標(biāo) 的時(shí)候,如果發(fā)生了 投影矩陣翻轉(zhuǎn)剃允,那么我們也需要在shader中手工翻轉(zhuǎn)uv沛简,這樣才能獲得正確的 屏幕坐標(biāo)。
- Unity的 ComputeScreenPos 幫我們處理好了這個(gè)過程斥废。
早前在寫 Fantastic SSR Water 這個(gè)插件的時(shí)候椒楣,我也遇到過類似的問題。
Fantastic SSR Water 是一款關(guān)于水的插件牡肉,用 屏幕空間反射 實(shí)現(xiàn)水的反射捧灰。
- 因?yàn)樾枰谄聊豢臻g計(jì)算 光線步進(jìn),因此我需要計(jì)算屏幕坐標(biāo) screenUV统锤。
- 因?yàn)橛昧?GrabPass 去抓取屏幕顏色以計(jì)算反射顏色毛俏,因此我還需要計(jì)算 grabUV。
當(dāng)時(shí)饲窿,我錯(cuò)誤的把 screenUV 和 grabUV 等同了煌寇,然后發(fā)現(xiàn)只有在特定的設(shè)置選項(xiàng)下渲染才正確,設(shè)置選項(xiàng)包括:
- 平臺(tái)的選擇
- 前向渲染/延遲渲染的選擇
- 抗鋸齒開關(guān)的選擇
后面逾雄,我用 ComputeScreenPos 去計(jì)算 screenUV阀溶,用 ComputeGrabScreenPos 去計(jì)算 grabUV,問題就解決了鸦泳,在各種設(shè)置組合下渲染都正確银锻。
最后,附 Fantastic SSR Water 截圖一張:
個(gè)人主頁(yè)
本文的個(gè)人主頁(yè)鏈接:https://baddogzz.github.io/2020/01/02/GrabUV-Bug/
好了做鹰,拜拜击纬。