Unity+FlowMap+薄膜干涉的泡泡渲染效果
首先看效果
https://www。zhihu。com/video/1489957292074704897
效果我覺得還是挺好的。。不過考慮到遊戲中應用場景還是太少了 ,所以作用不大。
看了看知乎上幾位博主的泡泡實現,總感覺要麼就是太高端了,要麼就是效果不好,最近剛好看到FlowMap。覺得拿來做泡泡正合適。
存在的問題
從結果大致一看 的確是個泡泡了 而且是有流動效果的泡泡,
跟真實的圖片對比一下,特別小的Noise還是沒有,可以考慮多采樣個NOise圖 或者是在繪製FlowMap的時候多講究講究
實現思路
薄膜干涉部分
使用FlowMap 取樣水面法線貼圖,作為泡泡表面法線 計算模擬法線,獲得干擾過後的入射角。
並且取FlowMap的一個值 作為頁面厚度的干擾值。
組合作為UV值取樣下面的貼圖。
這麼看來確實還真挺物理的,利用泡泡表面流動取樣真實計算出來的貼圖。
鏡面反射光部分
因為泡泡是雙層的,所以會生成兩個鏡面反轉的像。
只需要正常使用 ReflectDir和倒轉ReflectDir ,取樣兩次CubeMap後疊加即可實現。
有了思路之後,這個Shader我覺得最難的其實是中間的調參的過程,如果利用手頭的素材讓效果更好才是難點
正式開始計算
需要用到的貼圖資源
薄膜干涉貼圖、FLowmap、反射的CubeMap、水面的Normal
薄膜干涉貼圖下載剪下下就好
FlowMap 按照百人計劃中的使用方法 繪製出來,
Ball模型是按縱向展開的,繪製的時候特意保證從上往下流動的感覺,因為泡泡上面的水也受到重力的影響向下流。所以我就大概按照這個趨勢畫出來。
反射的QubeMap用個探針Bake出來就好,
Normal就拿隔壁Demo的水面效果,用起來感覺不錯。
計算薄膜干涉
第一步 計算取樣NormalMap的UV
因為我們要使用FLowMap 取樣Normal
同時為了避免FlowMap太重複 跟個岩漿的感覺一樣,所以也要給FlowMap加上Time 讓FlowMap的UV也有所變化。
實現流程可以參考FlowMap的使用方法
(這裡的程式碼可能不能複製過去直接執行,看個大概 最後有完整的Shader)
//取樣 FLowMap
float2
flowUV
=
(
i
。
uv
。
xy
+
_Time
*
0。1
*
_FlowUVDir
)*
_FlowTex_ST
。
xy
;
float3
flowDir
=
tex2D
(
_FlowTex
,
flowUV
)
*
2
-
1
;
flowDir
*=
_FlowSpeed
;
//控制時間週期
float
phase0
=
frac
(
_Time
*
0。1
*
_TimeSpeed
);
float
phase1
=
frac
(
_Time
*
0。1
*
_TimeSpeed
+
0。5
);
float4
tex0
=
tex2D
(
_NormalTex
,
i
。
uv
。
xy
-
flowDir
。
xy
*
phase0
);
float4
tex1
=
tex2D
(
_NormalTex
,
i
。
uv
。
xy
-
flowDir
。
xy
*
phase1
);
float
flowlerp
=
abs
((
0。5
-
phase0
)
/
0。5
);
float4
packedNormal
=
lerp
(
tex0
,
tex1
,
flowlerp
)
;
float3
normalTS
=
UnpackNormal
(
packedNormal
);
計算後 獲得使用FlowMap取樣出來的NormalMap
第二步,把取樣出來的NormalMap用TBN 套用到模型表面並計算nDotV
float3
tDirWS
:
TEXCOORD1
;
float3
nDirWS
:
TEXCOORD2
;
float3
bDirWS
:
TEXCOORD3
;
o
。
tDirWS
=
normalize
(
mul
(
unity_ObjectToWorld
,
float4
(
v
。
tangent
。
xyz
,
0。0
)
)。
xyz
);
o
。
nDirWS
=
UnityObjectToWorldNormal
(
v
。
normal
);
o
。
bDirWS
=
normalize
(
cross
(
o
。
nDirWS
,
o
。
tDirWS
)
*
v
。
tangent
。
w
);
//TBN
half3x3
TBN
=
float3x3
(
i
。
tDirWS
,
i
。
bDirWS
,
i
。
nDirWS
);
//計算Ndir
half3
nDirWS
=
normalize
(
mul
(
normalTS
,
TBN
));
float
nDotv
=
saturate
(
dot
(
nDirWS
,
vDirWS
));
讓我們看下帶有FlowMap的菲尼爾效果
https://www。zhihu。com/video/1490014719922991106
如果使用這個直接作為取樣薄膜干涉圖的UV 就會發現 會有一箇中心向周圍的整體顏色變化趨勢。
看真實泡泡圖的話,其實這種感覺沒那麼強烈。
所以下一步
就是使用 取樣出來的nDirWS dot vDirWS 減去 原生的模型的 nDirWS dot vDirWS
相當於減輕了菲尼爾效果 只保留了部分取樣的Noise效果。
(這裡可能不需要這麼麻煩,可能有其他更好的辦法 請大佬幫我指出)
//減弱菲尼爾效果 對泡泡影響
float
nDotv
=
saturate
(
dot
(
nDirWS
,
vDirWS
));
float
nDotv2
=
saturate
(
dot
(
i
。
nDirWS
,
vDirWS
));
float
RampYAxis
=
saturate
((
nDotv
-
nDotv2
*
0。95
)+
0。4
-
wave
*
0。8
);
不帶Wave 光使用nDotV相減的結果
可能有人注意到上面的這個Wave 了
Wave是我自己根據FlowMap取樣出來的一個數值,感覺最後結果中少了點大塊的顏色,
所以利用了上面FlowMap計算出來的過程量 ,繪製出個Wave波,參與運算。 讓效果好點
//引入波控制
float wave = lerp(flowDir。xy * phase0,flowDir。xy * phase1,flowlerp);
單wave的效果
把Wave納入計算後 得到的效果
將取樣出來的Wave值 作為取樣薄膜干涉圖的Y軸
取樣圖的X軸,並採樣圖片
X軸就利用NormalMap的X 作為模擬的厚度,並加入自定義的偏移量和權重來控制
取樣出來的顏色也乘以一個亮度方便控制。
float
RampXAxis
=
_RampXAxisOffset
+
packedNormal
。
r
*
_RampXAxisNoiseStrength
;
float2
rampTexUV
=
float2
(
RampXAxis
,
RampYAxis
);
float3
rampColor
=
tex2D
(
_RampTex
,
rampTexUV
)*
_ColorReflectIntensity
;
好像有點詭異
計算反射影象
因為泡泡是反射兩個顛倒的Cube
所以我們需要
1 計算反射方向
2計算反的反射方向
3利用這兩個方向分別取樣Cube
4將這兩個取樣出來的結果合併到一起
過程都很簡單 直接上程式碼
o
。
posWS
=
mul
(
unity_ObjectToWorld
,
v
。
vertex
);
o
。
rDirWS
=
reflect
(-
UnityWorldSpaceViewDir
(
o
。
posWS
),
o
。
nDirWS
);
//反射效果
float3
NegaReflectDir
=
float3
(-
i
。
rDirWS
。
x
,-
i
。
rDirWS
。
y
,
i
。
rDirWS
。
z
);
//計算兩個反射 然後混合
float3
reflectCol1
=
texCUBE
(
_EnvCube
,
i
。
rDirWS
)*
_ReflectAmount
;
float3
reflectCol2
=
texCUBE
(
_EnvCube
,
NegaReflectDir
)*
_ReflectAmount
;
float3
reflectCol
=
reflectCol1
+
reflectCol2
;
一樣加入了一些引數控制反射的亮度
效果還不錯
混合影象
其實我覺得這個最難的就是混合影象 調參
有幾個要點,
薄膜干涉計算出來的顏色 受到反射影象的明度影響很大,
明度高的地方反射出來的顏色也很明顯
(透明度也是這樣,越亮越不透明)
真實的影象
所以
首要,就是先計算反射影象的明度 然後作為權重來控制反射顏色
其次,反射影象的對比度也比我計算的結果更強一點 ,需要 平方一下增加下對比度
還有,泡泡的邊緣效果也強烈一些 可以計算出 fresnel 控制下強度。
大概思路就是這樣 剩下全是自己控制大小調參的過程了
//菲尼爾效果
float
fresnel
=
pow
(
1
-
nDotv2
,
_FresnelPow
);
//開始混合
//獲取明度
float
reflectLumin
=
dot
(
reflectCol
,
float3
(
0。22
,
0。707
,
0。071
));
//Ramp顏色受反射影象明度影響很大
float3
finalRampCol
=
rampColor
*(
pow
(
reflectLumin
,
1。5
)+
0。05
);
finalRampCol
=
pow
(
finalRampCol
,
1。4
);
float3
finalCol
=
finalRampCol
+
fresnel
*
_FresnelIntenisty
*
finalRampCol
*
reflectLumin
+
reflectCol
*
reflectCol
;
//透明度受反射影象明度 邊緣厚度影響
float
finalAlpha
=
_BubbleAlpha
*(
reflectLumin
*
0。5
+
0。5
)+
fresnel
*
0。2
;
//輸出
return
float4
(
finalCol
,
finalAlpha
);
所有Shader程式碼
Shader
“Unlit/Bubble”
{
Properties
{
_NormalTex
(
“NormalTex”
,
2D
)
=
“bump”
{}
_FlowTex
(
“_FlowTex”
,
2D
)
=
“white”
{}
_TimeSpeed
(
“_TimeSpeed”
,
float
)
=
1
_FlowSpeed
(
“_FlowSpeed”
,
float
)
=
1
_FlowUVDir
(
“flow UV Dir”
,
vector
)
=
(
1
,
1
,
1
,
1
)
_RampTex
(
“RampTex”
,
2D
)
=
“white”
{}
_RampXAxisOffset
(
“X Axis_Offset”
,
Range
(
0
,
1
))=
0。2
_RampXAxisNoiseStrength
(
“Ramp Tex X Axis Noise Strength”
,
float
)
=
2
_ColorReflectIntensity
(
“薄膜干涉亮度”
,
Range
(
0
,
20
))=
2
_EnvCube
(
“Reflection Cubemap”
,
Cube
)=
“_Skybox”
{}
_ReflectAmount
(
“反射的強度”
,
Range
(
0
,
1
))=
0。5
_BubbleAlpha
(
“泡泡透明度”
,
Range
(
0
,
2
))
=
1
_FresnelPow
(
“菲尼爾 對比度”
,
float
)
=
3
_FresnelIntenisty
(
“菲尼爾 亮度”
,
float
)=
0。2
}
SubShader
{
Tags
{
“RenderType”
=
“Transparent”
}
LOD
100
Pass
{
Tags
{
“LightMode”
=
“ForwardBase”
}
ZWrite
off
Blend
SrcAlpha
OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#
include
“UnityCG。cginc”
struct
appdata
{
float4
vertex
:
POSITION
;
float2
uv
:
TEXCOORD0
;
float4
normal
:
NORMAL
;
float4
tangent
:
TANGENT
;
};
struct
v2f
{
float4
uv
:
TEXCOORD0
;
float4
vertex
:
SV_POSITION
;
float3
tDirWS
:
TEXCOORD1
;
float3
nDirWS
:
TEXCOORD2
;
float3
bDirWS
:
TEXCOORD3
;
float4
posWS
:
TEXCOORD4
;
float3
rDirWS
:
TEXCOORD5
;
};
sampler2D
_NormalTex
;
sampler2D
_FlowTex
;
float4
_FlowTex_ST
;
sampler2D
_RampTex
;
float
_FlowSpeed
;
float
_TimeSpeed
;
float2
_FlowUVDir
;
float
_RampXAxisOffset
;
float
_RampXAxisNoiseStrength
;
//反射
samplerCUBE
_EnvCube
;
float
_ReflectAmount
;
float
_BubbleAlpha
;
float
_ColorReflectIntensity
;
float
_FresnelPow
;
float
_FresnelIntenisty
;
v2f
vert
(
appdata
v
)
{
v2f
o
;
o
。
uv
。
xy
=
v
。
uv
;
o
。
posWS
=
mul
(
unity_ObjectToWorld
,
v
。
vertex
);
o
。
vertex
=
UnityObjectToClipPos
(
v
。
vertex
);
o
。
tDirWS
=
normalize
(
mul
(
unity_ObjectToWorld
,
float4
(
v
。
tangent
。
xyz
,
0。0
)
)。
xyz
);
o
。
nDirWS
=
UnityObjectToWorldNormal
(
v
。
normal
);
o
。
bDirWS
=
normalize
(
cross
(
o
。
nDirWS
,
o
。
tDirWS
)
*
v
。
tangent
。
w
);
o
。
rDirWS
=
reflect
(-
UnityWorldSpaceViewDir
(
o
。
posWS
),
o
。
nDirWS
);
return
o
;
}
fixed4
frag
(
v2f
i
)
:
SV_Target
{
//TBN
half3x3
TBN
=
float3x3
(
i
。
tDirWS
,
i
。
bDirWS
,
i
。
nDirWS
);
//取樣 FLowMap
float2
flowUV
=
(
i
。
uv
。
xy
+
_Time
*
0。1
*
_FlowUVDir
)*
_FlowTex_ST
。
xy
;
float3
flowDir
=
tex2D
(
_FlowTex
,
flowUV
)
*
2
-
1
;
flowDir
*=
_FlowSpeed
;
//控制時間週期
float
phase0
=
frac
(
_Time
*
0。1
*
_TimeSpeed
);
float
phase1
=
frac
(
_Time
*
0。1
*
_TimeSpeed
+
0。5
);
float4
tex0
=
tex2D
(
_NormalTex
,
i
。
uv
。
xy
-
flowDir
。
xy
*
phase0
);
float4
tex1
=
tex2D
(
_NormalTex
,
i
。
uv
。
xy
-
flowDir
。
xy
*
phase1
);
float
flowlerp
=
abs
((
0。5
-
phase0
)
/
0。5
);
float4
packedNormal
=
lerp
(
tex0
,
tex1
,
flowlerp
)
;
float3
normalTS
=
UnpackNormal
(
packedNormal
);
//計算Ndir
half3
nDirWS
=
normalize
(
mul
(
normalTS
,
TBN
));
half3
vDirWS
=
normalize
(
_WorldSpaceCameraPos
。
xyz
-
i
。
posWS
);
//引入波控制
float
wave
=
lerp
(
flowDir
。
xy
*
phase0
,
flowDir
。
xy
*
phase1
,
flowlerp
);
//減弱菲尼爾效果 對泡泡影響
float
nDotv
=
saturate
(
dot
(
nDirWS
,
vDirWS
));
float
nDotv2
=
saturate
(
dot
(
i
。
nDirWS
,
vDirWS
));
float
RampYAxis
=
saturate
((
nDotv
-
nDotv2
*
0。95
)+
0。4
-
wave
*
0。8
);
float
RampXAxis
=
_RampXAxisOffset
+
packedNormal
。
r
*
_RampXAxisNoiseStrength
;
float2
rampTexUV
=
float2
(
RampXAxis
,
RampYAxis
);
float3
rampColor
=
tex2D
(
_RampTex
,
rampTexUV
)*
_ColorReflectIntensity
;
//反射效果
float3
NegaReflectDir
=
float3
(-
i
。
rDirWS
。
x
,-
i
。
rDirWS
。
y
,
i
。
rDirWS
。
z
);
//計算兩個反射 然後混合
float3
reflectCol1
=
texCUBE
(
_EnvCube
,
i
。
rDirWS
)*
_ReflectAmount
;
float3
reflectCol2
=
texCUBE
(
_EnvCube
,
NegaReflectDir
)*
_ReflectAmount
;
float3
reflectCol
=
reflectCol1
+
reflectCol2
;
//菲尼爾效果
float
fresnel
=
pow
(
1
-
nDotv2
,
_FresnelPow
);
//開始混合
//獲取明度
float
reflectLumin
=
dot
(
reflectCol
,
float3
(
0。22
,
0。707
,
0。071
));
//Ramp顏色受反射影象明度影響很大
float3
finalRampCol
=
rampColor
*(
pow
(
reflectLumin
,
1。5
)+
0。05
);
finalRampCol
=
pow
(
finalRampCol
,
1。4
);
float3
finalCol
=
finalRampCol
+
fresnel
*
_FresnelIntenisty
*
finalRampCol
*
reflectLumin
+
reflectCol
*
reflectCol
;
//透明度受反射影象明度 邊緣厚度影響
float
finalAlpha
=
_BubbleAlpha
*(
reflectLumin
*
0。5
+
0。5
)+
fresnel
*
0。2
;
//輸出
return
float4
(
finalCol
,
finalAlpha
);
}
ENDCG
}
}
}
專案檔案
打包成UnityPackages的形式 上傳到百度雲
連結:
https://
pan。baidu。com/s/1n0ruUr
ebWFTYDuGw87MwBw
提取碼:54dw
——來自百度網盤超級會員V5的分享