在顶点着色器中,使用模型的normal法线方向来在模型空间下偏移顶点的位置。来做出类似藤蔓生长的效果。同时使用ASE的Opacity Mask剔除掉藤蔓一些片元来模拟生长。

Local Vertex Offset
在ASE的输出节点中,可以看到Local Vertex Offset插槽,这就是用来在模型空间下偏移顶点的。

Opacity Mask
如果想要让ASE实现第二节中所讲的Alpha Test + Clip函数的功能,需要先将Render Queue设置为Alpha Test,会出现Mask Clip Value,这个是根据贴图的明度来剔除的阈值,我们这里设为0,这时ASE的输出节点中Opacity Mask就能使用了,我们可以给它一个黑白贴图,贴图中偏黑的部分就会被剔除。
在藤蔓生长的例子中,我们使用uv的v分量作为黑白贴图来剔除藤蔓的片元,因为v分量正好是顺着藤蔓生长的方向的。
同时,我们还需要将uv的v分量使用SmoothStep来提高一下对比度,使用这个值来控制顶点的偏移,这样藤蔓末梢的偏移就能平滑了。

上图并不是最终版本
Roughness和Smoothness
将Roughness贴图反向一下(One Minus)就能当作Smoothness贴图使用。
最终Shader代码
这里的Shader代码是Unlit的,没有实现PBR
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| Shader "KerryTA/ASE/05ViceCode" { Properties { _MainTex ("Texture", 2D) = "white" {} _Shrink("Shrink", Float) = 0.0 _Expand("Expand", Float) = 0.5 _Grow ("Grow", Range(-2.0, 2.0)) = 0.0 _GrowMin("GrowMin", Range(0, 1)) = 0.6 _GrowMax("GrowMax", Range(0, 1.5)) = 1.35 _EndMin("EndMin", Range(0, 1)) = 0.5 _EndMax("EndMax", Range(0, 1.5)) = 1.0 } SubShader { Tags { "RenderType"="Opaque" }
Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
#include "UnityCG.cginc"
struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; };
struct v2f { float2 uv : TEXCOORD0; float4 pos : SV_POSITION; };
sampler2D _MainTex; float4 _MainTex_ST; float _Shrink; float _Expand; float _Grow; float _GrowMin; float _GrowMax; float _EndMin; float _EndMax;
v2f vert (appdata v) { v2f o; float weight_expand = smoothstep(_GrowMin, _GrowMax, (v.texcoord.y - _Grow)); float weight_end = smoothstep(_EndMin, _EndMax, v.texcoord.y); float weight_combine = max(weight_expand, weight_end);
float3 vertex_shrink = v.normal * _Shrink * 0.01 * weight_combine; float3 vertex_expand = v.normal * _Expand *0.01; float3 final_offset = vertex_shrink + vertex_expand;
v.vertex.xyz = v.vertex.xyz + final_offset; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; return o; }
fixed4 frag (v2f i) : SV_Target { clip(1.0 - (i.uv.y - _Grow)); fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } }
|
显示法线脚本
在Built-in渲染管线中,需要自定义脚本来显示模型法线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif
public class DrawNormals : MonoBehaviour { #if UNITY_EDITOR [SerializeField] private MeshFilter _meshFilter = null; [SerializeField] private NormalsDrawData _vertexNormals = new NormalsDrawData(new Color32(0,0,255, 127), true); [SerializeField] private NormalsDrawData _vertexTangents = new NormalsDrawData(new Color32(0, 255, 0, 127), true); [SerializeField] private NormalsDrawData _vertexBinormals = new NormalsDrawData(new Color32(255, 0, 0, 127), true);
[System.Serializable] private class NormalsDrawData { [SerializeField] protected DrawType _draw = DrawType.Selected; protected enum DrawType { Never, Selected, Always } [SerializeField] protected float _length = 0.3f; [SerializeField] protected Color _normalColor; private Color _baseColor = new Color32(255, 133, 0, 255); [SerializeField] protected float _baseSize = 0.0125f;
public NormalsDrawData(Color normalColor, bool draw) { _normalColor = normalColor; _draw = draw ? DrawType.Selected : DrawType.Never; }
public bool CanDraw(bool isSelected) { return (_draw == DrawType.Always) || (_draw == DrawType.Selected && isSelected); }
public void Draw(Vector3 from, Vector3 direction) { Gizmos.color = _baseColor; Gizmos.DrawWireSphere(from, _baseSize);
Gizmos.color = _normalColor; Gizmos.DrawRay(from, direction * _length); } }
void OnDrawGizmosSelected() { OnDrawNormals(true); }
void OnDrawGizmos() { if (!Selection.Contains(this)) OnDrawNormals(false); }
private void OnDrawNormals(bool isSelected) { if (_meshFilter == null) { _meshFilter = GetComponent<MeshFilter>(); if (_meshFilter == null) return; }
Mesh mesh = _meshFilter.sharedMesh;
Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; Vector4[] tangents = mesh.tangents;
for (int i = 0; i < vertices.Length; i++) { Vector3 view_world = Vector3.Normalize(Camera.current.transform.forward - vertices[i]); Vector3 normal_world = Vector3.Normalize(transform.TransformVector(normals[i])); float NdotV = Vector3.Dot(normal_world, view_world); if (NdotV < 0.0) { Vector3 tangent_world = transform.TransformVector(new Vector3(tangents[i].x, tangents[i].y, tangents[i].z)); if (_vertexNormals.CanDraw(isSelected)) _vertexNormals.Draw(transform.TransformPoint(vertices[i]), normal_world); if (_vertexTangents.CanDraw(isSelected)) _vertexTangents.Draw(transform.TransformPoint(vertices[i]), tangent_world); Vector3 binormal_world = Vector3.Normalize(Vector3.Cross(normal_world, tangent_world) * tangents[i].w); if (_vertexBinormals.CanDraw(isSelected)) _vertexBinormals.Draw(transform.TransformPoint(vertices[i]), binormal_world); } } } #endif }
|