利用深度纹理实现全局雾效

news2024/12/23 14:03:42

1、为什么要实现屏幕后处理效果的全局雾效

既然Unity中已经提供了全局雾效,那为什么还要自己来实现呢?主要是因为Unity自带的全局雾效有以下几个缺点:

  • 需要为每个自定义Shader按规则书写雾效处理代码
  • 自带的全局雾效无法实现一些自定义效果,比如:
    • 基于高度的雾效 - 可以用来做出悬浮的水雾效果
    • 不规则的雾效(结合噪声图实现) - 可以为雾增加随机性和不规则形
    • 动态变化的雾、基于纹理的雾 等等

总体而言,就是Unity自带的全局雾效只能满足最基础的效果,较为局限。

因此通过结合深度纹理来制作屏幕后处理的全局雾效,来感受同一种效果的不同实现思路。

而基于深度纹理的全局雾效,它相对于Unity自带的全局雾效的好处是:

  • 一次屏幕后处理便可以得到雾的效果,不用为每个自定义Shader添加雾效代码
  • 可以基于该全局雾效拓展出多种雾效,可以方便的模拟出线性、指数、指数平方雾效,甚至实现一些基于高度的雾效、使用噪声图的雾效、动态变化的雾效等等

2、基本原理

想要实现基于深度纹理的屏幕后处理的全局雾效的关键点就是如何利用深度纹理来获得每个像素在世界空间下的位置,这样才能计算出离摄像机的距离,才能利用雾的计算公式计算混合因子来实现雾效!

在【基于深度纹理实现的运动模糊】相中,也利用了深度纹理获取了像素点在世界空间下的位置

但是这种做法有一个很大的缺点,那就是性能消耗较高,原因主要有以下两点:

  • 在片元着色器函数中进行计算,计算次数较大
  • 每次都进行了矩阵变换计算,计算量较大

因此在实现全局雾效时将不会使用这种方式,而是使用一种性能更好的计算方式!

这种性能更好的新方法的主要思路还是利用深度纹理来获得每个像素在世界空间下的位置
除此以外我们还需要获得摄像机指向像素对应世界坐标的方向向量
利用坐标偏移的方式得到像素的世界坐标
像素的世界坐标 = 摄像机位置 + 观察空间线性深度值 * 摄像机指向像素世界坐标的方向向量
而其中摄像机位置已知,观察空间线性深度值已知(从深度纹理中采样后计算)
那么关键点就是讲解如何计算出 摄像机指向像素世界坐标的方向向量

关键思路:

  • 顶点着色器中
    • 屏幕后处理中处理的内容是一张抓取的屏幕图像,相当于是一个面片,它具有4个顶点(四个角),这四个顶点即顶点着色器中处理的所有顶点
      • 我们其实可以认为屏幕后处理中处理的屏幕图像的四个顶点,就是摄像机视锥体中近裁剪面的四个角,因为在裁剪空间变换中,我们是将观察空间变换到了裁剪空间再到NDC空间中,最终又变换到了屏幕空间中,可以理解相当于把近裁剪面变换到了屏幕空间中。因此近裁剪面的四个角相当于是屏幕图像四个顶点在世界空间下的位置
    • 通过C#代码计算四个顶点在世界坐标系下的射线方向后传递给顶点着色器
      • 这一步我们可以在C#中计算好,然后将结果作为参数传递到Shader的变量中,在顶点着色器中使用即可:
      • halfH = Near * tan(FOV/2) halfW = halfH * aspect
      • toTop = Camera.up * halfH
      • toTop = Camera.up * halfH
      • TL = Camera.forward * Near + toTop – toRight
      • TR = Camera.forward * Near + toTop + toRight
      • BL = Camera.forward * Near - toTop – toRight
      • BR = Camera.forward * Near - toTop + toRight

推导出来了四个顶点的方向向量,我们是不是就可以利用它们得到四个顶点的世界空间下坐标了呢?比如得到左上角的方向向量的单位向量,然后乘以左上角顶点对应像素点的深度值:

左上角像素点对应世界坐标 = 摄像机位置 + TL.Normalized * Depth ;
注意,如果这样去计算,那么得到的结果是错误的!!!!
因为深度值Depth即使我们将其转换为观察空间下的线性值,它表示的也是离摄像机在Z轴方向的距离,并不是两点之间的距离(欧式距离),因此我们还需要对该向量进行处理

我们可以利用相似三角形的原理,推导出深度值和两点之间距离(欧式距离)的关系。

而 左上角像素点对应世界坐标 = 摄像机位置 + TL.Normalized * Depth 就变为了:

  • 左上角像素点对应世界坐标 = 摄像机位置 + TL.Normalized * |TL|/Near * Depth

那也就意味着,真正最终和深度一起计算的确定世界坐标位置的方向向量其实就是::TL.Normalized * |TL|/Near
由于近裁剪面4个点是对称的, |TL|/Near 可以通用 ,只需要变换前面的单位向量即可。

通过推导,我们已经得到了四个顶点对应的方向向量信息了

  • Scale = |TL|/Near
  • RayTL = TL.Normalized * Scale
  • RayTR = TR.Normalized * Scale
  • RayBL = BL.Normalized * Scale
  • RayBR = BR.Normalized * Scale

我们只需要在顶点着色器中根据顶点的位置设置对应的向量即可!

  • 片元着色器中:当数据传递到片元着色器要处理每个像素时,像素对应的射线方向是基于4个顶点的射线插值计算而来(无需我们自己计算)
    • 利用 像素世界坐标 = 摄像机位置 + 深度值 * 世界空间下射线方向 得到对应像素在世界空间下位置
      • 已经有了对应的射线,直接从深度纹理中采样获取深度值,并利用 LinearEyeDepth 内置函数得到像素到摄像机的实际距离 便可以利用上面的公式进行计算得到每个像素在世界空间下的位置了
    • 利用得到的世界空间下位置利用雾的公式计算出对应雾效颜色
      • 有了世界空间下的位置,我们就可以利用雾的计算公式进行雾效的混合因子计算
        利用算出的雾效混合因子参与雾颜色和像素颜色的混合运算即可

3、实现

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

public class FogWithDepthTexture : PostEffectBase
{
    // 雾的颜色
    public Color fogColor = Color.gray;
    // 雾的浓度
    [Range(0, 3)]
    public float fogDensity = 1f;
    public float fogStart = 0f;
    public float fogEnd = 5f;
    // 4x4 的矩阵用于传递4个角的向量参数
    private Matrix4x4 rayMatrix;

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

    protected override void UpdateProperty() {
        if (material != null) {
            float fov = Camera.main.fieldOfView / 2f;
            float near = Camera.main.nearClipPlane;
            float aspect = Camera.main.aspect;

            float halfH = near * Mathf.Tan(fov * Mathf.Deg2Rad);
            float halfW = halfH * aspect;

            // 计算竖直向上的和水平向右的偏移向量
            Vector3 toTop = Camera.main.transform.up * halfH;
            Vector3 toRight = Camera.main.transform.right * halfW;
            // 算出指向四个顶点的向量
            Vector3 TL = Camera.main.transform.forward * near + toTop - toRight;
            Vector3 TR = Camera.main.transform.forward * near + toTop + toRight;
            Vector3 BL = Camera.main.transform.forward * near - toTop - toRight;
            Vector3 BR = Camera.main.transform.forward * near - toTop + toRight;
            //为了让深度值 计算出来是两点间距离 所以需要乘以一个缩放值
            float scale = TL.magnitude / near;
            //真正的最终想要的四条射线向量
            TL = TL.normalized * scale;
            TR = TR.normalized * scale;
            BL = BL.normalized * scale;
            BR = BR.normalized * scale;

            rayMatrix.SetRow(0, BL);
            rayMatrix.SetRow(1, BR);
            rayMatrix.SetRow(2, TR);
            rayMatrix.SetRow(3, TL);

            //设置材质球相关属性(Shader属性)
            material.SetColor("_FogColor", fogColor);
            material.SetFloat("_FogDensity", fogDensity);
            material.SetFloat("_FogStart", fogStart);
            material.SetFloat("_FogEnd", fogEnd);
            material.SetMatrix("_RayMatrix", rayMatrix);
        }
    }
}

 

