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
        }
    }
}

效果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM