1.基于uv的texture distortion
當(dāng)液體靜止時(shí),它在視覺(jué)上與固體沒(méi)有太大區(qū)別。 但大多數(shù)時(shí)候,我們的性能不一定支持去實(shí)現(xiàn)特別復(fù)雜的水物理模擬, 需要的只是在常規(guī)的靜態(tài)材料的表面上讓其運(yùn)動(dòng)起來(lái)。我們可以對(duì)網(wǎng)格的 UV 坐標(biāo)實(shí)現(xiàn)動(dòng)態(tài)變化,從而讓表面的紋理效果實(shí)現(xiàn)變形的動(dòng)態(tài)變化。
1.1. uv實(shí)時(shí)變化
我們直接生成一個(gè)默認(rèn)unlit shader,然后賦予其對(duì)應(yīng)的紋理圖片,并且寫入隨時(shí)間變化的uv更新函數(shù)。
//in frag shader
i.uv += _Time.y;
為了實(shí)現(xiàn)隨機(jī)方向的uv變化,我們這里引入一張指示流動(dòng)方向的貼圖。
float2 flowDir = tex2D(_FlowMap, i.uv);
i.uv += _Time.y * flowDir;
可以看到隨著時(shí)間推移,出現(xiàn)了uv擠壓過(guò)度的情況,我們這里可以直接用一個(gè)frac或者正弦函數(shù)來(lái)限制uv隨時(shí)間的變化范圍。
tilling為4顯得紋理太小了,我們把tilling調(diào)整回1。
但是在進(jìn)行實(shí)時(shí)變形的過(guò)程中,紋理會(huì)完全恢復(fù)到未變形的狀態(tài),還是有點(diǎn)過(guò)于奇怪,我們通過(guò)對(duì)三角函數(shù)進(jìn)行縮放和位移,使紋理一直保持在變形的狀態(tài)。
//即使得時(shí)間參數(shù)一直為正數(shù)
i.uv += (sin(_Time.y)/2.0 + 1.0) * flowDir;
當(dāng)我們使用frac函數(shù)來(lái)對(duì)time進(jìn)行限制的時(shí)候,就會(huì)出現(xiàn)突然抖動(dòng),一跳又變成了平整的未變形紋理的情況。這是由于frac本身會(huì)帶來(lái)取值從1-0的非連續(xù)變化帶來(lái)的。
1.2. 實(shí)現(xiàn)循環(huán)銜接
既然frac函數(shù)存在不連續(xù)的問(wèn)題,我們就得想辦法掩蓋一下(或者直接換成sin函數(shù))。掩蓋的辦法就是通過(guò)黑色的淡入淡出,來(lái)掩蓋住發(fā)生紋理顏色跳躍的瞬間。
即在frac的極大值和極小值時(shí),都需要是黑色,且其需要在原有的周期內(nèi),實(shí)現(xiàn)從最小值到最大值的變化,再由最大值變?yōu)樽钚≈?,周期縮短一半(橙色線條)。
///flowDir 命名時(shí)改為Float3,但其實(shí)這邊單獨(dú)設(shè)一個(gè)小數(shù)也可以的
flowDir.z = 1.0 - abs(1.0 - 2* time_fac);
fixed4 col = tex2D(_MainTex, i.uv) * flowDir.z;
為了讓貼圖的變形更多樣化,增加信息量,我們引入相關(guān)的噪音,放入到flowmap中作為a通道。從a通道中采樣noise信息,混入時(shí)間計(jì)算當(dāng)中。
float3 flowDir;
flowDir.xy = tex2D(_FlowMap, i.uv).rg;
float noise = tex2D(_FlowMap, i.uv).a;
float time_fac = frac(_Time.y + noise);
flowDir.z = 1.0 - abs(1.0 - 2* time_fac);
i.uv += time_fac * flowDir;
fixed4 col = tex2D(_MainTex, i.uv) * flowDir.z;
由于噪音的混入,使得漸變的黑色也能像波紋一樣擴(kuò)散。且本身引入噪音后,即便是不適用黑色進(jìn)行漸入,非連續(xù)跳躍的問(wèn)題也得到了有效緩解。
1.3. 混合紋理變形
當(dāng)然通過(guò)黑色來(lái)進(jìn)行淡入淡出,還是略顯突兀一些,最好的方案肯定還是兩種不同變形的紋理進(jìn)行混合,通過(guò)權(quán)重進(jìn)行區(qū)分。最好是當(dāng)其中一個(gè)紋理的uv偏移為0時(shí),另一個(gè)紋理的uv偏移為1,以確保能夠全時(shí)刻都處在變形的狀態(tài)。
首先我們要將原本放在frag shader里面計(jì)算uv偏移的部分打包成一個(gè)函數(shù):
輸入是變形前的uv,變形方向(采樣自flowmap),時(shí)間參數(shù)和用于判斷是否為第二個(gè)混合紋理的布爾參數(shù)。
返回值是三位浮點(diǎn)數(shù),uvw,對(duì)應(yīng)偏移后的uv以及對(duì)應(yīng)的可見(jiàn)度參數(shù)。
float3 flowUVW(float2 uv, float2 flowVec, float3 time, bool Btag)
{
//period對(duì)應(yīng)偏移周期
float phaseOffset = Btag? _Period: 0 ;
time += phaseOffset;
float3 uvw;
uvw.xy = uv + time * flowVec;
uvw.z = 1.0 - abs(1.0 - 2* time);
return uvw;
}
//in frag shader
float2 flowVec;
flowVec = tex2D(_FlowMap, i.uv).rg;
float noise = tex2D(_FlowMap, i.uv).a;
float time_fac = frac(_Time.y + noise);
float3 uvwA = flowUVW(i.uv, flowVec, time_fac, false);
float3 uvwB = flowUVW(i.uv, flowVec, time_fac, true);
fixed4 colA = tex2D(_MainTex, uvwA.xy) * uvwA.z;
fixed4 colB = tex2D(_MainTex, uvwB.xy) * uvwB.z;
fixed4 col = colA + colB;
會(huì)出現(xiàn)黑色區(qū)域過(guò)多的問(wèn)題,目測(cè)是因?yàn)閡vw的z值沒(méi)有處理好,并沒(méi)有做好嚴(yán)格的偏移1/2個(gè)周期,導(dǎo)致累加的效果有問(wèn)題。所以我們這里需要換成:
fixed4 colA = tex2D(_MainTex, uvwA.xy) * uvwA.z;
fixed4 colB = tex2D(_MainTex, uvwB.xy) * (1 - uvwA.z);
確保兩個(gè)權(quán)重相加為1,調(diào)整后黑波紋的效果改善了。
當(dāng)然這里也可以對(duì)time += phaseOffset做調(diào)整,傳入前的時(shí)間參數(shù)不再進(jìn)行取小數(shù),而改用frac函數(shù)處理傳入后的值,能夠?qū)崿F(xiàn)一樣的效果。
在計(jì)算uv時(shí),為更好表現(xiàn)兩個(gè)uv的偏移程度,最好在uv初始化時(shí)也加入偏移值。
//in flowUVW function
uvw.xy = uv + time * flowVec + phaseOffset ;
1.4.通過(guò)uv jumping調(diào)整循環(huán)的周期
目前來(lái)說(shuō),我們的uv動(dòng)畫的周期,完全取決于_Time.y的固定周期。若是我們想要自定義地去調(diào)整uv動(dòng)畫的周期,則需要人為地添加相應(yīng)的變量。
這里我們引入了jump參數(shù),一個(gè)二維浮點(diǎn)數(shù)變量。
我們可以分別定義jump參數(shù)在u方向,v方向上的數(shù)值,通過(guò)這兩個(gè)數(shù)值來(lái)控制循環(huán)周期。
_UJump ("u direction jump para", Range(-0.25, 0.25)) = 0.25
_VJump ("v direction jump para", Range(-0.25, 0.25)) = 0.25
//in frag shader
float2 jump = float2(_UJump, _VJump);
在flowUVW,我們新傳入入jump參數(shù),在初始化uvw的xy值后,通過(guò)jump參數(shù)對(duì)uvw.xy做一個(gè)疊加。
float3 flowUVW_jump(float2 uv, float2 flowVec, float2 jump, float3 time, bool Btag){
float phaseOffset = Btag? _Period: 0 ;
float progress = frac(time + phaseOffset);
float3 uvw;
uvw.xy = uv + progress * flowVec + phaseOffset;
uvw.xy += (time - progress) * jump ;
uvw.z = 1.0 - abs(1.0 - 2* progress);
return uvw;
}
//in frag shader
float time_fac = _Time.y + noise;
float2 jump = float2(_UJump, _VJump);
float3 uvwA = flowUVW_jump(i.uv, flowVec, jump, time_fac, false);
float3 uvwB = flowUVW_jump(i.uv, flowVec, jump, time_fac, true);
可知我們得uv值的初始值是固定的,隨著時(shí)間的變化,time值(即_Time.y + noise的值)逐漸增大,uvw.xy的值最終會(huì)以(time - progress) * jump為周期穩(wěn)定循環(huán),最終實(shí)現(xiàn)uv動(dòng)畫的可控周期。
在使得flowVec 為(0,0)時(shí),我們能夠比較明顯地比較jump參數(shù)帶來(lái)的周期差異:
左邊是使用了jump為(0.25, 0)時(shí)的uv動(dòng)畫,右邊為僅使用flowUVW的uv動(dòng)畫。
2. 為uv動(dòng)畫添加其他參數(shù)
2.1. Tilling
用于調(diào)整貼圖縮放的參數(shù),典中典操作。
//in flowUVW
uvw.xy = uv + progress * flowVec + phaseOffset;
uvw.xy *= _Tilling;
//.....
2.2. 動(dòng)畫速度
顯然這個(gè)是放到跟_Time.y乘在一起的,影響uv動(dòng)畫的速度。
2.3. 變形/流動(dòng)強(qiáng)度
通過(guò)設(shè)置相關(guān)參數(shù),放大/縮小從a通道采樣到的變形方向。
2.4. 通過(guò)變形偏移控制uv動(dòng)畫的清晰度
目前來(lái)看,uv動(dòng)畫的清晰度多少有點(diǎn)捉雞,這是由uv偏移程度,和當(dāng)前紋理的權(quán)重決定的。
顯然,當(dāng)uvw.z=1時(shí),當(dāng)前計(jì)算的顏色越清晰。(無(wú)論是a顏色還是b顏色,只要有其中一方的uvw.z為1時(shí),另一方則一定為0)
當(dāng)uvw.z=1時(shí), progress值為1/2。即time + phaseoffset值為xxxxxx.5。
所以我們需要在uvw.z=1的前提下,添加一個(gè)_FlowOffset,通過(guò)和progress相互抵消,去抹平相應(yīng)的uv偏移,使得uv不受flowVec引入的各向異性變形的影響。
//in flowUVW,
uvw.xy = uv + (progress + _FlowOffset) * flowVec + phaseOffset;
uvw.xy *= _Tilling;
使用_FlowOffset(左)和不使用_FlowOffset(右)的對(duì)比。
當(dāng)變形強(qiáng)度增大時(shí),清晰化的效果會(huì)更明顯。
2.5. 替換顏色貼圖
接下來(lái)我們使用真正的液體顏色貼圖,以及對(duì)應(yīng)的法線貼圖。
同樣的,我們需要各自用uvwA和uvwB對(duì)變換后的法線貼圖進(jìn)行采樣,并對(duì)采樣后的法線和進(jìn)行標(biāo)準(zhǔn)化。
//in frag shader
//前置步驟:計(jì)算TBN矩陣,傳遞TBN矩陣,獲取worldPos,執(zhí)行uvwA,uvwB的計(jì)算等
float4 packedBumpA = tex2D(_NormalTex, uvwA.xy);
float4 packedBumpB = tex2D(_NormalTex, uvwB.xy);
float3 tangentSpaceNormalA = UnpackNormalWithScale(packedBumpA, _BumpScale);
float3 tangentSpaceNormalB = UnpackNormalWithScale(packedBumpB, _BumpScale);
float3 worldNormalA = normalize(mul(tangent2World, tangentSpaceNormalA));
float3 worldNormalB = normalize(mul(tangent2World, tangentSpaceNormalB));
float3 worldNormal = normalize(worldNormalA + worldNormalB);
//后續(xù)步驟:bling-phong三部曲
2.6.嘗試不同的噪音組合
我用SD也做了一點(diǎn)自制噪音貼圖,主要是voronoi和bnw混合,以及voronoi和plasma混合,大家也可以自己做一些嘗試。
如果是老版本的SD,沒(méi)有voronoi節(jié)點(diǎn)的話,可以用tile generator加上distance來(lái)做一個(gè)簡(jiǎn)易的voronoi。
2.7. 通過(guò)derivative map增加法線細(xì)節(jié)
由于圖像壓縮的機(jī)制,常規(guī)的法線貼圖往往會(huì)有一定程度的細(xì)節(jié)丟失。derivative map主要是將法線的x,y方向的偏導(dǎo)數(shù)(即切線和副切線的部分值)存儲(chǔ)在a,g通道,而不需要經(jīng)過(guò)再次轉(zhuǎn)換。
我們直接通過(guò)a,g通道的值組成兩個(gè)切線,并通過(guò)叉乘的方式反求法線。
同樣的,我們寫一個(gè)解包函數(shù),把從圖像中讀取到的值恢復(fù)到(-1,1)的區(qū)間。
float3 UnpackDerivativeHeight(float4 textureData){
float3 dh = textureData.agb;
dh.xy = dh.xy * 2 - 1;
return dh;
}
在片元著色器中,我們?nèi)縿h掉切線空間轉(zhuǎn)換相關(guān)的內(nèi)容,但是需要保留worldPos來(lái)做光照計(jì)算。其余的換成綜合兩個(gè)采樣的法線貼圖求解worldNormal的部分。
注意這里和catlikecoding大佬的教程不同,由于derivative map展開的是沿z方向的法線在xy方向的偏導(dǎo)數(shù),但是我們沒(méi)有再去求TBN矩陣來(lái)做轉(zhuǎn)換,而是直接使用相應(yīng)的值。需要還原出來(lái)的法線是沿y方向的,所以需要調(diào)整下相應(yīng)的組成分布來(lái)求解正確的法線方向。
//in frag shader
float3 dhA = UnpackDerivativeHeight(tex2D(_DerivHeightMap, uvwA.xy)) * uvwA.z;
float3 dhB = UnpackDerivativeHeight(tex2D(_DerivHeightMap, uvwB.xy)) * uvwB.z;
float2 worldNormalXY = -(dhA.xy + dhB.xy);
float3 worldNormal = normalize(float3(worldNormalXY.x, 1, worldNormalXY.y));
//正常計(jì)算bling-phong
右邊為使用derivative map的uv流體動(dòng)畫。
2.8.控制浪高
通過(guò)增加可調(diào)的_HeightScale參數(shù),影響采樣后的權(quán)重計(jì)算。
float3 dhA = UnpackDerivativeHeight(tex2D(_DerivHeightMap, uvwA.xy)) * (uvwA.z * _HeightScale);
float3 dhB = UnpackDerivativeHeight(tex2D(_DerivHeightMap, uvwB.xy)) * (uvwB.z * _HeightScale);
更近一步的,我們能夠通過(guò)波流動(dòng)速度來(lái)影響浪高。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-779810.html
float finalHeightScale = length(flowVec) * _Flow2HeightScale + _HeightScale;
float3 dhA = UnpackDerivativeHeight(tex2D(_DerivHeightMap, uvwA.xy)) * (uvwA.z * finalHeightScale);
float3 dhB = UnpackDerivativeHeight(tex2D(_DerivHeightMap, uvwB.xy)) * (uvwB.z * finalHeightScale);
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-779810.html
到了這里,關(guān)于【unity shader】水體渲染基礎(chǔ)-基于texture distortion的流體流動(dòng)材質(zhì)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!