接下來(lái)是水體渲染基礎(chǔ)的最后一篇,通過(guò)水面看到水下的物體,并呈現(xiàn)深度效果。
1. 搭建簡(jiǎn)單演示場(chǎng)景
我們直接搭一個(gè)小場(chǎng)景。
增加水面,賦予uv變形的水面材質(zhì),并增加透明度的設(shè)置。
SubShader
{
Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
LOD 100
Pass
{
//Tags {"LightMode" = "ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
//.......返回的color結(jié)果,添加一個(gè)控制透明度的參數(shù)
}
//注意FallBack也要注釋掉
}
2. 基于霧效實(shí)現(xiàn)水深效果
水體會(huì)吸收光線,所以真實(shí)的水體并不是完全透明的。此外,水體對(duì)不同頻率的光吸收率不同,藍(lán)光被吸收最少。
故深度越深,水中的物體就會(huì)變成藍(lán)色。
我們當(dāng)然可以直接上一個(gè)全局霧,但這里我們最好還是使用僅面向水體的霧效計(jì)算。
這里開(kāi)始,我們新增一個(gè)水下計(jì)算相關(guān)的cginc和一個(gè)返回水下片元顏色結(jié)果的函數(shù)。
新建cginc文件時(shí)我們需要注意,在windows文件夾下創(chuàng)建txt文件,并注意要修改文件后綴。
#include "LookingThroughWater.cginc"
float3 ColorBelowWater ()
{
//目前只返回黑色
return 0;
}
//返回值乘以ColorBelowWater()的結(jié)果,透明度調(diào)整為1
首先我們定義了最簡(jiǎn)單的水下片元顏色計(jì)算,得到了石油一般的流體
那么要計(jì)算深度霧,首先我們需要一個(gè)攝像機(jī)深度貼圖。
// in xxx.cginc
sampler2D _CameraDepthTexture;
另外,在著色器計(jì)算時(shí),我們需要獲取對(duì)應(yīng)的屏幕空間坐標(biāo)。
在surface shader里面加入screenPos:
struct Input
{
float2 uv_MainTex;
float4 screenPos;
};
…
void surf (Input IN, inout SurfaceOutputStandard o)
{
…
o.Albedo = ColorBelowWater(IN.screenPos);
o.Alpha = 1;
}
在unlit shader里面,我們可以在片元著色器中增加VPOS語(yǔ)義,實(shí)現(xiàn)平面空間坐標(biāo)的引入。
由于VPOS和SV_POSITION無(wú)法在同一個(gè)v2f結(jié)構(gòu)中存在,所以我們必須刪去原有的v2f中的SV_POSITION,并使之在頂點(diǎn)著色器的參數(shù)中通過(guò)out語(yǔ)義單獨(dú)輸出。
struct v2f
{
float2 uv : TEXCOORD0;
//......
// float4 vertex : SV_POSITION;
};
v2f vert (appdata_tan v, out float4 vertex : SV_POSITION)
{
v2f o;
vertex = UnityObjectToClipPos(v.vertex);
//.......
return o;
}
fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
{
//....
}
同樣的,我們也可以直接在進(jìn)行計(jì)算,那樣就無(wú)需使用VPOS,也不需要?jiǎng)h去v2f結(jié)構(gòu)體中的SV_POSITION定義。
v2f vert (appdata_tan v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.screenpos = ComputeScreenPos(o.vertex);
//.......
}
2.1 獲取水下片元到水面的距離
邏輯不難,即是通過(guò)水底的片元深度-水面的片元深度,求解對(duì)應(yīng)片元水體的厚度。
float3 ColorBelowWater (float4 screenPos)
{
float2 uv = screenPos.xy / screenPos.w;
float backgroundDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv));
float surfaceDepth = UNITY_Z_0_FAR_FROM_CLIPSPACE(screenPos.z);
float depthDifference = backgroundDepth - surfaceDepth;
//除以二十,拉開(kāi)層次差別,這個(gè)20的常量,我們可以理解為最大深度
//所有常量,最好都根據(jù)實(shí)際搭的場(chǎng)景深度,進(jìn)行靈活調(diào)整
return depthDifference/20;
}
注意這里片元著色器的返回值換成了純ColorBelowWater() 的結(jié)果。
若此時(shí)我們得到了黑白顛倒的結(jié)果,則可能是深度貼圖的v坐標(biāo)是從上到下計(jì)算的。對(duì)這種情況,我們需要對(duì)v維度的uv進(jìn)行取反。
//in xxxx.cginc
flaot4 _CameraDepthTexture_TexelSize;
float3 ColorBelowWater (float4 screenPos)
{
float2 uv = screenPos.xy / screenPos.w;
#if UNITY_UV_STARTS_AT_TOP
if (_CameraDepthTexture_TexelSize.y < 0) {
uv.y = 1 - uv.y;
}
#endif
//.........
}
2.2 獲取水底渲染幀緩沖
解決了深度信息的計(jì)算,新的問(wèn)題又來(lái)了,我們直接把深度信息乘算已有的結(jié)果的話,無(wú)法正確反映水下的顏色信息。
//in frag shader
return fixed4((col * _BaseColor + diffuse + specular)* ColorBelowWater(i.screenpos), _AlphaScale);
當(dāng)然我們可以自作聰明地去調(diào)整alpha值來(lái)稀釋黑色效果,但隨之而來(lái)又會(huì)直接破壞深度效果。
所以我們需要將原本的水下渲染的顏色結(jié)果,和深度結(jié)算的結(jié)果進(jìn)行差值混合。
因?yàn)樵镜乃wshader計(jì)算的只有水面的顏色結(jié)果,所以我們鐵定是不可能在單個(gè)pass里面完成混合了。
我們單獨(dú)增加一個(gè)GrabPass ,提前存儲(chǔ)其他物體渲染的結(jié)果。由于透明物體渲染順序本身就在非透明物體后,如果想面向非透明物體獲取GrabPass,要注意渲染順序問(wèn)題。
根據(jù)unity文檔的描述,grabpass只能抓取幀緩沖信息,擁有兩種調(diào)用方法。在不提供目標(biāo)貼圖時(shí),結(jié)果會(huì)被存儲(chǔ)到_GrabTexture;而用戶需要指定grabpass輸出的暫存貼圖時(shí),需要通過(guò)雙引號(hào)給出。
SubShader
{
Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
LOD 100
//增加一個(gè)GrabPass,將背景物體渲染的結(jié)果預(yù)先存儲(chǔ)到_WaterBackground中,供后續(xù)顏色插值混合使用
GrabPass {"_WaterBackground"}
Pass
{
//水體渲染pass
}
}
//in xxx.cginc
float3 ColorBelowWater (float4 screenPos)
{
float2 uv = screenPos.xy / screenPos.w;
#if UNITY_UV_STARTS_AT_TOP
if (_CameraDepthTexture_TexelSize.y < 0) {
uv.y = 1 - uv.y;
}
#endif
float3 backgroundColor = tex2D(_WaterBackground, uv).rgb;
return backgroundColor;
}
可以看到在滿alpha的情況下,也有了透視的效果。
2.3 完成背景渲染和水深的插值混合
在屬性中新增兩個(gè)霧效相關(guān)的參數(shù):
_WaterFogColor ("Water Fog Color", Color) = (0, 0, 0, 0)
_WaterFogDensity ("Water Fog Density", Range(0, 2)) = 0.1
根據(jù)水底顏色(霧顏色),背景顏色進(jìn)行差值混合,插值因子是霧濃度和深度的結(jié)合。
//in xxx.cginc
// update ColorBelowWater( )
float3 backgroundColor = tex2D(_WaterBackground, uv).rgb;
float fogFactor = exp2(-_WaterFogDensity * depthDifference);
return lerp(_WaterFogColor, backgroundColor, fogFactor);
現(xiàn)在,我們可以通過(guò)_WaterFogDensity 來(lái)控制水的散射效果,實(shí)現(xiàn)水體的深度差別。
然后對(duì)alphascale值做調(diào)整,顯然現(xiàn)在我們不需要一個(gè)控制輸出顏色透明度的參數(shù),而是需要一個(gè)控制水體是否為透明深度渲染的參數(shù)。
原本的alphascale主要用于控制片元著色器顏色計(jì)算結(jié)果的透明度。
現(xiàn)在我們將其調(diào)整,讓其影響fogFactor最終的計(jì)算結(jié)果,并使片元著色器的返回的w值固定為1
//in .cginc ColorBelowWater
return lerp(_WaterFogColor, backgroundColor, fogFactor * _AlphaScale);
與調(diào)整_WaterFogDensity 的效果不同,調(diào)節(jié)_AlphaScale主要影響是否進(jìn)行深度混合效果的計(jì)算。
3.實(shí)現(xiàn)水下物體的扭曲
有生活經(jīng)驗(yàn)的朋友們都知道,水下的物體在有水波時(shí),會(huì)出現(xiàn)扭曲現(xiàn)象,參考下圖中水下魚類的邊緣,隨著水波出現(xiàn)了一定程度的扭曲。
實(shí)現(xiàn)的邏輯不復(fù)雜,即是讓水下的部分的采樣uv,沿水波的法線方向(x,z方向)做偏移即可。
//額外新增了參數(shù)_RefractionStrength,用于控制采樣水下顏色結(jié)果的uv的偏移程度
float3 ColorBelowWater (float4 screenPos, float3 worldNormal)
{
float2 uvoffset = worldNormal.xz * _RefractionStrength;
float2 uv = (screenPos.xy + uvoffset) / screenPos.w;
//.........
}
水下扭曲現(xiàn)象從無(wú)到有的對(duì)比
3.1 修正水上物體的誤偏移
看上面的代碼就知道,由于uv偏移是全局性的計(jì)算,所以會(huì)導(dǎo)致很多明明沒(méi)有位于水下的物體,其對(duì)應(yīng)的水面也產(chǎn)生了扭曲顏色的情況。
修正的方法很簡(jiǎn)單,就是我們經(jīng)判斷后,只對(duì)水下的片元做uv偏移即可,對(duì)于水上的uv,則沿用原本的uv,并注意需要重算depthDifference。
float3 ColorBelowWater (float4 screenPos, float3 worldNormal)
{
//......新增一個(gè)originUV,用于存儲(chǔ)偏移前的uv
if(depthDifference < 0)
{
uv = originUV;
#if UNITY_UV_STARTS_AT_TOP
if (_CameraDepthTexture_TexelSize.y < 0) {
uv.y = 1 - uv.y;
}
#endif
//使用偏移前的uv采樣顏色緩沖,會(huì)導(dǎo)致偏移后的uv采樣的深度差,與顏色不匹配
//這里同時(shí)重采樣backgroundDepth,再算一次depthDifference
backgroundDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv));
depthDifference = backgroundDepth - surfaceDepth;
}
float3 backgroundColor = tex2D(_WaterBackground, uv).rgb;
float fogFactor = exp2(-_WaterFogDensity * depthDifference);
return lerp(_WaterFogColor, backgroundColor, fogFactor * _AlphaScale);
}
4.實(shí)現(xiàn)水面波動(dòng)
調(diào)整好水下折射效果后,我們?nèi)匀挥X(jué)得水面的效果明明如此的波濤洶涌,但是水平面仍然明鏡止水般鎮(zhèn)定,顯然不太符合常識(shí)。
最后在前面的渲染基礎(chǔ)上,我們?cè)陧旤c(diǎn)著色器內(nèi)對(duì)flowmap進(jìn)行采樣,求解其向量長(zhǎng)度,并用于太高vertex的位置(y方向)。
當(dāng)然,這里使用的是100x100的plane,此外,在頂點(diǎn)著色器無(wú)法使用tex2D進(jìn)行貼圖采樣,我們需要使用tex2Dlod來(lái)采樣lowmap,并提供四位浮點(diǎn)數(shù)的uv。
我們給uv的第三,第四位置為0。
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-839835.html
v2f vert (appdata_tan v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
float2 flowVec;
flowVec = tex2Dlod(_FlowMap, float4(o.uv + _Time.y * _Speed, 0.0, 0.0)).rg;
flowVec = flowVec * 2 -1;
o.vertex = UnityObjectToClipPos(v.vertex + float3(0.0, length(flowVec) * _HeightScale, 0.0));
o.screenpos = ComputeScreenPos(o.vertex);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
float3 worldBiTangent = cross(worldNormal, worldTangent) * v.tangent.w;
o.t2w_0 = float4(worldTangent.x,worldBiTangent.x,worldNormal.x, worldPos.x);
o.t2w_1 = float4(worldTangent.y,worldBiTangent.y,worldNormal.y, worldPos.y);
o.t2w_2 = float4(worldTangent.z,worldBiTangent.z,worldNormal.z, worldPos.z);
return o;
}
這樣水體與浸沒(méi)水體的物體邊緣的交互會(huì)隨著時(shí)間產(chǎn)生一定波動(dòng),能夠顯得更加真實(shí)一些。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-839835.html
到了這里,關(guān)于【unity shader】水體渲染基礎(chǔ)-水下透視效果的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!