unity模仿瓶子中的液体晃动

Posted 魔域大道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity模仿瓶子中的液体晃动相关的知识,希望对你有一定的参考价值。

在VR游戏《半条命:Alyx》中,有个酒瓶中液体晃动的交互。

 

 

 这里在patreon上有个实现了液体晃动的工程,作者忘了叫啥了,记得的话补回来。这里简单分析一下代码的意思,之前也看过,只是不太理解旋转部分的代码,现在重新复习一下。

 

 

 

液体的shader

Shader "Unlit/SpecialFX/Liquid"
{
    Properties
    {
        _Tint ("Tint", Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
        _FillAmount ("Fill Amount", Range(-10,10)) = 0.0
        [HideInInspector] _WobbleX ("WobbleX", Range(-1,1)) = 0.0
        [HideInInspector] _WobbleZ ("WobbleZ", Range(-1,1)) = 0.0
        _TopColor ("Top Color", Color) = (1,1,1,1)
        _FoamColor ("Foam Line Color", Color) = (1,1,1,1)
        _Rim ("Foam Line Width", Range(0,0.1)) = 0.0    
        _RimColor ("Rim Color", Color) = (1,1,1,1)
        _RimPower ("Rim Power", Range(0,10)) = 0.0
    }
 
    SubShader
    {
        Tags {"Queue"="Geometry"  "DisableBatching" = "True" }
  
        Pass
        {
         Zwrite On
         Cull Off // we want the front and back faces
         AlphaToMask On // transparency

         CGPROGRAM


         #pragma vertex vert
         #pragma fragment frag
         // make fog work
         #pragma multi_compile_fog
           
         #include "UnityCG.cginc"
 
         struct appdata
         {
           float4 vertex : POSITION;
           float2 uv : TEXCOORD0;
           float3 normal : NORMAL;    
         };
 
         struct v2f
         {
            float2 uv : TEXCOORD0;
            UNITY_FOG_COORDS(1)
            float4 vertex : SV_POSITION;
            float3 viewDir : COLOR;
            float3 normal : COLOR2;        
            float fillEdge : TEXCOORD2;
        
         };
 
         sampler2D _MainTex;
         float4 _MainTex_ST;
         float _FillAmount, _WobbleX, _WobbleZ;
         float4 _TopColor, _RimColor, _FoamColor, _Tint;
         float _Rim, _RimPower;
           
         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);                   //构造一个2x2的旋转矩阵
          return float4(vertex.yz , mul(m, vertex.xz)).xzyw ;      //mul(m,vertex.xz))是绕y轴旋转degrees的角度,这里传入的是360度,顶点还是保持在原来的位置
       //return float4(vertex.yz ,  vertex.xz).xzyw ;             //这里为啥要这么写呢?其实return得到的值表示成float4(vertex.y,vertex.x,vertex.z,vertex.z),由于只要float3,w分量可以忽略。这里其实意思是顶点的x与y互换了。参考图3
                                          //原顶点worldPos的值加上这里return的值(worldPosX),顶点就可以在左右的方向摆动,也就是在XY平面旋转,
                                          //之后的worldPosZ等于float3(vertex.x,vertex.z,vertex.y),就是顶点的z和y互换,在YZ平面旋转,即液体前后摆动。
                                          //

                                          //通过这种方式,不管你玻璃杯怎么旋转,左右晃动玻璃杯,液体也会左右摆动;前后晃动的话,液体就会前后摆动,就跟现实中晃动杯子里的水一样。
} v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); // get world position of the vertex float3 worldPos = mul (unity_ObjectToWorld, v.vertex.xyz); // rotate it around XY float3 worldPosX= RotateAroundYInDegrees(float4(worldPos,0),360);         // rotate around ZY float3 worldPosZ = float3 (worldPosX.y, worldPosX.z, worldPosX.x); // combine rotations with worldPos, based on sine wave from script float3 worldPosAdjusted = worldPos + (worldPosX * _WobbleX+worldPosZ * _WobbleZ); //液体原顶点加上在X轴和Z轴上的摆动,这里好奇既然是旋转,为啥液体不是像平常的3d物体那样整个旋转呢。 // how high up the liquid is //其实并没有改变顶点的位置,而是通过计算存储一个值,然后拿到片元中根据y轴的值去剔除,得到最终的颜色值。 o.fillEdge = worldPosAdjusted.y + _FillAmount; o.viewDir = normalize(ObjSpaceViewDir(v.vertex)); o.normal = v.normal; return o; } fixed4 frag (v2f i, fixed facing : VFACE) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv) * _Tint; // apply fog UNITY_APPLY_FOG(i.fogCoord, col); // rim light //在液体表面有层光晕,菲涅尔。 float dotProduct = 1 - pow(dot(i.normal, i.viewDir), _RimPower); float4 RimResult = smoothstep(0.5, 1.0, dotProduct); RimResult *= _RimColor; // foam edge                      //在液体水面上的泡沫 float4 foam = ( step(i.fillEdge, 0.5) - step(i.fillEdge, (0.5 - _Rim))) ; float4 foamColored = foam * (_FoamColor * 0.9); // rest of the liquid float4 result = step(i.fillEdge, 0.5) - foam; float4 resultColored = result * col; // both together, with the texture float4 finalResult = resultColored + foamColored; finalResult.rgb += RimResult; // color of backfaces/ top float4 topColor = _TopColor * (foam + result); //VFACE returns positive for front facing, negative for backfacing return facing > 0 ? finalResult: topColor; } ENDCG } } }

   

 

 图3  顶点的x和y互换后,变成右边的图,即顶点是往右图的旋转方向旋转了,液体在X轴向上摆动,左右摆动。

 

C#脚本把值传入液体的shader里

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Wobble : MonoBehaviour
{
    Renderer rend;
    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;
    float time = 0.5f;
    
    // Use this for initialization
    void Start()
    {
        rend = GetComponent<Renderer>();
    }
    private void Update()
    {
        time += Time.deltaTime;
        // decrease wobble over time
        wobbleAmountToAddX = Mathf.Lerp(wobbleAmountToAddX, 0, Time.deltaTime * (Recovery));
        wobbleAmountToAddZ = Mathf.Lerp(wobbleAmountToAddZ, 0, Time.deltaTime * (Recovery));

        // make a sine wave of the decreasing wobble
        pulse = 2 * Mathf.PI * WobbleSpeed;
        wobbleAmountX = wobbleAmountToAddX * Mathf.Sin(pulse * time);
        wobbleAmountZ = wobbleAmountToAddZ * Mathf.Sin(pulse * time);

        // send it to the shader
        rend.material.SetFloat("_WobbleX", wobbleAmountX);
        rend.material.SetFloat("_WobbleZ", wobbleAmountZ);

        // velocity
        velocity = (lastPos - transform.position) / Time.deltaTime;
        angularVelocity = transform.rotation.eulerAngles - lastRot;


        // add clamped velocity to wobble
        wobbleAmountToAddX += Mathf.Clamp((velocity.x + (angularVelocity.z * 0.2f)) * MaxWobble, -MaxWobble, MaxWobble);
        wobbleAmountToAddZ += Mathf.Clamp((velocity.z + (angularVelocity.x * 0.2f)) * MaxWobble, -MaxWobble, MaxWobble);

        // keep last position
        lastPos = transform.position;
        lastRot = transform.rotation.eulerAngles;
    }



}

这传入shader的wobbleAmountX是控制液体左右摆的幅度,同理,wobbleAmountZ是前后摆动的幅度。这里velocity的值是只有在物体移动时才会不等于0,angularVelocity在只有旋转时才会不等于0,即只有移动或旋转时才会晃动液体,移动的增量越大,液体摆动得越大。

 

最后把玻璃瓶的shader代码贴上

Shader "Toon/Lit Specular Alpha" {
    Properties{
        _Color("Main Color", Color) = (1,1,1,1)
        _SColor("Specular Color", Color) = (1,1,1,1)
        _MainTex("Base (RGB)", 2D) = "white" {}
      _Ramp("Toon Ramp (RGB)", 2D) = "gray" {}
      _RampS("Specular Ramp (RGB)", 2D) = "gray" {} // specular ramp, cutoff point
      _SpecSize("Specular Size", Range(0.65,0.999)) = 0.9 // specular size
        _SpecOffset("Specular Offset", Range(0.5,1)) = 0.5 // specular offset of the spec Ramp
        _TColor("Gradient Overlay Top Color", Color) = (1,1,1,1)
        _BottomColor("Gradient Overlay Bottom Color", Color) = (0.23,0,0.95,1)
        _Offset("Gradient Offset", Range(-4,4)) = 3.2
        [Toggle(RIM)] _RIM("Fresnel Rim?", Float) = 0
        _RimColor("Fresnel Rim Color", Color) = (0.49,0.94,0.64,1)
        [Toggle(FADE)] _FADE("Fade specular to bottom?", Float) = 0
        _TopBottomOffset("Specular Fade Offset", Range(-4,4)) = 3.2
    }
 
        SubShader{
        Tags{ "Queue" = "Transparent"}
        LOD 200
        Blend SrcAlpha OneMinusSrcAlpha 
 
        CGPROGRAM
#pragma surface surf ToonRamp vertex:vert keepalpha
#pragma shader_feature FADE // fade toggle
#pragma shader_feature RIM // rim fresnel toggle
        sampler2D _Ramp;
 
    // custom lighting function that uses a texture ramp based
    // on angle between light direction and normal
#pragma lighting ToonRamp exclude_path:prepass
    inline half4 LightingToonRamp(SurfaceOutput s, half3 lightDir, half atten)
    {
#ifndef USING_DIRECTIONAL_LIGHT
        lightDir = normalize(lightDir);
#endif
 
        half d = dot(s.Normal, lightDir)*0.5 + 0.5;
        half3 ramp = tex2D(_Ramp, float2(d,d)).rgb;
 
        half4 c;
        c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
        c.a = s.Alpha;
        return c;
    }
 
 
    sampler2D _MainTex;
    float4 _Color;
    float4 _SColor; // specular color
    sampler2D _RampS; // specular ramp
    float _SpecSize; // specular size
    float _SpecOffset; // offset specular ramp
    float4 _TColor; // top gradient color
    float4 _BottomColor;// bottom gradient color
    float _TopBottomOffset; // gradient bottom offset
    float _Offset; // specular fade offset
    float4 _RimColor; // fresnel rim color
 
    struct Input {
        float2 uv_MainTex : TEXCOORD0;
        float3 lightDir;
        float3 worldPos; // world position
        float3 viewDir; // view direction from camera
    };
 
    void vert(inout appdata_full v, out Input o)
    {
        UNITY_INITIALIZE_OUTPUT(Input, o);
        o.lightDir = WorldSpaceLightDir(v.vertex); // get the worldspace lighting direction
    }
 
    void surf(Input IN, inout SurfaceOutput o) {
        float3 localPos = (IN.worldPos - mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).xyz);// local position of the object, with an offset
        half4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
        half d = dot(o.Normal, IN.lightDir)*0.5 + _SpecOffset; // basing on normal and light direction
        half3 rampS = tex2D(_RampS, float2(d, d)).rgb; // specular ramp
 
        float rim = 1 - saturate(dot(IN.viewDir, o.Normal)); // calculate fresnel rim
#if RIM
        o.Emission = _RimColor.rgb * pow(rim, 1.5); // fresnel rim
#endif
        float specular= (step(_SpecSize, rampS.r)) * rampS * d * _SColor.a;
        o.Albedo = specular* _SColor; // specular
        o.Alpha = c.a + specular;
#if FADE
        float specular2 = (step(_SpecSize, rampS.r)) * rampS * d* saturate(localPos.y + _TopBottomOffset)* _SColor.a;
        o.Albedo = specular2* _SColor; // fade specular to bottom
        o.Alpha = c.a + specular2;
#endif
        o.Albedo += c.rgb*lerp(_BottomColor, _TColor, saturate(localPos.y + _Offset)) * 1.1; // multiply color by gradient lerp
      
    }
    ENDCG
 
    }
 
        Fallback "Diffuse"
}

 

以上是关于unity模仿瓶子中的液体晃动的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ:3712: [PA2014]Fiolki

bzoj 3712: [PA2014]Fiolki

Fiolki

Fiolki题解

[HDU 3712] Fiolki (带边权并查集+启发式合并)

Unity双层液体Shader