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 frame
void 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 100
Pass
{
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 texture
fixed4 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 100
Pass
{
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 texture
fixed4 col = tex2D(_MainTex, i.uv);
fixed4 splatCol = tex2D(_SplatTex, i.uv);
// apply fog
return 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 100
Pass
{
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.0
sampler2D _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 Factor
float 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;
}
#endif
float4 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 texture
fixed4 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次:

参考: