实现思路
今天来写一个肥皂泡的shader,虽然最后出来的结果不太像。。但是还是简单讲一下思路好了。
一般写shader的话先从现实生活找找参考,肥皂泡的话首先形状不是标准的球形,而且在移动的过程中外形会改变,因此我们需要写一个顶点偏移函数。
接着就是肥皂泡表面上的薄膜干涉效果,使得肥皂泡表面表现出来就是花花绿绿的。这种光学效果要真的靠计算光线去模拟的话不太现实,因此就用一张渐变贴图加上一张噪声图来模拟。
然后就是很常见的rim效果,这个只用计算出来之后叠加上去就可以了。
最后就是反射和折射,反射的话用表面着色器自带的就可以了,折射的话用grabpass加上一个基于法线的偏移就可以了。
大致就是这些效果叠加起来,理清楚之后就可以开始写了。
代码实现
顶点位移函数使用的是顶点的y坐标加上时间变量来计算,另外放在三角函数里保证了是一个周期效果,其实这个偏移函数也不是固定的,只要自己觉得看上去像是那么回事就可以了。另外记得在表面着色器的顶点偏移函数里如果要传递值,需要调用UNITY_INITIALIZE_OUTPUT宏来初始化结构体。
void vertexDataFunc(inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);
float3 value = (cos(5.0 * v.vertex.y + _Time.y) * 0.015 + sin(5.0 * v.vertex.y + _Time.y) * 0.005).xxx;
v.vertex.xyz += value;
}
接下来把rim效果加上,对视角方向和顶点法线方向做点积,然后用幂函数改变rim值的衰减曲线
float3 worldViewDir = normalize(UnityWorldSpaceViewDir(IN.worldPos));
float rimValue = 1 - pow(dot(worldViewDir, IN.worldNormal), 2);
肥皂泡的颜色用了两张贴图,一张黑白噪声贴图一张渐变颜色贴图,首先用主uv采样噪声图得到一个亮度值,然后用这个亮度值重新对渐变颜色图进行采样,同时加上定义的偏移值_UVOffset以及乘上缩放值_UVScale,就可以得到一种花花绿绿的效果。
使用的贴图
uv映射
//重新映射uv
fixed2 newUV = tex2D(_MainTex, (IN.uv_MainTex + _SinTime.x * fixed2(1, 1))* _UVScale).rr;
fixed4 c = tex2D(_ColorTex, saturate(newUV+fixed2(_UVOffset,_UVOffset)) );
反射的话只要用表面着色器就自带了反射效果。
折射的话首先设置该shader为透明队列
Tags
{
"RenderType" = "Transparent" "Queue" = "Transparent"
}
接着用grabpass抓取屏幕图像
GrabPass
{
"_RefractionTex"
}
计算折射的话首先在表面着色器里声明screenPos变量获取顶点的屏幕坐标,此坐标是裁剪空间下的,因此除以w获得[0,1]之间的屏幕坐标,正好用来采样折射贴图,然后还是根据法线方向对折射贴图采样坐标来进行偏移模拟折射的扭曲效果
fixed3 bump = UnpackNormal(tex2D(_NormalTex, IN.uv_MainTex.xy)).rgb;
bump.xy *= _NormalScale;
bump = normalize(bump);
IN.screenPos.xyz /= IN.screenPos.w;
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
fixed3 refrCol = tex2D(_RefractionTex, IN.screenPos.xy + offset).rgb;
然后调整参数就可以得到如下效果,其实看上去还是有点怪怪的,可能还是两张贴图选的不好,这个颜色有点对不上。
我们在后面放了一块石头,可以看到有一定的折射效果,同时也可以看到有一定的反射天空盒的效果。
效果2
参数面板
完整代码
Shader "LX/soapBubble"
{
Properties
{
_ColorTex ("ColorTex", 2D) = "white" {}
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Alpha ("Alpha", float) = 0.5
_RimColor ("RimColor", color) = (1,1,1,1)
_NormalTex ("NormalTex", 2D) = "white"
_NormalScale ("NormalScale", float) = 1
_Distortion("Distortion",float) = 1
_Smoothness("Smoothness", float) = 0.8
_Specular("Specular", float) = 0.5
_UVScale("UVScale", float) = 4
_UVOffset("UVOffset", float) = 0
_RimPow("RimPow", float) = 2
}
SubShader
{
Tags
{
"RenderType" = "Transparent" "Queue" = "Transparent"
}
LOD 200
GrabPass
{
"_RefractionTex"
}
Cull Back
CGPROGRAM
#pragma surface surf StandardSpecular fullforwardshadows vertex:vertexDataFunc keepalpha
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
float3 worldNormal;
INTERNAL_DATA
float3 worldPos;
float4 screenPos;
};
float _Specular;
sampler2D _ColorTex;
float _Alpha;
fixed4 _RimColor;
sampler2D _NormalTex;
float _NormalScale;
float _Distortion;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;
float _Smoothness;
float _UVScale;
float _RimPow;
float _UVOffset;
void vertexDataFunc(inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);
float3 value = (cos(5.0 * v.vertex.y + _Time.y) * 0.015 + sin(5.0 * v.vertex.y + _Time.y) * 0.005).xxx;
v.vertex.xyz += value;
}
void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
//rim
float3 worldViewDir = normalize(UnityWorldSpaceViewDir(IN.worldPos));
float rimValue = 1 - pow(dot(worldViewDir, IN.worldNormal), _RimPow);
//重新映射uv
fixed2 newUV = tex2D(_MainTex, (IN.uv_MainTex + _SinTime.x * fixed2(1, 1)) * _UVScale).rr;
fixed4 c = tex2D(_ColorTex, saturate(newUV + fixed2(_UVOffset, _UVOffset)));
//折射
fixed3 bump = UnpackNormal(tex2D(_NormalTex, IN.uv_MainTex.xy)).rgb;
bump.xy *= _NormalScale;
bump = normalize(bump);
IN.screenPos.xyz /= IN.screenPos.w;
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
fixed3 refrCol = tex2D(_RefractionTex, IN.screenPos.xy + offset).rgb;
o.Albedo = (c.rgb * rimValue + refrCol * (1 - rimValue)) * _Alpha;
o.Emission = rimValue * _RimColor * _Alpha;
o.Smoothness = _Smoothness;
o.Specular = _Specular;
}
ENDCG
}
FallBack "Diffuse"
}
另外代码也传到github仓库里了,大家也可以关注一下哦~
我的github