天天看點

水瓶效果制作前言一、實作效果效果分析總結

前言

提示:這裡可以添加本文要記錄的大概内容:

本次分享主要為水瓶效果,思路借鑒于https://www.patreon.com/posts/quick-game-art-18245226 該連結,不過部分内容較難了解,是以打算使用自己的思路實作一下

提示:以下是本篇文章正文内容,下面案例可供參考

一、實作效果

水瓶效果制作前言一、實作效果效果分析總結

效果分析

1.膠囊體模型,可修改shader屬性修改水瓶水面高度

2.水面厚度,可修改shader屬性修改水準厚度

3.水瓶面顔色,可修改shader屬性修改水準面顔色

4.水瓶滿足實體效果,可左右搖曳,上下反轉

5.菲涅爾邊緣效果,加強層級

6.邊緣拓展,增加形體

實作流程

1.邊緣拓展,增加形體

首先是邊緣效果,為了突出水準的的形狀,我們的一個Pass用于邊緣效果實作。這裡直接使用了頂點沿法線方向拓展,并且追加了菲尼爾效果,作為我們第一個Pass這裡關閉深度寫入,避免第二個Pass深度測試失敗。以下是實作效果。

水瓶效果制作前言一、實作效果效果分析總結
邊緣效果代碼如下:
```c
     Pass
        {
            ZWrite Off 
            Cull Back
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 world_pos : TEXCOORD1  ;
                float3 world_normal:TEXCOORD2 ;
            };

            float _EdgePower;
            float _DimPow;

            v2f vert (appdata v)
            {
                v2f o;
                v.vertex.xyz += v.normal * _EdgePower;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.world_pos = mul(unity_ObjectToWorld, v.vertex);
                o.world_normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i, fixed facing : VFACE ) : SV_Target
            {
                float3 world_view_dir = normalize(UnityWorldSpaceViewDir(i.world_pos));
                float rim = 1 - pow(saturate(dot(i.world_normal, world_view_dir)), _DimPow);
                return fixed4(1,1,1, rim);
            }
            ENDCG
        }
           

膠囊體模型,可修改shader屬性修改水瓶水面高度

由于我們的水準面要要适應水瓶旋轉的各個角度保持平衡,是以我們這裡需要使用世界空間來計算,又避免世界空間影響水準高度計算,我們需要将對象空間的中心點切換到世界空間,再用世界空間的中心位置減去世界空間的頂點位置,這樣既不受世界空間的影響,也根據內插補點計算水準高度。得到的內插補點再減去一個控制變量,得到我們alpha值,我們就是通過修改透明通道的值來做剔除效果,最後Pass标記上當然要加上 AlphaToMask On 剔除透明部分。在上面的連結裡面我看到他直接用object->world的矩陣乘以對象空間頂點的xyz,這裡我是不太了解的,object->world矩陣不是一個4x4的矩陣,怎麼能乘以一個3維坐标呢。而且算出來的坐标也不受世界空間影響,我是覺得很奇怪的,有誰知道可以跟我講一下。

水瓶效果制作前言一、實作效果效果分析總結

邊緣效果代碼如下:

fixed4 frag (v2f i, fixed facing : VFACE ) : SV_Target
            {                
                float4 world_center = mul(unity_ObjectToWorld, float4(0, 0, 0,  1));
                float4 world_pos = i.world_pos;
                float4 cache_output1 = world_center - world_pos;
                float cache_output2 = 1 - _Height;
                float mulTime2 = _Time.y * _Speed;           
                float alpha = clamp((cache_output1.y - cache_output2 )/ _Falloff, 0 , 1 ) ;
               
                return fixed4(_Color.rgb , alpha);
            }
           

水面厚度,可修改shader屬性修改水準厚度

水準厚度我們直接調用step()函數,根據以上計算的內插補點,我們再減去一個水準厚度控制變量,得到一個非0即1的結果,再根據這個結果在Color1,以及Color之間做漸變。step()和smoothstep()這個函數我覺得在做漸變時候很管用,一個是無過渡式漸變,一個是過渡式漸變。

水瓶效果制作前言一、實作效果效果分析總結

邊緣效果代碼如下:

float4 world_center = mul(unity_ObjectToWorld, float4(0, 0, 0,  1));
                float4 world_pos = i.world_pos;
                float4 cache_output1 = world_center - world_pos;
                float cache_output2 = 1 - _Height;
                float alpha = clamp((cache_output1.y - cache_output2 )/ _Falloff, 0 , 1 ) ;
                float cache_output3 = step(cache_output1.y - _Width, cache_output2);
                float4 cache_output4 = lerp(_Color, _Color3, cache_output3);               
                return fixed4(cache_output4.rgb , alpha);
           

水瓶面顔色,可修改shader屬性修改水準面顔色,菲涅爾邊緣效果,加強層級

水準面顔色我們使用了fixed facing : VFACE 字段,該字段表示被渲染的面是否朝向錄影機,用于片段着色器。同時我們也追加菲尼爾效果

水瓶滿足實體效果,可左右搖曳,上下反轉

這個的實作方式我主要借鑒連結裡的思路。首先左右搖曳,我們需要得到速度以及力的衰弱權重。我這裡做了簡化版。連結還考慮了旋轉值,我這邊就直接考量速度與衰弱。速度就直接拿上一幀的坐标減去目前幀的坐标得到速度,衰弱就根據時間系數在速度值與0之間做內插補點得到衰弱權重。并将其整合成_WobbleX,_WobbleZ兩個方向上的權重傳入shader計算。

水瓶效果制作前言一、實作效果效果分析總結

以下是完整代碼

shader部分

Shader "Unlit/Test14"
{
    Properties
    {
        _Height("Height", Float) = 0.1
        _Falloff("Falloff", Float) = 0.1
        _Color("Color", Color) = (1, 0, 0, 0)
        _Color2("Color", Color) = (0, 0, 0, 0)
        _Color3("Color", Color) = (0, 1, 0, 0)
        _Speed("Speed", Float) = 0
        _Size("Size", Float) = 0.1
        _EdgePower("EdgePower", Range(0, 1)) = 0.1
        _DimPow("DimPower", Float) = 1
        _RimColor("RimColor", Color) = (1, 1, 1, 1)
        [HideInInspector] _WobbleX ("WobbleX", Range(-1,1)) = 0.0
        [HideInInspector] _WobbleZ ("WobbleZ", Range(-1,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent"}
        LOD 100
        Pass
        {
            ZWrite Off 
            Cull Back
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 world_pos : TEXCOORD1  ;
                float3 world_normal:TEXCOORD2 ;
            };

            float _EdgePower;
            float _DimPow;

            v2f vert (appdata v)
            {
                v2f o;
                v.vertex.xyz += v.normal * _EdgePower;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.world_pos = mul(unity_ObjectToWorld, v.vertex);
                o.world_normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i, fixed facing : VFACE ) : SV_Target
            {
                float3 world_view_dir = normalize(UnityWorldSpaceViewDir(i.world_pos));
                float rim = 1 - pow(saturate(dot(i.world_normal, world_view_dir)), _DimPow);
                return fixed4(1,1,1, rim);
            }
            ENDCG
        }
        Pass
        {

            AlphaToMask On
            Cull Off
            Blend SrcAlpha OneMinusSrcAlpha
            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;
                float4 vertex : SV_POSITION;
                float4 world_pos : TEXCOORD1  ;
                float3 world_normal: TEXCOORD2 ;
            };

            float _Height;
            float _Falloff;
            float4 _Color;
            float4 _Color2;
            float4 _Color3;
            float _WobbleX, _WobbleZ;
            float _Speed;
            float4 _RimColor;
            float _Size;
            float _DimPow;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.world_pos = mul(unity_ObjectToWorld, v.vertex);
                o.world_normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i, fixed facing : VFACE ) : SV_Target
            {
                float3 world_view_dir = normalize(UnityWorldSpaceViewDir(i.world_pos));
                float rim = 1 - pow(saturate(dot(i.world_normal, world_view_dir)), _DimPow);
               

                facing = facing * 0.5 + 0.5;
                 float4 rim_color = rim * _RimColor * facing;
                float4 world_center = mul(unity_ObjectToWorld, float4(0, 0, 0,  1));
                float4 world_pos = i.world_pos;
                float4 cache_output1 = world_center - world_pos;
                float cache_output2 = 1 - _Height;
                float mulTime2 = _Time.y * _Speed;
                float cache_output_3 = (( _WobbleX * cache_output1.x * sin( mulTime2 ) * _Size ) 
                    +( _WobbleZ * cache_output1.z * sin( mulTime2 ) * _Size ) 
                    + cache_output1.y);
                float alpha = clamp((cache_output_3 - cache_output2 )/ _Falloff, 0 , 1 ) ;
                float cache_output3 = step(cache_output_3 - 0.1, cache_output2);
                float4 cache_output4 = lerp(_Color + rim_color, _Color3, cache_output3);
                float4 finalcolor = lerp(_Color2 , cache_output4, facing);
                return fixed4(finalcolor.rgb + rim_color.rgb , alpha);
            }
            ENDCG
        }

    }
}

           

cs部分

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

public class MyWobble : MonoBehaviour
{
    Renderer rend;
    Vector3 lastPos;
    Vector3 velocity;
    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;
    // Start is called before the first frame update
    void Start()
    {
        rend = GetComponent<Renderer>();
    }

    // Update is called once per frame
    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", wobbleAmountToAddX);
        rend.material.SetFloat("_WobbleZ", wobbleAmountToAddZ);
        //print(wobbleAmountX + "--------------" +wobbleAmountZ);
        // velocity
        velocity = (lastPos - transform.position) / Time.deltaTime;
        print(velocity);


        // add clamped velocity to wobble
        wobbleAmountToAddX += Mathf.Clamp((velocity.x) * MaxWobble, -MaxWobble, MaxWobble);
        wobbleAmountToAddZ += Mathf.Clamp((velocity.z ) * MaxWobble, -MaxWobble, MaxWobble);
         
        // keep last position
        lastPos = transform.position;
      
    }
}

           

總結

以上就是水瓶制作的流程,歡迎讨論。特别是在上面的連結裡面直接用object->world的矩陣乘以對象空間頂點的xyz,這裡我是不太了解的,object->world矩陣不是一個4x4的矩陣,怎麼能乘以一個3維坐标呢。而且算出來的坐标也不受世界空間影響,而且剛好能用,神奇。