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 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次:

参考:

https://zhuanlan.zhihu.com/p/110447928

https://www.bilibili.com/video/BV1wJ411c7vj/?spm_id_from=333.337.search-card.all.click\&vd_source=d99d4bd9635d5402f84c65bbb41a26d9

相关推荐
知了一笑3 小时前
互联网十年,从博客到知识库
笔记·博客·知识库·自媒体
三体世界3 小时前
Qt从入门到放弃学习之路(1)
开发语言·c++·git·qt·学习·前端框架·编辑器
hrrrrb4 小时前
【机器学习】无监督学习
人工智能·学习·机器学习
zyq~4 小时前
【课堂笔记】概率论-3
笔记·概率论
崎岖Qiu4 小时前
【设计模式笔记07】:迪米特法则
java·笔记·设计模式·迪米特法则
D.....l5 小时前
STM32学习(MCU控制)(DMA and ADC)
stm32·单片机·学习
AI浩5 小时前
自监督 YOLO:利用对比学习实现标签高效的目标检测
学习·yolo·目标检测
摇滚侠6 小时前
Spring Boot3零基础教程,SpringApplication 自定义 banner,笔记54
java·spring boot·笔记