序·薄膜干涉
前几天读了 @中国科普博览 @Luyao Zou 的一篇文章,里面讲了肥皂泡薄膜干涉的原理,并给出了定量测量的颜色图
觉得十分有趣,因此才起了用Shader实现一下肥皂泡的想法
为什么阳光下的泡沫是彩色的?www.zhihu.com一般渲染中只涉及几何光学,不会涉及干涉,但有定量测量结果后,我们可以只通过角度与薄膜厚度映射相关的色彩,从而模拟出干涉效果
项目下载
感兴趣的小伙伴可以在Github上下载这个小项目:
TYJia/Bubblegithub.com肥皂泡的属性分析
除了绚丽多彩,肥皂泡还有一些其他的属性,也是渲染的关键,仔细观察可以发现如下现象:
1. 菲涅尔现象
肥皂泡的菲涅尔现象是十分明显的,当视线与泡泡表面法线方向相同、相反时,反射率较低;当视线与泡泡表面法线垂直时,反射率较高
2. 镜面反射与光透明度
泡泡的表面都是极为光滑的,所以是镜面反射;不过在不反射光线或光线较弱时,它是全透明的,反射光线较强时,才会被我们看见
所以在进行透明度计算时,也要同时考虑反射明度
3. 双面反射(中心对称反射)
与水晶球不同,泡泡的内外表面都是参与反射的,所以一个球形泡泡会形成中心对称的反射结果
4. 流动的表面
泡泡的表面是液体,再加上一些力的作用,所以会慢慢流动,也因此会产生表面厚度的变化
源码
Shader "TJia/BubbleShader" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_MetalRef ("MetalRef", Range(0,1)) = 0
_MainTex("Main Tex", 2D) = "white" {}
_BumpMap ("Bumpmap (RGB)", 2D) = "bump" {}
_BumpValue ("BumpValue", Range(0,3)) = 1
_LightModle ("LightModleDiffuse (RGB)", 2D) = "white" {}
_LightModleValue("LightModleValue", Range(0,3)) = 1
_LightModleSpec ("LightModleSpec (RGB)", 2D) = "black" {}
_SpecValue ("SpecValue", Range(0,4)) = 0
_Bubble ("Bubble (RGB)", 2D) = "white" {}
_BubbleNoise("BubbleNoise",2D) = "white" {}
_BubblelValue ("Bubble Value", Range(0,2)) = 1
}
Subshader {
Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
Fog { Color [_AddFog] }
Pass {
Name "BASE"
Tags { "LightMode" = "Always" }
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_fog_exp2
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
//#define _Shadow 1
//#define _ShadowRange 0.25
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 TtoV0 : TEXCOORD1;
float3 TtoV1 : TEXCOORD2;
float3 visual : TEXCORRD3;
float3 normal : NORMAL;
};
uniform float4 _BumpMap_ST, _DetailGreyTex_ST, _DetailBumpTex_ST;
uniform float4 _MainTex_ST;
v2f vert (appdata_tan v)
{
v2f o;
o.pos = UnityObjectToClipPos (v.vertex+_SinTime.y*v.normal*0.1);
o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpMap);
TANGENT_SPACE_ROTATION;
o.TtoV0 = normalize(mul(rotation, UNITY_MATRIX_IT_MV[0].xyz));
o.TtoV1 = normalize(mul(rotation, UNITY_MATRIX_IT_MV[1].xyz));
o.visual = normalize(mul(rotation, -ObjSpaceViewDir(v.vertex)));
return o;
}
uniform fixed4 _Color, _ShadowColor;
uniform sampler2D _BumpMap, _Bubble, _BubbleNoise;
uniform sampler2D _LightModle;
uniform sampler2D _MainTex;
uniform sampler2D _LightModleSpec;
uniform fixed _SpecValue;
uniform fixed _BumpValue;
uniform fixed _LightModleValue, _BubblelValue;
uniform fixed _MetalRef;
float3 lum(fixed3 c)
{
return c.r * 0.2 + c.g * 0.7 + c.b * 0.1;
}
float4 frag (v2f i) : COLOR
{
fixed4 c = tex2D(_MainTex, i.uv.xy);
float3 normal = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
normal.xy *= _BumpValue;
normal = normalize(normal);
float v_n_angle = saturate(acos(dot(-i.visual,normal))*2/3.14);
float BubbleR = tex2D(_BubbleNoise, i.uv.xy+float2(0,_Time.x)).r;
float BubbleG = tex2D(_BubbleNoise, i.uv.xy+float2(BubbleR*0.5,_Time.x)*0.9+0.05*_SinTime.x).g;
float BubbleB = tex2D(_BubbleNoise, i.uv.xy+float2(BubbleG*0.5,_Time.x)*1.1-0.05*_SinTime.y).b;
float BubbleNoise = saturate(BubbleR * BubbleG * BubbleB * 4);
fixed4 Bubble = tex2D(_Bubble, fixed2(BubbleNoise, v_n_angle));
float3 correctiveNormal = normalize(reflect(i.visual, normal));
normal = normalize(lerp(normal, correctiveNormal, _MetalRef));
half2 vn;
vn.x = dot(i.TtoV0, normal);
vn.y = dot(i.TtoV1, normal);
fixed4 LightModleLookup = saturate(tex2D(_LightModle, vn * 0.495 + 0.505) * _Color * _LightModleValue);
//LightModleLookup.a = 1;
fixed2x2 rotSpec =
{
-1, 0,
0, -1
};
half2 vnsp = mul(rotSpec, vn);
fixed4 LightModleSpec = tex2D(_LightModleSpec, vn*0.495 + 0.505);
fixed4 LightModleSpec2 = tex2D(_LightModleSpec, vnsp*0.495 + 0.505);
LightModleSpec.rgb = lum(saturate((LightModleSpec + LightModleSpec2)*0.5).rgb) * (LightModleSpec.rgb+LightModleSpec2.rgb)*0.5;
LightModleSpec.a = 1;
fixed4 diff = c * LightModleLookup ;
fixed4 finalColor = clamp(diff + LightModleSpec * _SpecValue, 0, 1) * lerp(fixed4(1, 1, 1, 1), Bubble * 1.5, _BubblelValue);
float fresnel = saturate(pow(v_n_angle,1.6));
finalColor.a = saturate(fresnel * 0.2 + 0.8 * (fresnel+0.1) * lum(LightModleSpec.rgb * _SpecValue + 0.1) + 0.05);
return finalColor;
}
ENDCG
}
}
Fallback "VertexLit"
}
实现方法注释
1. 用以模拟泡泡表面液体流动的厚度噪声图
为了模拟肥皂泡厚度流动的变化,这里使用了一张噪声图,RGB通道都是PS随机生成的,厚度计算方法如下:
float BubbleR = tex2D(_BubbleNoise, i.uv.xy+float2(0,_Time.x)).r;
float BubbleG = tex2D(_BubbleNoise, i.uv.xy+float2(BubbleR*0.5,_Time.x)*0.9+0.05*_SinTime.x).g;
float BubbleB = tex2D(_BubbleNoise, i.uv.xy+float2(BubbleG*0.5,_Time.x)*1.1-0.05*_SinTime.y).b;
float BubbleNoise = saturate(BubbleR * BubbleG * BubbleB * 4);
2. 中心对称的反射
这里使用了旋转矩阵,将反射结果转了180°,并进行了如下的混合计算:
LightModleSpec.rgb = lum(saturate((LightModleSpec + LightModleSpec2)*0.5).rgb) * (LightModleSpec.rgb+LightModleSpec2.rgb)*0.5;
3. 基于菲涅尔和反光明度的透明度计算
菲涅尔通过视线、法线夹角得出
这里使用acos是为了薄膜干涉映射,在菲涅尔模拟中不必要
最终透明度通过菲涅尔和光照强度混合计算获得:
float v_n_angle = saturate(acos(dot(-i.visual,normal))*2/3.14);
float fresnel = saturate(pow(v_n_angle,1.6));
finalColor.a = saturate(fresnel * 0.2 + 0.8 * (fresnel+0.1) * lum(LightModleSpec.rgb * _SpecValue + 0.1) + 0.05);
最终结果如下:
拓展
改改采样图,调调混合模式,就可以当流动的防护罩来用啦~