【Unity】RenderFeature应用(简单场景扫描效果)

【Unity】RenderFeature应用(简单场景扫描效果)

RenderFeature 是一个用于渲染图形的概念,通常在图形引擎或游戏引擎中使用。它是一个模块化的组件,负责处理特定的渲染功能,例如阴影、光照、粒子效果等。

点击地面生成一个不断扩展的圆光效果,用于实现一些画面的特效

实现效果

一、实现方法

1.要求和原理

案例是基于unity urp渲染管线制作,使用了RenderFeature后处理效果。

基本原理一句话,通过相机深度图重建像素的世界空间位置,空间坐标和点击点的坐标进行距离运算画出圆。

2.实现步骤

1)创建UniversalRenderPipelineAsset

​ 在编写RenderFeature前需要创建UniversalRenderPipelineAsset

​ Create→Randering→URPAsset(with Universal Render)

​ 创建后会同时生成UniversalRenderPipelineAsset和UniversalRenderData

​ 动态设置当前UniversalRenderPipelineAsset的方法(也可以手动设置)

   //使用的UniversalRenderPipelineAsset
    public UniversalRenderPipelineAsset UniversalRenderPipelineAsset;
    void Start()
    {
        //分别在Graphics和 Quality里设置成使用的UniversalRenderPipelineAsset
        GraphicsSettings.renderPipelineAsset = UniversalRenderPipelineAsset;
        QualitySettings.renderPipeline = UniversalRenderPipelineAsset;
    }

2)编写RenderFeature

​ 创建RenderFeature,具体介绍可以参见【Unity】RenderFeature笔记

​ Create→Randering→RenderFeature

​ 下面是通用的shader后处理方法,不同的是参数内容和方法

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class ScanRenderPassFeature : ScriptableRendererFeature
{
    class CustomRenderPass : ScriptableRenderPass
    {
        public Material _Material;
        public Vector4 _Pos;//点击点
        public Color _Color;//线颜色
        public float _Interval;//线间距
        public float _Strength;//强度范围

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
           // Matrix4x4 frustumCorners = GetFrustumCornersRay();
            CommandBuffer cmd = CommandBufferPool.Get("ScanRender");
            _Material.SetVector("_CentorPoint", _Pos);
            _Material.SetColor("_Color", _Color);
            _Material.SetFloat("_Interval", _Interval);
            _Material.SetFloat("_Strength", _Strength);
            cmd.Blit(colorAttachment, RenderTargetHandle.CameraTarget.Identifier(), _Material);
            //执行CommandBuffer
            context.ExecuteCommandBuffer(cmd);
            //回收CommandBuffer
            CommandBufferPool.Release(cmd);
        }
    }

    CustomRenderPass m_ScriptablePass;
    public Shader ScanShader;
    public Vector4 Pos;
    public Color Color;
    public float Interval;//间距
    public float Strength;//强度
    /// <inheritdoc/>
    public override void Create()
    {
        m_ScriptablePass = new CustomRenderPass();
        m_ScriptablePass._Material = new Material(ScanShader);
        m_ScriptablePass._Pos = Pos;
        m_ScriptablePass._Color = Color;
        m_ScriptablePass._Interval = Interval;
        m_ScriptablePass._Strength = Strength;
        // Configures where the render pass should be injected.
        m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRendering;
    }
    public void SetParam()
    {
        m_ScriptablePass._Pos = Pos;
        m_ScriptablePass._Strength = Strength;
    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(m_ScriptablePass);
    }
}

3)编写shader

​ shader要实现的是两个功能,一是深度图重建像素的世界空间位置,再是根据空间位置画出扩展圆

​ 重建像素的世界空间位置可以参照官方的案例

从深度纹理重建像素的世界空间位置 |URP |7.7.1 (unity3d.com)

​ 核心方法:

​ ComputeWorldSpacePosition:是一个用于计算物体在世界空间中位置的函数。它通常用于计算游戏中的物体 在世界坐标系中的位置。

​ SampleSceneDepth:用于获取深度图像。它主要用于实现一些基本的深度相关功能,比如观察场景中物体深 度信息、计算物体之间的距离等。

