前言
提示:這裡可以添加本文要記錄的大概内容:
本次分享主要為水瓶效果,思路借鑒于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維坐标呢。而且算出來的坐标也不受世界空間影響,而且剛好能用,神奇。