foreword
提示:这里可以添加本文要记录的大概内容:
This sharing is mainly about the water bottle effect, and the ideas are borrowed from https://www.patreon.com/posts/quick-game-art-18245226 This link, but some content is difficult to understand, so I plan to use my own ideas to realize it
提示:以下是本篇文章正文内容,下面案例可供参考
1. Realize the effect
Effectiveness analysis
1. Capsule model, you can modify the shader attribute to modify the height of the water surface of the water bottle.
2. The thickness of the water surface, you can modify the shader attribute to modify the horizontal thickness. 3.
The color of the water bottle surface, you can modify the shader attribute to modify the color of the
horizontal surface. Up and down reversal
5. Fresnel edge effect, strengthen the level
6. Edge expansion, increase the shape
Implementation process
1. Expand the edge and increase the shape
The first is the edge effect. In order to highlight the horizontal shape, one of our Passes is used to achieve the edge effect. Here, the vertices are directly used to expand along the normal direction, and the Fennel effect is added. As our first pass, the depth writing is turned off here to avoid the failure of the depth test of the second pass. The following is the effect.
边缘效果代码如下:
```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
}
Capsule model, you can modify the shader attribute to modify the water surface height of the water bottle
Since our horizontal plane has to adapt to the various angles of the water bottle rotation to maintain balance, we need to use the world space to calculate here, and to avoid the world space from affecting the level calculation, we need to switch the center point of the object space to the world space, and then use the world The center position of the space is subtracted from the vertex position of the world space, so that it is not affected by the world space, and the horizontal height is calculated according to the difference. Subtract a control variable from the obtained difference to get our alpha value. We modify the value of the transparent channel to remove the effect. Finally, AlphaToMask On must be added to the Pass mark to remove the transparent part. In the above link, I saw that he directly multiplies the object->world matrix by the xyz of the object space vertex. I don’t quite understand here. The object->world matrix is not a 4x4 matrix. How can it be multiplied by a 3 dimensional coordinates. And the calculated coordinates are not affected by the world space. I find it very strange. If anyone knows, please tell me.
The edge effect code is as follows:
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);
}
The thickness of the water surface, the shader property can be modified to modify the horizontal thickness
For the horizontal thickness, we directly call the step() function. According to the difference calculated above, we subtract a horizontal thickness control variable to get a result that is either 0 or 1, and then make a gradient between Color1 and Color based on this result. I think the functions step() and smoothstep() are very useful when making gradients. One is a non-transitional gradient and the other is a transitional gradient.
The edge effect code is as follows:
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);
Water bottle surface color, you can modify the shader attribute to modify the horizontal plane color, Fresnel edge effect, strengthen the level
For the horizontal plane color we use the fixed facing: VFACE field, which indicates whether the rendered face is facing the camera, for the fragment shader. At the same time, we also added the Finnier effect
The water bottle satisfies the physical effect, can swing left and right, reverse up and down
I mainly use the ideas in the link for the realization of this method. First swaying side to side, we need to get velocity and force decay weights. I made a simplified version here. The link also takes into account the rotation value, and my side directly considers speed and weakening. The speed is directly obtained by subtracting the coordinates of the previous frame from the coordinates of the current frame to obtain the speed, and the weakening is obtained by making a difference between the speed value and 0 according to the time coefficient to obtain the weakening weight. And integrate it into _WobbleX, _WobbleZ The weights in the two directions are passed to the shader for calculation.
The following is the complete code
shader part
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 part
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;
}
}
Summarize
The above is the process of making water bottles, welcome to discuss. Especially in the above link, directly multiply the object->world matrix by the xyz of the object space vertex. I don’t quite understand here. The object->world matrix is not a 4x4 matrix. How can it be multiplied by a 3-dimensional coordinate? Woolen cloth. Moreover, the calculated coordinates are not affected by the world space, and it just works, which is amazing.