Shader——LiquidBottle(液体瓶)
参考: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
}
}
}
效果: