參考:Quick Game Art Tips - Unity Liquid Shader
關於這個液體瓶效果,其實網上也有挺多的版本的,但是我看了下貌似還有改進的余地:
1.網上的版本在FillAmount上的取值范圍有點隨意,可能因為他們是在世界空間下算的液體高度,所以覺得直接設個大點的值,然后能大概模擬液體填充效果就好吧,但是其實只要在模型空間算液體高度的話,就不存在這個問題,FillAmount可以取0表示液體瓶是空的,即高度0,取1表示液體瓶是滿的,即液體填滿整個瓶子。
實現的原理其實就是:用腳本取瓶子在x軸或z軸上的移動速度或者在這兩個軸上的旋轉速度,然后把這個速度轉化為一個在x軸或z軸的值,設進Shader里,然后Shader里把這個值和FillAmount相加當作可顯示的最高液體高度,當頂點y軸小於等於這個高度代表時液體,大於這個高度則時在液體之上,不顯示液體。
具體實現的解釋寫在代碼注釋里了,就不再詳細解釋代碼了。
C#腳本控制代碼:
using UnityEngine;
public class LiquidBottleController : MonoBehaviour
{
private Material material = null;
Vector3 lastPos;
Vector3 velocity;
Vector3 lastRot;
Vector3 angularVelocity;
public float MaxWobble = 0.03f;
public float WobbleSpeed = 1f;
public float Recovery = 1f;
float wobbleAmountX;
float wobbleAmountZ;
float wobbleAmountToAddX;
float wobbleAmountToAddZ;
float pulse;
private void Start()
{
material = GetComponent<MeshRenderer>().material;
}
private void Update()
{
wobbleAmountToAddX = Mathf.Lerp(wobbleAmountToAddX, 0, Time.deltaTime);
wobbleAmountToAddZ = Mathf.Lerp(wobbleAmountToAddZ, 0, Time.deltaTime);
pulse = 2 * Mathf.PI * WobbleSpeed;
wobbleAmountX = wobbleAmountToAddX * Mathf.Sin(pulse * Time.time);
wobbleAmountZ = wobbleAmountToAddZ * Mathf.Sin(pulse * Time.time);
material.SetFloat("_WobbleX", wobbleAmountX);
material.SetFloat("_WobbleZ", wobbleAmountZ);
velocity = (lastPos - transform.position) / Time.deltaTime;
angularVelocity = transform.rotation.eulerAngles - lastRot;
wobbleAmountToAddX += Mathf.Clamp((velocity.x + (angularVelocity.z / 0.2f)) * MaxWobble, -MaxWobble, MaxWobble);
wobbleAmountToAddZ += Mathf.Clamp((velocity.z + (angularVelocity.x / 0.2f)) * MaxWobble, -MaxWobble, MaxWobble);
lastPos = transform.position;
lastRot = transform.rotation.eulerAngles;
}
}
Shader "WS_Shader/WS_LiquidBottle"
{
Properties
{
_LiquidColor("Liquid Color", Color) = (1, 1, 1, 1)
_FillAmount("Fill Amount", Range(0.1, 0.9)) = 0.0
[HideInInspector] _WobbleX("Wobble X", Range(-1, 1)) = 0.0
[HideInInspector] _WobbleZ("Wobble Z", Range(-1, 1)) = 0.0
_LiquidTopColor("Liquid Top Color", Color) = (1, 1, 1, 1)
_LiquidFoamColor("Liquid Foam Color", Color) = (1, 1, 1, 1)
_FoamLineWidth("Foam Line Width", Range(0, 0.05)) = 0.0
_LiquidRimColor("Liquid Rim Color", Color) = (1, 1, 1, 1)
_LiquidRimPower("Liquid Rim Power", Range(0, 10)) = 0.0
_LiquidRimIntensity("Liquid Rim Intensity", Range(0, 3)) = 1.0
//Bottle
_BottleThickness("Bottle Thickness", Range(0.01, 0.3)) = 0.01
_BottleSpecularGloss("Bottle Specular Gloss", Range(1, 100)) = 0.0
_BottleColor("Bottle Color", Color) = (1, 1, 1, 1)
_BottleSpecularColor("Bottle Specular Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" "DisableBatching"="True"}
//第一個pass用於渲染液體
Pass
{
//Tags{"RenderType"="Opaque" "Queue"="Geometry"}
AlphaToMask On
//ZWrite On
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 position : SV_POSITION;
float3 viewDir : TEXCOORD0;
float3 normal : TEXCOORD1;
float fillEdge : TEXCOORD2;
};
//返回的float中x是vertex.y, y是旋轉后的x,z是旋轉前的z,w是旋轉后的z
float4 RotateAroundYInDegrees(float4 vertex, float degrees)
{
float alpha = degrees * UNITY_PI / 180;
float sina, cosa;
sincos(alpha, sina, cosa);
float2x2 m = float2x2(cosa, sina, -sina, cosa);
return float4(vertex.yz,mul(m, vertex.xz)).xzyw;
}
float _FillAmount, _WobbleX, _WobbleZ;
float4 _LiquidRimColor, _LiquidFoamColor, _LiquidTopColor, _LiquidColor;
float _LiquidRimPower, _LiquidRimIntensity, _FoamLineWidth;
v2f vert(appdata v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
o.position = UnityObjectToClipPos(v.vertex);
//未加入旋轉y軸,所以先定死360,即不旋轉
float4 rotatedPos = RotateAroundYInDegrees(v.vertex, 360);
//posX和posZ用於決定液體高度,因為膠囊體的網格是對稱的,所以能夠呈現成一邊水面上升,一邊水面下降
float posX = rotatedPos.y;
float posZ = rotatedPos.w;
//頂點高度 - 最高可顯示液體高度,大於0表示超出可顯示高度,小於等於0表示可顯示液體
o.fillEdge = (v.vertex.y / 2 + 0.5) - ((posX * _WobbleX) + (posZ * _WobbleZ) + _FillAmount);
//視線向量和法線向量都用模型空間的
o.viewDir = normalize(ObjSpaceViewDir(v.vertex));
o.normal = v.normal;
return o;
}
fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
{
//邊緣光
float dotProduct = 1 - pow(dot(i.normal, i.viewDir), _LiquidRimPower);
float3 RimResult = _LiquidRimColor * smoothstep(0.5, 1.0, dotProduct) * _LiquidRimIntensity;
//泡沫
float foam = step(i.fillEdge, 0.0) - step(i.fillEdge, (0.0 - _FoamLineWidth));
float4 foamColor = foam * _LiquidFoamColor;
//液體顏色
//step(i.fillEdge, 0.0) - foam 表示如果顯示了泡沫就不顯示液體顏色了
float result = step(i.fillEdge, 0.0) - foam;
float4 resultColor = result * _LiquidColor;
//最后顯示的顏色,如果液體顏色和泡沫顏色都沒有,那最后的顏色值是(0,0,0,0),因為開了AlphaToMask On,所以alpha通道為0時,就不給像素着色了,就相當於是透明的了。
float4 finalResult = resultColor + foamColor;
finalResult.rgb += RimResult;
//水面,直接用背面來顯示水面,正面用於顯示液體
//(foam + result)表示在背面對應的正面有液體或者泡沫時才顯示水面
float4 topColor = _LiquidTopColor * (foam + result);
return facing > 0 ? finalResult : topColor;
}
ENDCG
}
//第二個pass用於渲染瓶子
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 position : SV_POSITION;
float3 worldViewDir : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float3 worldLightDir : TEXCOORD2;
};
float _BottleThickness, _BottleSpecularGloss, _BottleRimPower, _BottleRimIntensity;
float4 _BottleSpecularColor, _BottleColor, _BottleRimColor;
v2f vert(appdata v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
v.vertex.xyz += _BottleThickness * v.normal;
o.position = UnityObjectToClipPos(v.vertex);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldNormal = normalize(UnityObjectToWorldNormal(v.vertex));
o.worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
o.worldViewDir = normalize(_WorldSpaceCameraPos.xyz - worldPos);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//高光反射
float3 halfDir = normalize(i.worldViewDir + i.worldLightDir);
float3 specular = _LightColor0.rgb * _BottleSpecularColor.rgb * (pow(max(0, dot(halfDir, i.worldNormal)), _BottleSpecularGloss));
fixed4 finalColor = _BottleColor + fixed4(specular.rgb, 0);
return finalColor;
}
ENDCG
}
}
}
效果: