三言两语说shader(九)钻石
這次的目標(biāo)是繪制一顆閃閃發(fā)光的鉆石,追求效果是越接近真實(shí)越好。
先說說為此我這幾天干了些什么。
1.看了stalendp blog里那篇《鉆石效果》后頭的參考文獻(xiàn)
最有價(jià)值的就是ATI在2004年GDC上作的演講,題目就叫Drawing a Diamond。但是由于只有ppt,所以很難重現(xiàn)工程深入學(xué)習(xí)。思路大概是預(yù)備了一張折射CubeMap,一張帶模擬色散的反射CubeMap,最后再加上耀斑混合而成。
2.試驗(yàn)了一些簡單的鏡面反射
非實(shí)時(shí)的CubeMap反射最簡單沒什么好說的,需要注意的是要做到位置精確的鏡面反射還是很難的,一不小心就會(huì)發(fā)現(xiàn)當(dāng)物體離反射面很近時(shí),尺寸位置都會(huì)嚴(yán)重錯(cuò)誤。
像下圖這樣,球的鏡像明顯尺寸過大了。
實(shí)時(shí)獲取環(huán)境CubeMap的反射也測試了下,沒記錯(cuò)的話上圖CubeMap的FaceSize設(shè)的是512,在我的一加手機(jī)上基本就跑不到正常幀了,FaceSize設(shè)成256幀率還可以接受,但畫質(zhì)就沒法看了,總之實(shí)時(shí)反射是手機(jī)無法承受的。
這里我總結(jié)一點(diǎn)思想:
游戲畫面表現(xiàn)本身就帶有很多虛假的成分,因?yàn)槿凑鎸?shí)的來計(jì)算性能根本無法承受,最簡單的拿光照來說,不談多次反射的環(huán)境光,單純的一次平行光照實(shí)時(shí)計(jì)算都沒法滿足,都要預(yù)烘焙光照貼圖,陰影也一樣,真實(shí)的計(jì)算太費(fèi)性能,一般也弄個(gè)假的完事。
所以我們會(huì)發(fā)現(xiàn)游戲畫面和現(xiàn)實(shí)照片的差距總是一目了然。所以會(huì)出現(xiàn)很多專門掩飾的技巧。
如何抓住主要部分,在效果和性能間做出取舍,而又能盡量達(dá)到接近真實(shí)的效果,大概就是圖形工程師們大多數(shù)時(shí)候在思考的事情了。
這個(gè)鉆石效果的演示demo,明顯要按固定環(huán)境的思路來,不需要考慮實(shí)時(shí)問題,像前面提到的那種尺寸扭曲的問題,基本也是可以忽略的,因?yàn)殂@石形狀類似小球,環(huán)境的輕微扭曲是能被輕易掩飾過去的。
3.到AssetStore搜索了下相關(guān)資源
發(fā)現(xiàn)免費(fèi)的Gem Shaders就是Unity官方提供的,而且還為5.0專門更新過一次。我跑了下它的示例場景,感覺觀感不是很滿意。
主要是它提供的幾個(gè)鉆石模型形狀我也不喜歡,于是自己到網(wǎng)上找了個(gè)大概是傳說中“八星八箭”形狀的模型,然后還是用這個(gè)shader試了下效果,結(jié)果發(fā)現(xiàn)還是很屌的。
我原本的想法是盡量用真實(shí)的光線折射、反射計(jì)算得到最終效果的。但是就算暫不考慮代碼實(shí)現(xiàn),不考慮實(shí)現(xiàn)后的性能如何,首先單純就目前憑我有限的光學(xué)姿勢能不能建立起真實(shí)準(zhǔn)確的光學(xué)模型都很明顯是成問題的。
所以這次我們還是以學(xué)習(xí)官方的示例為主,不要整天想自己搞什么大新聞了。
代碼如下:
Shader "FX/Gem" {Properties {_Color ("Color", Color) = (1,1,1,1)_ReflectionStrength ("Reflection Strength", Range(0.0,2.0)) = 1.0_EnvironmentLight ("Environment Light", Range(0.0,2.0)) = 1.0_Emission ("Emission", Range(0.0,2.0)) = 0.0[NoScaleOffset] _RefractTex ("Refraction Texture", Cube) = "" {}}SubShader {Tags {"Queue" = "Transparent"}// First pass - here we render the backfaces of the diamonds. Since those diamonds are more-or-less// convex objects, this is effectively rendering the inside of them.Pass {Cull FrontZWrite OffCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f {float4 pos : SV_POSITION;float3 uv : TEXCOORD0;};v2f vert (float4 v : POSITION, float3 n : NORMAL){v2f o;o.pos = mul(UNITY_MATRIX_MVP, v);// TexGen CubeReflect:// reflect view direction along the normal, in view space.float3 viewDir = normalize(ObjSpaceViewDir(v));o.uv = -reflect(viewDir, n);o.uv = mul(_Object2World, float4(o.uv,0));return o;}fixed4 _Color;samplerCUBE _RefractTex;half _EnvironmentLight;half _Emission;half4 frag (v2f i) : SV_Target{half3 refraction = texCUBE(_RefractTex, i.uv).rgb * _Color.rgb;half4 reflection = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.uv);reflection.rgb = DecodeHDR (reflection, unity_SpecCube0_HDR);half3 multiplier = reflection.rgb * _EnvironmentLight + _Emission;return half4(refraction.rgb * multiplier.rgb, 1.0f);}ENDCG }// Second pass - here we render the front faces of the diamonds.Pass {ZWrite OnBlend One OneCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f {float4 pos : SV_POSITION;float3 uv : TEXCOORD0;half fresnel : TEXCOORD1;};v2f vert (float4 v : POSITION, float3 n : NORMAL){v2f o;o.pos = mul(UNITY_MATRIX_MVP, v);// TexGen CubeReflect:// reflect view direction along the normal, in view space.float3 viewDir = normalize(ObjSpaceViewDir(v));o.uv = -reflect(viewDir, n);o.uv = mul(_Object2World, float4(o.uv,0));o.fresnel = 1.0 - saturate(dot(n,viewDir));return o;}fixed4 _Color;samplerCUBE _RefractTex;half _ReflectionStrength;half _EnvironmentLight;half _Emission;half4 frag (v2f i) : SV_Target{half3 refraction = texCUBE(_RefractTex, i.uv).rgb * _Color.rgb;half4 reflection = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.uv);reflection.rgb = DecodeHDR (reflection, unity_SpecCube0_HDR);half3 reflection2 = reflection * _ReflectionStrength * i.fresnel;half3 multiplier = reflection.rgb * _EnvironmentLight + _Emission;return fixed4(reflection2 + refraction.rgb * multiplier, 1.0f);}ENDCG}// Shadow casting & depth texture support -- so that gems can// cast shadowsUsePass "VertexLit/SHADOWCASTER"} }下面我來分析其中的姿勢點(diǎn):
個(gè)人水平有限很多說法可能是錯(cuò)的,這里申明下先。
1.折射光的模擬
首先它的折射光并沒有真實(shí)計(jì)算,而是給了這樣一張CubeMap圖,然后實(shí)際上是通過反射在計(jì)算。
2.pass設(shè)計(jì)
大致分兩步,第一個(gè)pass是繪制鉆石的背面,第二個(gè)當(dāng)然就是前面了。注意第二個(gè)pass里的Blend One One,表示兩個(gè)圖層1:1混合。
考慮到鉆石是個(gè)透明的物體,這樣設(shè)計(jì)也是合情合理的。
3.光照模型設(shè)計(jì)
大致也是折射光、反射光、環(huán)境光、自發(fā)光各種疊加各種乘啦。反正因?yàn)榘咨?55,黑色是0,這里雖說算的是光,但實(shí)際處理的還是顏色,不管是加還是乘,都是越疊越亮,符合光線疊加的基本規(guī)律。至于它的計(jì)算公式是源自準(zhǔn)確的光學(xué)原理,還是近似的模擬,我當(dāng)然也不知道啦。
4.HDR
High Dynamic Range,不負(fù)責(zé)任的說下,大概就是假如0是黑色,1是白色,那么有些部分的顏色可以超過1,這樣最終畫面結(jié)果會(huì)呈現(xiàn)出一種亮處高閃耀的效果,具體請參考Unity官方文檔說明。
5.菲涅爾效果
簡單說就是因?yàn)楣饩€射向玻璃時(shí),一部分被反射,一部分直接射進(jìn)去了,導(dǎo)致最終反射光線呈現(xiàn)一個(gè)視角越平行于入射面,反射效果越強(qiáng),越垂直越弱的現(xiàn)象。
第二個(gè)pass計(jì)算時(shí)引入了這個(gè)因子。
所以一開始我說自己難以建立準(zhǔn)確的模型,比如像這種細(xì)微而又真實(shí)存在的現(xiàn)象,僅憑一點(diǎn)粗淺的折射反射姿勢而不去深入學(xué)習(xí)又怎么能考慮到呢?
這個(gè)shader里用了好多宏,在CGIncludes文件夾里的那些文件里都可以查到,這些我大致可以理解或者叫猜到怎么回事,但要真正弄清楚肯定得費(fèi)一番功夫。
這里請?jiān)试S豬哥偷個(gè)懶,隨便說說裝作懂了的樣子,具體的計(jì)算細(xì)節(jié)就不一一展開了。
結(jié)語:
至此初階段的shader學(xué)習(xí)計(jì)劃就算完成了,由于我刻意趕了下進(jìn)度,偷了些懶,比計(jì)劃提前了一周。一分耕耘一分收獲,shader這塊水很深,想要有所造詣肯定得花更多的時(shí)間。但我出于全盤考慮目前并不想在這塊繼續(xù)投放精力。
下一階段目光回到C#語言這塊最基礎(chǔ)的領(lǐng)域,繼續(xù)修內(nèi)力。去年買的《C#本質(zhì)論》要收尾,一直沒翻的《深入理解C#》也要看完。
談?wù)Z言難免會(huì)牽涉到設(shè)計(jì)模式,但也盡量避開。
周期定在五周吧。
總結(jié)
以上是生活随笔為你收集整理的三言两语说shader(九)钻石的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android代码设置锁屏壁纸
- 下一篇: 为什么单片机接地电阻都是10千欧