当前位置: 首页 > article >正文

利用深度纹理实现运动模糊

利用 深度纹理实现运动模糊效果 的这种方式有以下两点需要注意:

  • 这种实现方式 只适合 场景静止,即摄像机快速移动的情况,

它不太适用于物体快速移动产生的运动模糊效果,只有摄像机移动时才能看到运动模糊效果

  • 这种实现方式 并不是基于真实的物理运动规律来计算的,只是一种近似计算!

它符合图形学基本规则:看起来对那么就是对的
虽然它使用上有局限性,并且实现上也不符合物理计算规则,
但是由于它实现出来的效果和性能消耗都还不错,因此它也是一种常用的运动模糊处理方案。

1、基本原理

得到像素当前帧和上一帧中在裁剪空间下的位置,利用两个位置计算出物体的运动方向,从而模拟出运动模糊的效果。
其中的关键点:

  1. 如何得到像素当前帧和上一帧在裁剪空间下的位置
  2. 如何得到运动方向
  3. 如何模拟运动模糊效果

如何得到像素当前帧和上一帧在裁剪空间下的位置

关键步骤:

  1. 利用UV坐标和深度值组合成一个裁剪空间下的组合坐标 nowClipPos
  2. 利用 这一帧 世界空间—>裁剪空间 的变换矩阵 nowM 的逆矩阵,将刚才裁剪空间下的组合坐标nowClipPos转换到世界空间中
  3. 利用 上一帧的 世界空间—>裁剪空间 的变换矩阵oldM得到,上一帧该世界空间下的组合坐标oldClipPos在裁剪空间下的位置

步骤一:

利用UV坐标和深度值组合成一个裁剪空间下的组合坐标 nowClipPos

  • float4 clipPos = float4(uv.x, uv.y, depth, 1);

uv坐标空间下的值是0~1,通过宏取出来的深度值也是0~1,而裁剪空间下的坐标范围是-1~1
因此,我们需要将它利用简单的公式转换到裁剪空间坐标系下

  • float4 clipPos = float4(uv.x * 2 - 1, uv.y * 2 - 1, depth * 2 - 1, 1);

可以认为这一步是把带有深度值的uv坐标转换到了裁剪空间下,之所以加入深度值是为了让之后的裁剪空间转到世界空间或其他坐标空间更合理,相当于是把UV的2D坐标系转换到了3D坐标系中

步骤二:

利用 世界空间—>裁剪空间 的变换矩阵的逆矩阵,将刚才裁剪空间下的nowClipPos转换到世界坐标中

只需要利用上面的这个裁剪空间到世界空间的变换矩阵,就可以将刚才得到的裁剪空间下的组合坐标变换到世界空间下了

步骤三:

利用上一帧的世界坐标—>裁剪空间的变换矩阵得到上一帧该世界空间下的组合坐标在裁剪空间下位置

如何得到运动方向

得到像素当前帧和上一帧在裁剪空间下的位置后,直接用 当前位置.xy – 上一帧位置.xy 便可以得到移动方向

如何模拟运动模糊效果

有了像素在裁剪空间的移动方向,相当于知道了像素在“uv纹理空间的移动方向”吗,那么只需要利用这个方向在纹理中进行多次uv坐标偏移采样后,将得到的颜色累加起来,最后进行算数平均值计算即可,会加入一个 模糊偏移量 来控制模糊程度,只需要在每次采样时进行 方向 * 模糊偏移量 的偏移采样即可

2、实现

