前言
提示:这里可以添加本文要记录的大概内容:
本次分享主要为水瓶效果,思路借鉴于https://www.patreon.com/posts/quick-game-art-18245226 该链接,不过部分内容较难理解,所以打算使用自己的思路实现一下
提示:以下是本篇文章正文内容,下面案例可供参考
一、实现效果
效果分析
1.胶囊体模型,可修改shader属性修改水瓶水面高度
2.水面厚度,可修改shader属性修改水平厚度
3.水瓶面颜色,可修改shader属性修改水平面颜色
4.水瓶满足物理效果,可左右摇曳,上下反转
5.菲涅尔边缘效果,加强层级
6.边缘拓展,增加形体
实现流程
1.边缘拓展,增加形体
首先是边缘效果,为了突出水平的的形状,我们的一个Pass用于边缘效果实现。这里直接使用了顶点沿法线方向拓展,并且追加了菲尼尔效果,作为我们第一个Pass这里关闭深度写入,避免第二个Pass深度测试失败。以下是实现效果。
边缘效果代码如下:
```cPass{ZWrite Off Cull BackBlend SrcAlpha OneMinusSrcAlphaCGPROGRAM#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 100Pass{ZWrite Off Cull BackBlend SrcAlpha OneMinusSrcAlphaCGPROGRAM#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 OnCull OffBlend SrcAlpha OneMinusSrcAlphaCGPROGRAM#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 updatevoid Start(){rend = GetComponent<Renderer>();}// Update is called once per framevoid Update(){time += Time.deltaTime;// decrease wobble over timewobbleAmountToAddX = 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 shaderrend.material.SetFloat("_WobbleX", wobbleAmountToAddX);rend.material.SetFloat("_WobbleZ", wobbleAmountToAddZ);//print(wobbleAmountX + "--------------" +wobbleAmountZ);// velocityvelocity = (lastPos - transform.position) / Time.deltaTime;print(velocity);// add clamped velocity to wobblewobbleAmountToAddX += Mathf.Clamp((velocity.x) * MaxWobble, -MaxWobble, MaxWobble);wobbleAmountToAddZ += Mathf.Clamp((velocity.z ) * MaxWobble, -MaxWobble, MaxWobble);// keep last positionlastPos = transform.position;}
}
总结
以上就是水瓶制作的流程,欢迎讨论。特别是在上面的链接里面直接用object->world的矩阵乘以对象空间顶点的xyz,这里我是不太理解的,object->world矩阵不是一个4x4的矩阵,怎么能乘以一个3维坐标呢。而且算出来的坐标也不受世界空间影响,而且刚好能用,神奇。