1.關(guān)于頂點(diǎn)波形:
為了表示寬廣水域中的水體變化,往往需要進(jìn)行水平面的整體運(yùn)動(dòng)變化。即對(duì)平面的頂點(diǎn)進(jìn)行位移,以實(shí)現(xiàn)波浪的起伏效果。現(xiàn)在對(duì)于波浪的構(gòu)成,如快速傅里葉變換和波浪的統(tǒng)計(jì)學(xué)理論,在游戲中的應(yīng)用也相對(duì)完善。今天主要是做一個(gè)基礎(chǔ)的波浪實(shí)現(xiàn):正弦波形。
1.1. 基礎(chǔ)正弦波形
我們拖出一塊平面,修改其頂點(diǎn)著色器,片元著色器中我們直接返回一個(gè)海面顏色。
v2f o;
float3 p;
p = v.vertex;
p.y = sin(p.x);
//注意這里肯定不能在視口變換完后再求正弦,原因不用多說了吧?
o.vertex = UnityObjectToClipPos(p);
得到基礎(chǔ)波形。
1.2. 幅度參數(shù)
增加幅度參數(shù)_Amplitude,主要進(jìn)行波峰控制。
p.y = _Amplitude * sin(p.x);
1.3. 波長(zhǎng)參數(shù)
波長(zhǎng)參數(shù)_Wavelength,主要影響的是正弦函數(shù)的周期。即函數(shù)需要花費(fèi)更長(zhǎng)的時(shí)間完成一次周期性的變化,表現(xiàn)上的意義就是水波的寬度。
注意默認(rèn)下正弦函數(shù)sin(x)的周期是2π,為了更直觀地進(jìn)行控制,我們要增加一個(gè)k參數(shù)作為最終的控制向量。通過2π/_Wavelength來求解k,實(shí)現(xiàn)_Wavelength越大,最終表現(xiàn)上的水波寬度增大。
float k = 2 * UNITY_PI / _Wavelength;
p.y = _Amplitude * sin(k * p.x);
注意看這里明顯出現(xiàn)了尖銳抖動(dòng)的問題,說明平面本身的頂點(diǎn)數(shù)顯然不夠支撐太細(xì)致的表現(xiàn)了。我們?cè)谙乱徊街刈鲆幌聝蓚€(gè)頂點(diǎn)數(shù)更多的平面來對(duì)比一下。
1.4. 波浪速度參數(shù)
_Wavespeed,這個(gè)不太需要多說明了
p.y = _Amplitude * sin(k * (p.x + _Wavespeed * _Time.y));
這里第一個(gè)是unity自帶的平面,左右兩個(gè)是自己做的2020,3030的平面,可以看到其在波長(zhǎng)較小的情況,由于頂點(diǎn)不夠下會(huì)出現(xiàn)硬邊緣的問題。
小提示:關(guān)于blender的模型導(dǎo)出到unity
默認(rèn)配置下,尺寸差異為五倍,且在保持
1.5. 求解法向量
目前的問題是,即便是有了波形,這個(gè)平面看起來也只是一層皮,所以需要進(jìn)行法向量以便于在片元著色器里面做光照計(jì)算。
由于我們這里對(duì)頂點(diǎn)進(jìn)行了調(diào)整,所以平面原本的法線信息肯定也不能用了,我們必須自己去求解。
首先我們要求解他的切線方向,就是求導(dǎo)數(shù)。
所幸的是,目前我們的向量只在x,y方向上有變化,即z方向上的導(dǎo)數(shù)為0。導(dǎo)數(shù)T = ( x’, y’ , 0 )。
由于目前發(fā)生變化的是x,所以最終導(dǎo)數(shù)都是對(duì)于x進(jìn)行求導(dǎo), T = ( 1, k * _Amplitude * cos( k * x), 0)。
對(duì)于副切線的方向,我們很好確定,就是沿z方向的單位向量(0, 0, 1)。
最后我們通過叉乘方式獲得法向量。
//in vertex shader
float3 tangent = normalize(float3(1, k * _Amplitude * cos(f), 0));
float3 normal = cross(float3(0, 0, 1.0), tangent);
//normal直接傳遞使用即可
//in vertex shader
float3 LightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0 * dot(i.normal, LightDir);
我們把自定義的水面顏色作為ambient,與diffuse相加返回即可。
1.6. 關(guān)于網(wǎng)格精度
在波長(zhǎng)相對(duì)較大的時(shí)候,由于水波幅度本身較大,即各頂點(diǎn)間的位移相對(duì)變化越小,平面網(wǎng)格本身的差異帶來的表現(xiàn)影響看似區(qū)別不大。當(dāng)波長(zhǎng)較小時(shí),網(wǎng)格間的精度差異就會(huì)對(duì)表現(xiàn)產(chǎn)生顯著影響。
當(dāng)波長(zhǎng)為2時(shí),1010, 2020, 3030的網(wǎng)格表現(xiàn)差異就特別明顯。
所以這里我們引入一個(gè)100100的高精度平面網(wǎng)格,進(jìn)行2020,3030, 100100的網(wǎng)格對(duì)比。
后續(xù)我們的案例中只使用100100的平面。
1.7. 陰影的修正
我們直接上陰影三件套給正弦波附上陰影,這里顯然看出來默認(rèn)的陰影計(jì)算存在一些問題:雖然法線的計(jì)算已經(jīng)正確了,但是頂點(diǎn)的變化顯然沒有對(duì)shadow map產(chǎn)生影響,從而致使陰影顯得非常平面。(這邊視效變了,我只是又調(diào)了一下光照參數(shù))
主要的原因還是,默認(rèn)的shadowmapping方法不支持頂點(diǎn)偏移的shader,所以我們需要自己去寫一個(gè)shadowcaster。
對(duì)于surface shader來說,我們有比較簡(jiǎn)易的方案,讓陰影計(jì)算使用我們進(jìn)行頂點(diǎn)變化的vertex shader。
#pragma surface surf Standard fullforwardshadows vertex:vert addshadow
//#pragma surface <surface function> <lighting model> <optional parameters>
但對(duì)于unlit shader來說,并不支持直接使用#pragma來指定頂點(diǎn)著色器,我們需要自己去寫一個(gè)陰影計(jì)算的pass。
實(shí)際上shadowcaster也有三件套:
定義在fragment 結(jié)構(gòu)體定義中的V2F_SHADOW_CASTER;
放在vertex shader里面做光口變換的TRANSFER_SHADOW_CASTER_NORMALOFFSET( );
放在fragment shader中的SHADOW_CASTER_FRAGMENT(i);
Pass
{
//原本的頂點(diǎn)偏移pass,記得添加陰影三件套
}
Pass
{
Tags {"LightMode"="ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
float _Amplitude;
float _Wavelength, _Wavespeed;
//陰影映射不需要顏色紋理相關(guān)的參數(shù)
struct v2f
{
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f o;
float3 p;
float k = 2 * UNITY_PI / _Wavelength;
p = v.vertex;
float f = k * (p.x + _Wavespeed * _Time.y);
p.y = _Amplitude * sin(f);
v.vertex.xyz = p;
//無需進(jìn)行視口變換,只需要給原有的vertex賦值
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
return o;
}
fixed4 frag(v2f i): SV_Target
{
//若需要做深度剔除,仍需要進(jìn)行單獨(dú)計(jì)算處理
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
當(dāng)頂點(diǎn)偏移計(jì)算方法改變后,shadowcaster中的vertex計(jì)算方法也要隨之進(jìn)行對(duì)應(yīng)調(diào)整。為了簡(jiǎn)化,后續(xù)我就不再單獨(dú)對(duì)shadowcaster pass進(jìn)行額外說明了。
Gerstner波:
正弦波比較簡(jiǎn)單,但是顯然是有點(diǎn)簡(jiǎn)化過頭了。所以我們需要在這個(gè)基礎(chǔ)上,更進(jìn)一步。
同樣的,我們做的頂點(diǎn)shader實(shí)際上是對(duì)模型表面的頂點(diǎn)(一層皮)進(jìn)行處理。在正弦波中,每個(gè)頂點(diǎn)都遵循正弦函數(shù),只在y軸方向上做上下運(yùn)動(dòng)。在Gerstner波中,頂點(diǎn)在做沿y軸方向的運(yùn)動(dòng)同時(shí),也做沿x軸方向的運(yùn)動(dòng)。
在x軸方向上,可以看到頂點(diǎn)的偏移由正到負(fù)再到正,這個(gè)是典型的余弦函數(shù)的特征。
所以我們新的頂點(diǎn)方程就是在原有的x值基礎(chǔ)上加上余弦函數(shù)作為偏移:
vertex = (x + Acos(kx), Asin(kx), 0)
求導(dǎo)后,即
T = (1 - Aksin(kx), Akcos(kx), 0)
副切線依然沒有變化,我們可以直接使用叉乘求解法線。
p.y = _Amplitude * sin(f);
p.x += _Amplitude * cos(f);
float3 tangent = normalize(float3(1 - k * _Amplitude * sin(f), k * _Amplitude * cos(f), 0));
float3 normal = normalize(cross(float3(0, 0, 1.0), tangent));
2.1. 重疊波的調(diào)整
雖然由此產(chǎn)生的波浪可能看起來不錯(cuò),但情況并非總是如此。 例如,將波長(zhǎng)和波幅都減少后,會(huì)產(chǎn)生奇怪的結(jié)果,看起來各個(gè)波浪間出現(xiàn)了相互折疊的現(xiàn)象。
為什么會(huì)有這樣的現(xiàn)象?主要還是因?yàn)閤值計(jì)算方法的調(diào)整,加上波幅和波長(zhǎng)兩個(gè)參數(shù)的共同作用,導(dǎo)致不同x的計(jì)算值結(jié)果重疊的情況。
為什么會(huì)出現(xiàn)這樣的情況?
主要是在兩個(gè)參數(shù)的影響下,x2-x1的值無法保證一定比A*( cos(kx1) - cos(kx2))的值大。
當(dāng)k較大時(shí),三角函數(shù)會(huì)被極大地壓縮,在一定范圍內(nèi)的變化幅度將非常巨大,導(dǎo)致A*( cos(kx1) - cos(kx2)) 的最大值為 2A。
顯然A值是一個(gè)不可控的因素,為了更好的控制這種情況,我們使用1/k來代替原有的A(波幅)參數(shù)。當(dāng)k增大,導(dǎo)致三角函數(shù)的波動(dòng)較大時(shí),1/k會(huì)相應(yīng)地減少,從而削減( cos(kx1) - cos(kx2)) 的最大值,使其最大值為 2/k。此外,我們?cè)黾右粋€(gè)_Steepness 參數(shù),使用_Steepness /k替換原有的波幅參數(shù),以更好發(fā)揮波峰處的控制作用。
float _Amplitude = _Steepness / k;
float f = k * (p.x + _Wavespeed * _Time.y);
p.y = _Amplitude * sin(f);
p.x += _Amplitude * cos(f);
可以看到在使用新參數(shù)后,波峰重疊的現(xiàn)象大大緩解了,在進(jìn)一步縮小_Steepness 后,整個(gè)波峰都會(huì)被相應(yīng)地削平。
2.2. 現(xiàn)實(shí)中的波速
在現(xiàn)實(shí)物理中,波速實(shí)際上跟波長(zhǎng)相關(guān),即
物理的問題我們不多聊,直接進(jìn)行參數(shù)替換,以后就用不著_Wavespeed了。
float _Amplitude = _Steepness / k;
//新增c,波長(zhǎng)相關(guān)的波速參數(shù),原有的_Wavespeed被替換
float c = sqrt(9.8 / k);
float f = k * (p.x + c * _Time.y);
p.y = _Amplitude * sin(f);
p.x += _Amplitude * cos(f);
2.3. 自定方向波
說完了以x軸為方向的,以xz平面中任一直線方向的波公式,也呼之欲出了。即x,z會(huì)同時(shí)作用于y方向的計(jì)算結(jié)果,且在x,z方向上也會(huì)根據(jù)當(dāng)前的參數(shù)值發(fā)生偏移。
對(duì)于**二維方向 (x1, z1)**來說:
首先放入三角函數(shù)中的f值要變?yōu)椋?/p>
f = k *(dot((x1, z1),(vertex.x,vertex.z)+ c * _Time.y)
所以偏移后的頂點(diǎn)Vo中,x,y,z的值為:
x = x + x1 * _Steepness / k * cos(f)
y = _Steepness / k * sin(f)
z = z + z1 * _Steepness / k * cos(f)
搞定了頂點(diǎn)偏移的三維表示,接下來我們?cè)撉蠼夥ㄏ蛄苛???芍谧远ǚ较虿ǖ膮⑴c下,x軸z軸都會(huì)參與到法線的求解中。
常見的求解思路有兩種,一種是沿著波的方向進(jìn)行求解切線和副切線,一種是依然按照x,z兩個(gè)方向分別進(jìn)行求解。(原諒我圖示的法線不是畫的嚴(yán)格準(zhǔn)確的)
顯而易見的是,沿著波方向做切線的求解基本上是不可能的,或者說復(fù)雜度高的嚇人。依然沿x,z方向求解各自的偏導(dǎo)數(shù)的方案,復(fù)雜度比較低。因?yàn)橹唤o出一條法線的情況下,能夠叉乘得出其的兩個(gè)垂直切線的結(jié)果是不唯一的。
沿x方向進(jìn)行dx求導(dǎo),得到tangent:
x = 1 - x1 * x1 * _Steepness * sin(f)
y = x1 * _Steepness * cos(f)
z = - z1 * x1 * _Steepness * sin(f)
沿z方向進(jìn)行dz求導(dǎo),得到bitangent:
x = - x1 * z1 * _Steepness * sin(f)
y = z1 * _Steepness * cos(f)
z = 1 - z1 * z1 * _Steepness * sin(f)
2.4. 多波疊加
在現(xiàn)實(shí)的海面場(chǎng)景中,往往都是多個(gè)波進(jìn)行疊加,各個(gè)波之間能夠有不同的參數(shù)。在原有的設(shè)置下,3個(gè)參數(shù)非常不便于管理。
我們索性用一個(gè)四維變量將其整合到一起,并使用一個(gè)函數(shù)來對(duì)這個(gè)四維變量進(jìn)行處理。)
Properties
{
//其他屬性
_WavwA("wave A: directionX, directionZ, _Steepness, _Wavelength", Vector) = (1, 1, 0.8, 3.0)
}
float3 gerstner(float4 wave, float3 p, inout float3 tangent, inout float3 bitangent)
{
///另附
}
v2f vert (appdata v)
{
v2f o;
float3 p = v.vertex;
float3 tangent = float3(0.0, 0.0, 0.0);
float3 bitangent = float3(0.0, 0.0, 0.0);
p += gerstner(_WavwA, v.vertex, tangent, bitangent);
float3 normal = normalize(cross(bitangent, tangent));
//其他變換和屬性賦值,陰影計(jì)算
return o;
}
主要是將原有的計(jì)算搬到自定義函數(shù)里
float3 gerstner(float4 wave, float3 p, inout float3 tangent, inout float3 bitangent)
{
float k = 2 * UNITY_PI / wave.w;
float _Amplitude = wave.z / k;
float c = sqrt(9.8 / k);
float f = k * ( dot(wave.xy, p.xz) + c * _Time.y);
tangent += normalize(float3(1 - wave.x * wave.x * k * _Amplitude * sin(f),
wave.x * k * _Amplitude * cos(f),
- wave.x * wave.y * k * _Amplitude * sin(f)));
bitangent += normalize(float3( - wave.y * wave.x * k * _Amplitude * sin(f),
wave.y * k * _Amplitude * cos(f),
1 - wave.y * wave.y * k * _Amplitude * sin(f)));
return float3( wave.x * _Amplitude * cos(f), _Amplitude * sin(f), wave.y * _Amplitude * cos(f));
}
完成前置準(zhǔn)備后,我們直接在屬性中定義兩個(gè)波,并在頂點(diǎn)著色器中使之相加,即可完成多波疊加。
v2f vert (appdata v)
{
v2f o;
float3 p = v.vertex;
float3 tangent = float3(0.0, 0.0, 0.0);
float3 bitangent = float3(0.0, 0.0, 0.0);
p += gerstner(_WavwA, v.vertex, tangent, bitangent);
p += gerstner(_WavwB, v.vertex, tangent, bitangent);
float3 normal = normalize(cross(bitangent, tangent));
//變換,陰影計(jì)算等
}
需要注意的是,多波疊加后,重疊波的問題又會(huì)再次出現(xiàn),我們需要注意讓各個(gè)波_Steepness的累計(jì)和不要超過1,以避免該問題。文章來源:http://www.zghlxwxcb.cn/news/detail-768301.html
2.4.1. 三波疊加
參數(shù)如下,一般三個(gè)波會(huì)采取等比例的波長(zhǎng)。
單波作為對(duì)比文章來源地址http://www.zghlxwxcb.cn/news/detail-768301.html
到了這里,關(guān)于【unity shader】水體渲染基礎(chǔ)-通過頂點(diǎn)偏移實(shí)現(xiàn)波浪的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!