【Unity3D】实现Decal贴花效果,模拟战旗游戏地形效果

目录

一、基础版

[二、Post Process 辉光Bloom效果 + 矩形渐隐](#二、Post Process 辉光Bloom效果 + 矩形渐隐)


涉及知识点:Decal贴花、屏幕后处理Bloom、屏幕空间构建世界空间、ChracterController物体移动、Terrain地形创建

一、基础版

Unity 2019.4.0f1 普通渲染管线(非URP、非HDRP)

URP HDRP 在Unity 2021.2以上均可直接使用内置的贴花系统,具体网上有说明。

贴花物体是一个个Cube立方体,我将它拉长了Y轴 便于能接触到地面。

其中角色物体胶囊体要进行设置渲染层级,GetComponent<MeshRenderer>().material.renderQueue = 3001; //将人物放到贴花物体之后渲染,不被贴花物体影响

如这个Cube长方体就是红色边缘的Cube贴花,贴花颜色是红色。

贴花物体是透明层,不写入深度和深度检测,以及裁剪正面,不裁剪背面。

可参考:贴花(Decal)效果在Shader里是如何实现的_哔哩哔哩_bilibili

核心代码是DepthToWorldPosition,屏幕空间反向构建世界空间,很类似全局雾效,注意摄像机要开启深度贴图。摄像机挂上这个如下脚本设置:

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class OpenDepthTexture : MonoBehaviour
{
    public DepthTextureMode mode;
    private void Awake()
    {
        GetComponent<Camera>().depthTextureMode = mode;
    }
}
cs 复制代码
Shader "Unlit/SimpleDecal"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100

        Pass
        {
            //Blend One DstAlpha
            Blend SrcAlpha OneMinusSrcAlpha
            //Blend One One
            ZWrite Off
            ZTest Off
            Cull Front 
            
            
            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;
                float4 screenPos : TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _CameraDepthTexture;
            fixed4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.screenPos = ComputeScreenPos(o.vertex);
                return o;
            }

            float3 DepthToWorldPosition(float4 screenPos)
            {
                float depth = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture,screenPos)));//线性深度值
                float4 ndcPos = (screenPos/screenPos.w) * 2 - 1;  //除以w是归一化Unity提供的屏幕坐标[0,1]  *2-1操作 转[-1,1]得到ndc(仅有x,y有效位)
                float3 clipPos = float3(ndcPos.x, ndcPos.y, 1) * _ProjectionParams.z;  //ndc补z位,直接用1 远平面值,之后乘以far远平面距离得到裁剪坐标
                //clipPos还缺一个w,实际z值就是w值故完整clipPos是(clipPos.xyzz) 再直接转视图空间得到远平面的视图空间坐标点(即Depth为1的点)                
                float3 viewPos = mul(unity_CameraInvProjection, clipPos.xyzz).xyz * depth; //*depth相当于一个Lerp操作,depth是[0,1]范围的值,取到depth深度的视图点
                float3 worldPos = mul(UNITY_MATRIX_I_V,float4(viewPos, 1)).xyz; //视图转世界坐标
                return worldPos;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 worldPos = DepthToWorldPosition(i.screenPos);
                float4 localPos = mul(unity_WorldToObject, float4(worldPos, 1.0));
                clip(float3(0.5,0.5,0.5) - abs(localPos.xyz));

                fixed2 decalUV = fixed2(localPos.x, localPos.z);
                decalUV = decalUV + 0.5; //模型空间 [-0.5,0.5] => [0,1] uv空间
                fixed4 color = tex2D(_MainTex, decalUV) * _Color;
                return color;
            }
            ENDCG
        }
    }
}

玩家胶囊体脚本:

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player3D : MonoBehaviour
{
    public GameObject greenPrefab;
    public GameObject redPrefab;
    public int size = 5;

    public Terrain terrain;

    private CharacterController characterController;
    private float h;
    private float v;
    private float verticalVelocity;
    public float moveSpeed = 1;


    // Start is called before the first frame update
    void Start()
    {
        Vector3 pos = transform.position;
        int halfSize = Mathf.FloorToInt(size / 2);
        for (int i = -halfSize; i <= halfSize; i++)
        {
            for (int j = -halfSize; j <= halfSize; j++)
            {
                int v = Mathf.Abs(i) + Mathf.Abs(j);
                if (v <= halfSize)
                {
                    GameObject go = GameObject.Instantiate(v == halfSize ? redPrefab : greenPrefab, transform);
                    go.transform.position = new Vector3(pos.x + i, pos.y, pos.z + j);
                }
            }
        }
        characterController = GetComponent<CharacterController>();

        GetComponent<MeshRenderer>().material.renderQueue = 3001; //将人物放到贴花物体之后渲染,不被贴花物体影响
    }



    private void Update()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");

        Vector3 moveDir = transform.forward * v + transform.right * h;

        if (!characterController.isGrounded)
        {
            verticalVelocity -= 9.8f * Time.deltaTime;
            moveDir.y = verticalVelocity;
        }
        else
        {
            verticalVelocity = -0.5f;
        }

        characterController.Move(moveDir * moveSpeed * Time.deltaTime);
    }
}

二、Post Process 辉光Bloom效果 + 矩形渐隐

Package Manager导入Post Processing