Shader "ShaderProj/13/fogWithDepthTexture"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _FogColor ("FogColor", Color) = (1,1,1,1)
        _FogDensity ("FogDensity", Float) = 1
        _FogStart ("FogStart", Float) = 0
        _FogEnd ("FogEnd", Float) = 10
    }
    SubShader
    {
        ZTest Always
        Cull Off
        ZWrite Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float2 uv_depth : TEXCOORD1;
                //顶点射线 指向四个角的方向向量 (传递到片元时 会自动进行插值 运算)
                float4 ray : TEXCOORD2;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            half4 _MainTex_TexelSize;
            sampler2D _CameraDepthTexture;
            fixed4 _FogColor;
            fixed _FogDensity;
            float _FogStart;
            float _FogEnd;
            //矩阵相关 里面存储了 4条射线向量
            //0-左下 1-右下 2-右上 3-左上
            float4x4 _RayMatrix;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                o.uv_depth = v.texcoord;
                
                //顶点着色器函数 每一个顶点都会执行一次
                //对于屏幕后处理来说 就会执行4次 因为有4个顶点 (4个角)
                //通过uv坐标判断 当前的顶点位置
                int index = 0;
                if(v.texcoord.x < 0.5 && v.texcoord.y < 0.5)
                    index = 0;
                else if(v.texcoord.x > 0.5 && v.texcoord.y < 0.5)
                    index = 1;
                else if(v.texcoord.x > 0.5 && v.texcoord.y > 0.5)
                    index = 2;
                else
                    index = 3;
                //判断 是否需要进行纹理翻转 如果翻转了 深度的uv和对应顶点需要变化
                #if UNITY_UV_STARTS_AT_TOP
                if(_MainTex_TexelSize.y < 0)
                {
                    o.uv_depth.y = 1 - o.uv_depth.y;
                    index = 3 - index;
                }
                #endif

                //根据顶点的位置 决定使用那一个射线向量
                o.ray = _RayMatrix[index];
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //观察空间下 离摄像机的实际距离(Z分量)
                float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
                //计算世界空间下 像素的坐标
                float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.ray;
                //混合因子 
                float f = (_FogEnd - worldPos.y)/(_FogEnd - _FogStart);
                f = saturate(f * _FogDensity);
                fixed3 color = lerp(tex2D(_MainTex, i.uv).rgb, _FogColor.rgb, f);

                return fixed4(color.rgb, 1);
            }
            ENDCG
        }
    }
    Fallback Off
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2264261.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决“SVN无法上传或下载*.so、*.a等二进制文件“问题

今天&#xff0c;在使用Subversion提交代码到服务器时&#xff0c;发现无法提交*.a、*.so等二进制文件&#xff0c;右击这些文件&#xff0c;发现其属性为ignores。     问题原因&#xff1a;SVN的配置文件里&#xff0c;屏蔽了*.a、*.so文件的上传与下载&#xff0c;并把这些…

Linux下学 〖MySQL 〗表的属性之约束条件(下)(自增长auto_increament、唯一键unique key、外键foreign key)

绪论​ 每日激励&#xff1a;“不是看到希望才坚持&#xff0c;而是坚持了才有希望。—Jack” 绪论​&#xff1a; 本章是约束的下半篇&#xff0c;它将带你认识到一些在字段中非常常用的约束条件&#xff0c;自增长AUTO_CREMENT、唯一键UNIQUE KEY 和 外键FOREIGN KEY这三个。…

观察者模式(sigslot in C++)

大家&#xff0c;我是东风&#xff0c;今天抽点时间整理一下我很久前关注的一个不错的库&#xff0c;可以支持我们在使用标准C的时候使用信号槽机制进行观察者模式设计&#xff0c;sigslot 官网&#xff1a; http://sigslot.sourceforge.net/ 本文较为详尽探讨了一种观察者模…

内置函数.

日期函数 current_date/time() 日期/时间 获得年月日&#xff1a; 获得时分秒&#xff1a; 获得时间戳&#xff1a;日期时间 now()函数 体会date(datetime)的用法&#xff1a;只显示日期 在日期的基础上加日期&#xff1a;按照日历自动计算 关键字为 intervalinterval 后的数值…

web实验三

web实验三 三四个小时左右吧&#xff0c;做成功了学到新东西了&#xff0c;还是挺有趣的&#xff0c;好玩。还有些功能没做完&#xff0c;暂时这样了&#xff0c;要交了。 html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF…

36. Three.js案例-创建带光照和阴影的球体与平面

36. Three.js案例-创建带光照和阴影的球体与平面 实现效果 知识点 Three.js基础 WebGLRenderer WebGLRenderer 是Three.js中最常用的渲染器&#xff0c;用于将场景渲染到网页上。 构造器 new THREE.WebGLRenderer(parameters)参数类型描述parametersobject可选参数&#…

windows平台小熊猫C++安装fmt库

前言 Windows安装C第三方库有点不容易&#xff0c;使用VS的可能还好点&#xff0c;有vcpkg工具用。使用其他工具的就麻烦了&#xff0c;因为基本上第三方库&#xff0c;都默认你是Linux平台&#xff0c;给的安装方式教程都是清一色Cmake&#xff0c;Windows上没有cmake&#x…

信号处理相关的东东(学习解惑)

信号处理相关的东东&#xff08;学习解惑&#xff09; 所有内容学习自知乎专栏&#xff0c;https://www.zhihu.com/column/xinhao&#xff0c;写的很好&#xff0c;值得反复学习 时频域分析的一些常用概念 FROM&#xff1a;https://zhuanlan.zhihu.com/p/35742606 1、相加性…

