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

效果:

posted @ 2020-09-12 15:04  夜里寻星  阅读(1464)  评论(0编辑  收藏  举报