​ 下面结合官方的方法进行修改

Shader "Unlit/ScanShaderURP"
{
    Properties
    {
        _CentorPoint("CentrePoint",Vector) = (0, 0, 0, 0)
        _Color("color",Color) = (1,1,1,1)   //颜色
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
          
           struct Attributes
            {
            float4 positionOS   : POSITION;
        };

        struct Varyings
        {
            float4 positionHCS  : SV_POSITION;
        };

            float4x4 _FrustumCornersRay;
            float  _Interval;//间距
            float _Strength;//强度
            sampler2D _CameraColorTexture;
            float4 _CentorPoint;
            float4 _Color;
            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                return OUT;
            }
            half4 frag(Varyings IN) : SV_Target
            {
                 //uv变换
                float2x2 muls = float2x2(-1, 0, 0, 1);
                float2 centerUV = float2(1, 0);
                float2 UV =1-( mul(( IN.positionHCS.xy / _ScaledScreenParams.xy), muls)+centerUV);
                //获取深度图
#if UNITY_REVERSED_Z
                real depth = SampleSceneDepth(UV);
#else
                real depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(UV));
#endif
                //获取世界坐标位置
                float3 worldPos = ComputeWorldSpacePosition(UV, depth, UNITY_MATRIX_I_VP);
                half4 col2 = tex2D(_CameraColorTexture, UV);
                float lerpValue = 0;
                //对截面外的空间进行屏蔽
                if (depth < _ProjectionParams.z - 1) {
                    float Mul = distance(_CentorPoint.xyz, worldPos.xyz);
                    //change控制圈的距离
                    float change = _Strength;
                //Mul的值是一定大于0的
                //第一个smoothstep小于change的值裁剪为0,大于_Interval + change的为1
                //第二个smoothstep大于_Interval + change的为1,小于的为0
                //两smoothstep相减得到0 + change和 _Interval + change距离间0到1的变化,其余为0
                    float lerp1 = smoothstep(0 + change, _Interval + change, Mul);
                    float lerp2 = smoothstep(_Interval + change, _Interval + change, Mul);
                    float dis = lerp1 - lerp2;
                    lerpValue = dis;
                }
                half4 myCol = lerp(col2, _Color, lerpValue);
                return myCol;
            }
                ENDHLSL
        }
    }
}

4)控制方法


using System.Linq;
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class ScanControl : MonoBehaviour
{
    public UniversalRendererData renderData;
    ScanRenderPassFeature custom;
    private void Start()
    {
      custom = renderData.rendererFeatures.OfType<ScanRenderPassFeature>().FirstOrDefault();
    }
    private void Update()
    {
        if (Input.GetMouseButtonDown(1))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                Vector3 vector = hit.point;
                Vector4 vector4 = new Vector4(vector.x, vector.y, vector.z, 1);
                custom.Pos = vector4;
                custom.Strength = 0;
            }
        }
    }
    private void LateUpdate()
    {
        custom.Strength += Time.deltaTime*10;
        custom.SetParam();
    }
}

3.在built-in里的实现方法

shader

Shader "Unlit/ScanShaderBuiltIn"
{
    Properties
    {

        _MainTex("Base (RGB)", 2D) = "white" {} // 主纹理
        _CentorPoint("CentrePoint",Vector) = (0, 0, 0, 0)
        _Color("color",Color) = (1,1,1,1)   //颜色,一般用fixed4
        _Interval("Interval", Float) = 1
    }
    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 pos : SV_POSITION;
                 float4 interpolatedRay : TEXCOORD2; // 插值射线向量(由相机指向近平面上点的向量除以near后的坐标)
            };
            float4x4 _FrustumCornersRay; // 视锥体四角射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)
            sampler2D _MainTex; // 主纹理
            float _Interval;//间距
            float _Strength;//强度
            float _InverseZ;
            sampler2D _CameraColorTexture;
            sampler2D _CameraDepthTexture;
            float4 _CentorPoint;
            fixed4 _Color;

             float4 getInterpolatedRay(half2 uv) { // 获取插值射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)
                int index = 0;
                if (uv.x < 0.5 && uv.y < 0.5) {
                    index = 0;
                }
                else if (uv.x > 0.5 && uv.y < 0.5) {
                    index = 1;
                } else if (uv.x > 0.5 && uv.y > 0.5) {
                    index = 2;
                } else {
                    index = 3;
                }
                return _FrustumCornersRay[index];
            }
            v2f vert (appdata v)
            {
                v2f o;
                o.uv = v.uv;
                o.pos = UnityObjectToClipPos(v.vertex); //MVP变换
                 o.interpolatedRay = getInterpolatedRay(v.uv); 
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col2 = tex2D(_MainTex, i.uv);
                float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);//采样深度图
               depth = LinearEyeDepth(depth); //转换为线性深度
              float3 worldPos = _WorldSpaceCameraPos + depth * i.interpolatedRay.xyz; // 顶点世界坐标


                float factor = 0;
                 float Mul = distance(_CentorPoint.xyz, worldPos.xyz);
                 float change = _Strength;
                if (depth < _ProjectionParams.z - 1) {
                    float lerp1 = smoothstep(0 + change, _Interval + change, Mul);
                    float lerp2 = smoothstep(_Interval + change, _Interval + change, Mul);
                    float dis = lerp1 - lerp2;
                    float lerpDis = smoothstep(0.99, 1, dis);
                    factor = dis;
                }

                if(Mul-change<0)
                {
                        return float4(0,0,0,0);
                    }
                    else{
                          return lerp(col2, _Color, factor);
                    }
            }
            ENDCG
        }
    }
}



控制脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScanControl : MonoBehaviour
{
    [SerializeField]
    private bool enableWave = false; // 是否开启扫描特效
    [SerializeField]
    private Shader scanShader;
    public  Material material = null; // 材质
    Vector4 _Pos = Vector4.zero;
    public Color _Color;
    float _Interval = 0.2f;//间距
    float _Strength = 0;//强度
    public Transform obj;
    // Start is called before the first frame update
    public Camera cam;

    public RenderTexture SwitchRT;
    public RenderTexture MianRT;
    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(1))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                Vector3 vector = hit.point;
                Vector4 vector4 = new Vector4(vector.x, vector.y, vector.z, 1);
                Debug.Log(vector4);
                _Pos = vector4;
                _Strength = 0;
                enableWave = true;
                obj.position = hit.point;
            }
        }
    }
    private void OnEnable()
    {
        Camera.main.depthTextureMode |= DepthTextureMode.Depth;
    }

        private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {

        if (enableWave)
        {
            Matrix4x4 frustumCorners = GetFrustumCornersRay();
            material.SetMatrix("_FrustumCornersRay", frustumCorners);
            _Strength += Time.deltaTime * 2;
            material.SetVector("_CentorPoint", _Pos);
            material.SetColor("_Color", _Color);
            material.SetFloat("_Interval", _Interval);
            material.SetFloat("_Strength", _Strength);
            Graphics.Blit(src, dest, material);
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
    private Matrix4x4 GetFrustumCornersRay()
    { // 获取插值射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)
        Matrix4x4 frustumCorners = Matrix4x4.identity;
        float fov = cam.fieldOfView;
        float near = cam.nearClipPlane;
        float aspect = cam.aspect;
        float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
        Vector3 toRight = cam.transform.right * halfHeight * aspect; // 指向右方的向量
        Vector3 toTop = cam.transform.up * halfHeight; // 指向上方的向量
        Vector3 toForward = cam.transform.forward * near; // 指向前方的向量
        Vector3 bottomLeft = (toForward - toTop - toRight) / near; // 指向左下角的射线
        Vector3 bottomRight = (toForward + toRight - toTop) / near; // 指向右下角的射线
        Vector3 topRight = (toForward + toRight + toTop) / near; // 指向右上角的射线
        Vector3 topLeft = (toForward + toTop - toRight) / near; // 指向左上角的射线
        frustumCorners.SetRow(0, bottomLeft);
        frustumCorners.SetRow(1, bottomRight);
        frustumCorners.SetRow(2, topRight);
        frustumCorners.SetRow(3, topLeft);
        return frustumCorners;
    }
}

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小生云木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值