pset4filter less: helpers.c

&#xff08;&#xff14;&#xff09;blur function 简单画图熟悉一下要做什么 可以看到3种情况&#xff0c;顶格&#xff0c;边界&#xff0c;里面如果分开算的话&#xff0c;是真的麻烦&#xff1b;但是当时还真的没有想到更好的&#xff0c;就先写一写&#xff08;此处摘取…

3. Kafka入门—安装与基本命令

Kafka基础操作 一. 章节简介二. kafka简介三. Kafka安装1. 准备工作2. Zookeeper安装2.1 配置文件2.2 启动相关命令3. Kafka安装3.1 配置文件3.2 启动相关命令-------------------------------------------------------------------------------------------------------------…

某政银行APP登陆逆向

版本 V10.0.0 环境检测 {"xposed": {"action": "warn_and_exit","msg": {"zh_CN": "检测到您的设备安装有Xposed框架&#xff0c;存在非法攻击风险&#xff01;"},"button": {"zh_CN": &qu…

51c自动驾驶~合集42

我自己的原文哦~ https://blog.51cto.com/whaosoft/12888355 #DriveMM 六大数据集全部SOTA&#xff01;最新DriveMM&#xff1a;自动驾驶一体化多模态大模型&#xff08;美团&中山大学&#xff09; 近年来&#xff0c;视觉-语言数据和模型在自动驾驶领域引起了广泛关注…

算法题(12): 特殊年份

审题&#xff1a; 需要输出特殊年份的个数 思路&#xff1a; 获取数据&#xff1a;用字符串获取&#xff0c;然后全部加到总字符串s上判断 使用for循环对每一个四位数年分进行判断&#xff0c;如果是特殊年份就让负责记录的cou变量 解题&#xff1a; 注意&#xff1a;为什么我们…

RuoYi-Vue 数据权限控制示例nvliz (作业机器版)

目录 需求分析 ​编辑建表 代码编写 service层 Mapper层 测试 修改数据权限 添加数据 需求分析 建表 在若依的数据库中建立设备表&#xff1a;equipment 代码编写 使用代码生成&#xff0c;设备管理信息界面&#xff0c;如下图&#xff1a; 使用RuoYi的代码生成的功…

汽车IVI中控开发入门及进阶(三十八):手机投屏HiCar开发

手机投屏轻松实现手机与汽车的无缝连接,导航、音乐、通话等功能应有尽有,还支持更多第三方应用,让车载互联生活更加丰富多彩。 HiCar在兼容性和开放性上更具优势。 手机投屏可以说是车机的杀手级应用,大大拓宽了车机的可用性范围。其中华为推出的HiCar就是非常好用的一种。…

数据结构经典算法总复习(下卷)

第五章:树和二叉树 先序遍历二叉树的非递归算法。 void PreOrderTraverse(BiTree T, void (*Visit)(TElemType)) {//表示用于查找的函数的指针Stack S; BiTree p T;InitStack(S);//S模拟工作栈while (p || !StackEmpty(S)) {//S为空且下一个结点为空&#xff0c;意味着结束遍…

PID 控制算法理论背景:飞控领域的核心调控机制(1)

在飞控工程领域&#xff0c;PID 控制算法占据着极为关键的地位&#xff0c;是实现飞行器精确稳定控制的基石。PID 作为比例&#xff08;P&#xff09;、积分&#xff08;I&#xff09;、微分&#xff08;D&#xff09;的集成代表&#xff0c;构建起了控制系统的核心架构&#x…

插入排序与计数排序详解

在 C 编程中&#xff0c;排序算法是非常基础且重要的知识。今天我们就来深入探讨两种常见的排序算法&#xff1a;插入排序和计数排序&#xff0c;包括它们的代码实现、时间复杂度、空间复杂度、稳定性分析以及是否有优化提升的空间。 一、插入排序 插入排序&#xff08;Inser…

示波器--UNI-T 优利德 UT4102C 使用介绍

示波器--UNI-T 优利德 UT4102C 使用介绍 1 介绍图示特点 2 UTP04示波器探头&#xff08;100M带宽&#xff09;3 功能介绍4 示例RS232 电平信号测试 参考 1 介绍 图示 特点 2GS/s的实时采样率&#xff1a;设备能够以每秒2吉萨&#xff08;Giga Samples per second&#xff09;…

【Mybatis-Plus】连表查询 逻辑删除 多租户

文章目录 连表查询逻辑删除多租户 连表查询 引入 mybatis-plus-join-boot-starter 依赖 <dependency><groupId>com.github.yulichang</groupId><artifactId>mybatis-plus-join-boot-starter</artifactId><version>1.5.1</version>…