需确保摄像机开启HDR,查看相应的Graphics Settings设置

如果你的是安卓环境,那么就要去看安卓下的HDR是否开启,如果你是URP、HDRP,则是看对应管线资源的配置是否有开启HDR。

摄像机挂上后处理组件 Post-process Layer,并设置摄像机和Layer要勾选我们想要辉光效果的层级,例如自定义的HDR层

创建一个PostProcess空物体,添加如下图组件Post-process Volume,并且设置它的Layer为HDR

点击组件的New按钮会创建一个Profile资源,再点击Add effect... 添加Unity -> Bloom曝光效果

设置Intensity强度适当5~10左右
Threshold阈值[0,1]范围保持默认即可(如果你设置大于1会导致曝光效果几乎没有)

Soft Knee 软曝光?可以自定义

Diffusion 漫反射,当曝光的地形在有漫反射的物体上时会叠加漫反射的强度好像,反正我不需要拉到最左边即可(即1)其他请自行研究,如下图即可得到gif图效果,如果没有辉光说明Layer层级设置不对,所有需要辉光的物体都要设置Layer层级是HDR(由Post-process Layer指定的层级)

贴花Shader脚本修改如下:

cs 复制代码
Shader "Unlit/SimpleDecal"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        [HDR]
        _Color ("Color", Color) = (1,1,1,1)
        _Range ("Range", Range(0,0.5)) = 0.45
        _FadeIntensity ("Fade Intensity", float) = 2
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            ZTest Off
            Cull Front
            
            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;
                float4 screenPos : TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _CameraDepthTexture;
            fixed4 _Color;
            float _Range;
            float _FadeIntensity;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.screenPos = ComputeScreenPos(o.vertex);
                return o;
            }

            float3 DepthToWorldPosition(float4 screenPos)
            {
                float depth = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture,screenPos)));//线性深度值
                float4 ndcPos = (screenPos/screenPos.w) * 2 - 1;  //除以w是归一化Unity提供的屏幕坐标[0,1]  *2-1操作 转[-1,1]得到ndc(仅有x,y有效位)
                float3 clipPos = float3(ndcPos.x, ndcPos.y, 1) * _ProjectionParams.z;  //ndc补z位,直接用1 远平面值,之后乘以far远平面距离得到裁剪坐标
                //clipPos还缺一个w,实际z值就是w值故完整clipPos是(clipPos.xyzz) 再直接转视图空间得到远平面的视图空间坐标点(即Depth为1的点)                
                float3 viewPos = mul(unity_CameraInvProjection, clipPos.xyzz).xyz * depth; //*depth相当于一个Lerp操作,depth是[0,1]范围的值,取到depth深度的视图点
                float3 worldPos = mul(UNITY_MATRIX_I_V,float4(viewPos, 1)).xyz; //视图转世界坐标
                return worldPos;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 worldPos = DepthToWorldPosition(i.screenPos);
                float4 localPos = mul(unity_WorldToObject, float4(worldPos, 1.0));
                clip(float3(0.5,0.5,0.5) - abs(localPos.xyz));

                fixed2 decalUV = fixed2(localPos.x, localPos.z);
                decalUV = decalUV + 0.5; //模型空间 [-0.5,0.5] => [0,1] uv空间
                fixed4 color = tex2D(_MainTex, decalUV) * _Color;

                //由外到内渐隐 (圆形渐隐)
                //float2 center = float2(0.5, 0.5);
                //float alpha = lerp(0, distance(float2(0,0), center), distance(decalUV, center));
                //color.a *= alpha;

                //由外到内渐隐(矩形渐隐)
                fixed2 uv = decalUV - 0.5; //转[-0.5,0.5]空间 方便计算
                float x = abs(uv.x) - _Range;
                float y = abs(uv.y) - _Range;               
                //float sum = x+y ;//max(0, x) + max(0, y);
                float sum = max(x, y);
                //color.a *= smoothstep(0, (0.5 - _Range) * 2, sum);
                color.a *= smoothstep(0, (0.5 - _Range) * _FadeIntensity, sum);
                return color;
            }
            ENDCG
        }
    }
}

有些bug就是Scene场景看不到贴花地形了,暂时没研究处来是啥情况

相关推荐
mxwin7 小时前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader
晚枫歌F8 小时前
三层时间轮的实现
网络·unity·游戏引擎
咸鱼永不翻身10 小时前
Lua脚本事件检查工具
unity·lua·工具
leo__52012 小时前
单载波中继系统资源分配算法MATLAB仿真程序
算法·matlab·unity
努力长头发的程序猿13 小时前
Unity使用ScriptableObject序列化资源
unity·游戏引擎
mxwin13 小时前
Unity Shader 手写基于 PBR 的 URP Lit Shader 核心光照计算
unity·游戏引擎·shader
小贺儿开发13 小时前
Unity3D 智能云端数字标牌系统
unity·阿里云·人机交互·视频·oss·广告·互动
魔士于安14 小时前
Unity windows 同步 异步 打开文件文件夹工具
游戏·unity·游戏引擎·贴图·模型
魔士于安14 小时前
unity lowpoly 风格 城市 建筑 道路 交通标志
游戏·unity·游戏引擎·贴图·模型
mxwin14 小时前
Unity GPU Shader 性能优化指南
unity·游戏引擎·shader