Unity Shader unity文档学习笔记(二十二):雪地几种实现方式(1. 2D贴花式 2.3D曲面细分并且实现顶点偏移)
1.2D 贴花式
1.记录该地形主帖图对应UV是否有数据:角色移动时利用Physics.Raycast方式获取到RaycastHit.textureCoord即UV的位置,并且对贴图(专门记录轨道的贴图)进行写入
c# (放在相机上)
public class TestDrawLine : MonoBehaviour
{public Camera camera;public Transform player;public Material drawLineMat;public Material snowMat;public RenderTexture _splatMap;private void Start(){_splatMap = new RenderTexture(1024, 1024, 0, RenderTextureFormat.ARGBFloat);}// Update is called once per framevoid Update(){var screenPos = camera.WorldToScreenPoint(player.position);int layerMask = 1 << LayerMask.NameToLayer("Ground");if (Physics.Raycast(camera.ScreenPointToRay(screenPos), out RaycastHit hit, 999, layerMask)){var clickUV = new Vector4(hit.textureCoord.x, hit.textureCoord.y);Debug.Log($"++screenPos:{screenPos}+hit.pos:{hit.point}+clickUV:" + clickUV);drawLineMat.SetVector("_DrawUv", clickUV);RenderTexture temp = RenderTexture.GetTemporary(_splatMap.width, _splatMap.height, 0, RenderTextureFormat.ARGBFloat);Graphics.Blit(_splatMap, temp);Graphics.Blit(temp, _splatMap, drawLineMat);RenderTexture.ReleaseTemporary(temp);snowMat.SetTexture("_SplatTex", _splatMap);}}
}
drawLineMat对应的shader
Shader "Unlit/TestDrawLine"
{Properties{_MainTex ("Texture", 2D) = "white" {}_Color ("Color", Color) = (1,0,0,1)_DrawUv ("DrawUv", Vector) = (0,0,0,0)_Size ("Size", float) = 2}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;};sampler2D _MainTex;float4 _MainTex_ST;float4 _Color;float4 _DrawUv;float _Size;v2f vert (appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;}fixed4 frag (v2f i) : SV_Target{// sample the texturefixed4 col = tex2D(_MainTex, i.uv);float draw = pow(saturate(1 - distance(i.uv, _DrawUv.xy)), 500/_Size);float4 drawColor = _Color * draw;return col + drawColor;}ENDCG}}
}
地形shader
Shader "Unlit/TestSnow"
{Properties{_MainTex ("Texture", 2D) = "white" {}_SplatTex ("SplatTexture", 2D) = "black" {}}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;};sampler2D _MainTex;float4 _MainTex_ST;sampler2D _SplatTex;v2f vert (appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;}fixed4 frag (v2f i) : SV_Target{// sample the texturefixed4 col = tex2D(_MainTex, i.uv);fixed4 splatCol = tex2D(_SplatTex, i.uv);// apply fogreturn col - splatCol.a;}ENDCG}}
}
效果

2.3D曲面细分并且实现顶点偏移
在上面的基础上对shader进行修改
domain细分计算时偏移顶点:
// 应用积雪位移float displace = tex2Dlod(_SplatTex, float4(uv, 0.0f, 0.0f)).x;position += normalize(float3(0, 1, 0)) * saturate(1 - displace) * _SnowMaxHeight;
重新计算法线
// 重新计算法线 - 使用有限差分法float offset = 0.0001;float2 uvX = uv + float2(offset, 0);float2 uvY = uv + float2(0, offset);float displaceX = tex2Dlod(_SplatTex, float4(uvX, 0.0f, 0.0f)).x;float displaceY = tex2Dlod(_SplatTex, float4(uvY, 0.0f, 0.0f)).x;float3 posX = position + float3(offset, 0, 0) + normalize(float3(0, 1, 0)) * saturate(1 - displaceX) * _SnowMaxHeight;float3 posY = position + float3(0, 0, offset) + normalize(float3(0, 1, 0)) * saturate(1 - displaceY) * _SnowMaxHeight;float3 tangent = normalize(posX - position);float3 bitangent = normalize(posY - position);v.normal = normalize(cross(tangent, bitangent));
地形shader
Shader "Unlit/TestSnow1"
{Properties{_MainTex ("Texture", 2D) = "white" {}_SplatTex ("SplatTexture", 2D) = "black" {}_SnowMaxHeight("Snow Max Height", Range(0.001,1)) = 0.1_TessellationUniform("TessellationUniform",Range(1,64)) = 1_Radius ("目标圆半径", Float) = 1.0 // 最终圆的半径(世界空间)_WorldSourcePos ("原世界坐标", Vector) = (0,0,0) // 原世界坐标_WorldTargetPos ("目标世界坐标", Vector) = (0,0,0) // 目标世界坐标_Transition ("过渡系数", Range(0,1)) = 0 // 0=原模型,1=完整圆(世界空间变形)}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma vertex tessvert#pragma hull hullProgram //细分控制#pragma domain ds //细分计算#pragma fragment frag#include "UnityCG.cginc"//引入曲面细分的头文件#include "Tessellation.cginc" #include "Lighting.cginc"#pragma target 5.0sampler2D _MainTex;float4 _MainTex_ST;sampler2D _SplatTex;float4 _SplatTex_TexelSize;float _SnowMaxHeight;float _Radius;float3 _WorldSourcePos;float3 _WorldTargetPos;fixed4 _Color;float _Transition;float _TessellationUniform;struct VertexInput{float4 vertex : POSITION;float2 uv : TEXCOORD0;float3 normal : NORMAL;float4 tangent : TANGENT;};struct VertexOutput{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;float4 worldPos : TEXCOORD1;float3 normal : NORMAL;float4 tangent : TANGENT;};VertexOutput vert (VertexInput v)//这个函数应用在domain函数中,用来空间转换的函数{VertexOutput o;o.vertex = UnityObjectToClipPos(v.vertex);o.worldPos = mul(unity_ObjectToWorld, o.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);o.tangent = v.tangent;o.normal = mul(v.normal, (float3x3)unity_WorldToObject);return o;}//有些硬件不支持曲面细分着色器,定义了该宏就能够在不支持的硬件上不会变粉,也不会报错#ifdef UNITY_CAN_COMPILE_TESSELLATION//顶点着色器结构的定义struct TessVertex{float4 vertex : INTERNALTESSPOS;float3 normal : NORMAL;float4 tangent : TANGENT;float2 uv : TEXCOORD0;};struct OutputPatchConstant { //不同的图元,该结构会有所不同//该部分用于Hull Shader里面//定义了patch的属性//Tessellation Factor和Inner Tessellation Factorfloat edge[3] : SV_TESSFACTOR;float inside : SV_INSIDETESSFACTOR;};TessVertex tessvert (VertexInput v){//顶点着色器函数TessVertex o;o.vertex = v.vertex;o.normal = v.normal;o.tangent = v.tangent;o.uv = v.uv;return o;}OutputPatchConstant hsconst (InputPatch<TessVertex,3> patch){//定义曲面细分的参数OutputPatchConstant o;o.edge[0] = _TessellationUniform;o.edge[1] = _TessellationUniform;o.edge[2] = _TessellationUniform;o.inside = _TessellationUniform;return o;}[UNITY_domain("tri")]//确定图元,quad,triangle等[UNITY_partitioning("fractional_odd")]//拆分edge的规则,equal_spacing,fractional_odd,fractional_even[UNITY_outputtopology("triangle_cw")][UNITY_patchconstantfunc("hsconst")]//一个patch一共有三个点,但是这三个点都共用这个函数[UNITY_outputcontrolpoints(3)] //不同的图元会对应不同的控制点TessVertex hullProgram (InputPatch<TessVertex,3> patch,uint id : SV_OutputControlPointID){//定义hullshaderV函数return patch[id];}[UNITY_domain("tri")]//同样需要定义图元VertexOutput ds (OutputPatchConstant tessFactors, const OutputPatch<TessVertex,3>patch,float3 bary :SV_DOMAINLOCATION)//bary:重心坐标{VertexInput v;// 原始顶点数据float3 position = patch[0].vertex * bary.x + patch[1].vertex * bary.y + patch[2].vertex * bary.z;float3 normal = patch[0].normal * bary.x + patch[1].normal * bary.y + patch[2].normal * bary.z;float2 uv = patch[0].uv * bary.x + patch[1].uv * bary.y + patch[2].uv * bary.z;v.tangent = patch[0].tangent * bary.x + patch[1].tangent * bary.y + patch[2].tangent * bary.z;// 应用积雪位移float displace = tex2Dlod(_SplatTex, float4(uv, 0.0f, 0.0f)).x;position += normalize(float3(0, 1, 0)) * saturate(1 - displace) * _SnowMaxHeight;v.vertex = float4(position, 1.0f);v.uv = uv;// 重新计算法线 - 使用有限差分法float offset = 0.0001;float2 uvX = uv + float2(offset, 0);float2 uvY = uv + float2(0, offset);float displaceX = tex2Dlod(_SplatTex, float4(uvX, 0.0f, 0.0f)).x;float displaceY = tex2Dlod(_SplatTex, float4(uvY, 0.0f, 0.0f)).x;float3 posX = position + float3(offset, 0, 0) + normalize(float3(0, 1, 0)) * saturate(1 - displaceX) * _SnowMaxHeight;float3 posY = position + float3(0, 0, offset) + normalize(float3(0, 1, 0)) * saturate(1 - displaceY) * _SnowMaxHeight;float3 tangent = normalize(posX - position);float3 bitangent = normalize(posY - position);v.normal = normalize(cross(tangent, bitangent));VertexOutput o = vert(v);return o;}#endiffloat4 frag (VertexOutput i) : SV_Target{// 使用屏幕空间导数重新计算法线float3 worldPos = i.worldPos.xyz;float3 dpdx = ddx(worldPos);float3 dpdy = ddy(worldPos);float3 worldNormal = normalize(cross(dpdy, dpdx));// sample the texturefixed4 col = tex2D(_MainTex, i.uv);float3 lDir = normalize(_WorldSpaceLightPos0.xyz);float3 nDir = normalize(i.normal); // 使用重新计算的法线//float3 nDir = worldNormal; // 使用导数重新计算的法线fixed3 diffuse = _LightColor0.xyz * saturate(dot(lDir, nDir));col.xyz = (col.xyz + (col.xyz * diffuse))/2;return col;}ENDCG}}
}
细分是因为地形本身网格不会太多
细分次数2次:

细分5次:

参考:
https://zhuanlan.zhihu.com/p/110447928
https://www.bilibili.com/video/BV1wJ411c7vj/?spm_id_from=333.337.search-card.all.click&vd_source=d99d4bd9635d5402f84c65bbb41a26d9