Shader "ShaderProj/13/MotionBlurWithDepthTexture"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        // 用于控制模糊程度的模糊偏移量
        _BlurSize ("BlurSize", Float) = 0.5 
    }

    SubShader
    {
        Pass
        {
            ZTest Always
            ZWrite Off
            Cull Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"


            struct v2f
            {
                float2 uv : TEXCOORD0;
                float2 uv_depth : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            sampler2D _CameraDepthTexture;
            float4x4 _ClipToWorldMatrix;          // 裁剪空间到世界空间的变换矩阵
            float4x4 _FrontWorldToClipMatrix;   // 上一帧世界空间到裁剪空间的变换矩阵
            float _BlurSize;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.vertex;
                o.uv_depth = v.vertex;
                #if UNITY_UV_STARTS_AT_TOP
                    if (_MainTex_TexelSize.y < 0) 
                    { 
                        o.uv_depth.y = 1 - o.uv_depth.y;
                    }
                #endif

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 得到裁剪空间下的两个点
                float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
                float4 nowClipPos = float4(i.uv * 2 - 1, depth * 2 - 1, 1);
                float4 worldPos = mul(_ClipToWorldMatrix, nowClipPos);
                worldPos /= worldPos.w;

                float4 oldClipPos = mul(_FrontWorldToClipMatrix, worldPos);
                oldClipPos /= oldClipPos.w;

                // 得到运动方向
                // 注意事项:让移动方向向量除以2,从而降低运动模糊效果的强度,不要过于强烈
                float2 moveDir = (nowClipPos.xy - oldClipPos.xy) / 2;

                // 进行模糊处理
                float2 uv = i.uv;
                float4 color = float4(0, 0, 0, 0);
                for (int it = 0; it < 3; it++)
                {
                    color += tex2D(_MainTex, uv);
                    uv += moveDir * _BlurSize;
                }
                color /= 3;
                return fixed4(color.rgb, 1);
            }
            ENDCG
        }
    }
    Fallback Off
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MotionblurWithDepthTexture : PostEffectBase
{
    [Range(0, 1)]
    public float blurSize = .5f;
    private Matrix4x4 frontWorldToClipMatrix;

    // Start is called before the first frame update
    void Start()
    {
        Camera.main.depthTextureMode = DepthTextureMode.Depth;
    }

    private void OnEnable() 
    {
        // 每次脚本激活时 初始化一次
        frontWorldToClipMatrix = Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix;
    }

    protected override void OnRenderImage(RenderTexture source, RenderTexture destination) {
        if (material != null) {
            material.SetFloat("_BlurSize", blurSize);
            material.SetMatrix("_FrontWorldToClipMatrix", frontWorldToClipMatrix);
            frontWorldToClipMatrix = Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix;
            material.SetMatrix("_ClipToWorldMatrix", frontWorldToClipMatrix.inverse);
            Graphics.Blit(source, destination, material);
        }
        else
            Graphics.Blit(source, destination);
    }
}


http://www.kler.cn/a/429335.html

相关文章:

  • Jupyter notebook中运行dos指令运行方法
  • HTML中link的用法
  • 安装指南:LLaMA Factory、AutoGPTQ 和 vllm
  • 45_Lua模块与包
  • 2024年11月架构设计师综合知识真题回顾,附参考答案、解析及所涉知识点(一)
  • RV1126+FFMPEG推流项目(7)AI音频模块编码流程
  • 17.字符串大小比较
  • 组件上传图片不回显问题
  • 反向代理后Request.Url.AbsoluteUri获取成了内网IP
  • YOLOv8改进,YOLOv8引入CARAFE轻量级通用上采样算子,助力模型涨点
  • mHand Pro动捕数据手套,赋予手部虚拟交互沉浸式极致体验
  • Ubuntu防火墙管理(六)——ARP防火墙过滤防御自定义系统服务
  • RFDiffusion中的ContigMap类介绍
  • linux 命令获取apk 的安装应用的包名
  • 使用ssh免密登录实现自动化部署rsync+nfs+lsync(脚本)
  • 20 设计模式之职责链模式(问题处理案例)
  • Android 事件分发机制详解/ 及Activity启动流程浅谈
  • Flutter如何调用java接口如何导入java包
  • 【数据结构】堆的概念、结构、模拟实现以及应用
  • SQL注入:sqli-labs靶场通关(第九关 时间盲注)
  • 【单元测试】单元测试介绍
  • Java 装饰器模式详解:动态增强对象功能
  • 宝塔面板-java项目 spring 无法正常启动 java spring 宝塔 没有显示日志 问题解决方案-spring项目宝塔面板无日志
  • 如何实现 3D GPR的仿真模拟
  • Scala 隐式转换
  • 【前端】JavaScript 的装箱(Boxing)机制详解