【Unity3D】雾效

news2024/12/25 0:34:34

1 前言 

        屏幕深度和法线纹理简介中对深度和法线纹理的来源、使用及推导过程进行了讲解,激光雷达特效中讲述了一种重构屏幕像素点世界坐标的方法,本文将介绍使用深度纹理重构屏幕像素点在相机坐标系下的坐标计算方法,并使用重构后的坐标模拟雾效。

        雾效即离观察者越远的点越趋近于雾的颜色,并且雾的浓度越大。本文将使用屏幕后处理技术,计算每个顶点与相机的距离,并根据距离计算雾的浓度,依据浓度给该像素点混合原始颜色与雾效颜色。

        本文完整资源见→Unity3D全局雾效。

2 由深度纹理重构相机坐标系下坐标

        1)重构像素点在相机坐标系下坐标

        对于屏幕上的任意一点,它对应的相机坐标系中的点记为 P,对应的近裁剪平面上的点记为 Q,相机位置记为 O(坐标为 (0, 0, 0)),假设 P 点的深度为 depth(由 LinearEyeDepth 函数获取),相机到近平面的距离为 near,如下图所示。

        根据上图,可以列出以下方程组关系。其中,公式 2 由三角形相似原理得到,公式 3 由 O、P、Q 三点共线得到。

        化简得:

        Q 点在近平面上,可以通过近裁剪平面的四个角插值得到,O 和 near 为定值,因此 (OQ / near) 也可以通过插值得到。假设近裁剪平面的四个角分别为 A、B、C、D,我们将 (OA  / near)、(OB  / near)、(OC  / near)、(OD  / near) 输入顶点着色器中,光珊化会自动为我们计算插值后的 (OQ  / near)。

        如下,我们可以在插值寄存器中定义变量 interpolatedRay,用于存储向量 (OQ / near)。

struct v2f {
    float4 pos : SV_POSITION; // 裁剪空间顶点坐标
    half2 uv : TEXCOORD0; // 纹理uv坐标, 
    float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向近平面上点的向量除以near后的坐标)
};

        2)近裁剪平面四角射线向量计算

        记近裁剪平面上左下角、右下角、右上角、左上角、中心、右中心、上中心顶点分别为 A、B、C、D、Q、E、F,相机位置为 O 点,如下:

        根据几何关系,可以计算向量 OA、OB、OC、OD 如下:

        假设像机竖直方向的视野角度为 fov(通过 camera.fieldOfView 获取),屏幕宽高比为 aspect(通过 camera.aspect 获取),相机距离近裁剪平面的距离为 near(通过 camera.nearClipPlane 获取),相机向右、向上、向前方向的单位方向向量分别为 right(坐标为 (1, 0, 0))、up(坐标为 (0, 1, 0)、forward(坐标为 (0, 0, 1),则向量 OQ、QE、QF 的计算如下:

         假设摄像机竖直方向的视野角度为 fov(通过 camera.fieldOfView 获取),屏幕宽高比为 aspect(通过 camera.aspect 获取),相机距离近裁剪平面的距离为 near(通过 camera.nearClipPlane 获取),相机向右、向上、向前方向的单位方向向量分别为 right、up、forward(通过 camera.transform 组件获取),则向量 OQ、QE、QF 的计算如下:

2 雾效因子计算

        雾效因子的计算一般采样线性衰减、反比衰减、指数衰减等方法。假设顶点与相机间的距离为 dist,雾效因子为 factor,则 factor 的计算如下。

        1)线性衰减雾效因子

        其中,dist_min 和 dist_max 分别为受雾效影响的最小距离和最大距离。

        2)反比衰减雾效因子

         其中,r 为衰减速率参数。

        3)指数衰减雾效因子

        其中,r 为衰减速率参数。

3 雾效实现

        FogEffect.cs

using UnityEngine;

[RequireComponent(typeof(Camera))] // 需要相机组件
public class FogEffect : MonoBehaviour {
    public bool enableFog = true; // 是否开启雾效
    public Color fogColor = Color.red; // 雾的颜色
    [Range(0, 20)]
    public float minDist = 1f; // 雾的最近距离(线性衰减函数才生效)
    [Range(30, 1000)]
    public float maxDist = 20; // 雾的最远距离(线性衰减函数才生效)
    [Range(0, 1)]
    public float r = 0.01f; // 衰减速率参数(反比衰减和指数衰减函数才生效)
    private Camera cam; // 相机
    private Material material = null; // 材质

    private void Awake() {
        cam = GetComponent<Camera>();
        material = new Material(Shader.Find("MyShader/FogEffect"));
        material.hideFlags = HideFlags.DontSave;
    }

    private void OnValidate() {
        if (material != null) {
            material.SetFloat("_Enable", enableFog ? 1 : 0);
        }
    }

    private void OnEnable() {
        cam.depthTextureMode |= DepthTextureMode.Depth;
        material.SetFloat("_Enable", enableFog ? 1 : 0);
    }

    private void Update() {
        float scroll = Input.GetAxis("Mouse ScrollWheel");
        if (Mathf.Abs(scroll) > 0)
        { // 缩放场景
            cam.transform.position += cam.transform.forward * scroll * 10;
        }
    }

    private void OnRenderImage(RenderTexture src, RenderTexture dest) {
        if (enableFog) {
            Matrix4x4 frustumCorners = GetFrustumCornersRay();
            material.SetMatrix("_FrustumCornersRay", frustumCorners);
            material.SetColor("_FogColor", fogColor);
            material.SetFloat("_MinDist", minDist);
            material.SetFloat("_MaxDist", maxDist);
            material.SetFloat("_R", r);
            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 = Vector3.right * halfHeight * aspect; // 指向右方的向量
        Vector3 toTop = Vector3.up * halfHeight; // 指向上方的向量
        Vector3 toForward = Vector3.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;
    }
}

        FogEffect.shader

Shader "MyShader/FogEffect" { // 雷达波特效
	Properties{
		_MainTex("Base (RGB)", 2D) = "white" {} // 主纹理
		_Enable("Enable", Int) = 0 // 是否启动雾效
		_FogColor("FogColor", Color) = (1, 0, 0, 1) // 雾的颜色
		_MinDist("MinDist", Float) = 5 // 雾的最近距离(线性衰减函数才生效)
		_MaxDist("MaxDist", Float) = 300 // 雾的最远距离(线性衰减函数才生效)
		_R("Density", Float) = 0.01 // 衰减速率参数(反比衰减和指数衰减函数才生效)
	}

	SubShader{
		Pass {
			// 深度测试始终通过, 关闭深度写入
			ZTest Always ZWrite Off

			CGPROGRAM

			#include "UnityCG.cginc"

			#pragma vertex vert
			#pragma fragment frag

			sampler2D _MainTex; // 主纹理
			sampler2D _CameraDepthTexture; // 深度纹理
			float4x4 _FrustumCornersRay; // 视锥体四角射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)
			int _Enable; // 是否启动雾效
			fixed4 _FogColor; // 雾的颜色
			float _MinDist; // 雾的最近距离(线性衰减函数才生效)
			float _MaxDist; // 雾的最远距离(线性衰减函数才生效)
			float _R; // 衰减速率参数(反比衰减和指数衰减函数才生效)

			struct v2f {
				float4 pos : SV_POSITION; // 裁剪空间顶点坐标
				half2 uv : TEXCOORD0; // 纹理uv坐标, 
				float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向近平面上点的向量除以near后的坐标)
			};

			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];
			}

			float getFactory(float len) { // 获取雾效因子
				float factor = saturate((_MaxDist - len) / (_MaxDist - _MinDist)); // 线性雾效衰减因子
				//float factor = 1 / (_R * len); // 反比雾效衰减因子
				//float factor = exp(-_R * len); // 指数雾效衰减因子
				return factor;
			}

			v2f vert(appdata_img v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex); // 计算裁剪坐标系中顶点坐标, 等价于: mul(unity_MatrixMVP, v.vertex)
				o.uv = v.texcoord;
				o.interpolatedRay = getInterpolatedRay(v.texcoord); // 获取插值射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)
				return o;
			}

			fixed4 frag(v2f i) : SV_Target{ // v2f_img为内置结构体, 里面只包含pos和uv
				if (_Enable == 0) {
					return tex2D(_MainTex, i.uv);
				}
				float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); // 非线性的深度, tex2D(_CameraDepthTexture, i.uv).r
				float linearDepth = LinearEyeDepth(depth); // 线性的深度
				//if (linearDepth > _ProjectionParams.z - 2) {
				//	return tex2D(_MainTex, i.uv); // 天空不参与雾效
				//}
				float3 viewPos = linearDepth * i.interpolatedRay.xyz; // 顶点在相机坐标系下的坐标(不是观察坐标系, 观察坐标系与相机坐标系z轴方向相反)
				float len = length(viewPos);
				float factor = getFactory(len); // 获取雾效因子
				fixed4 tex = tex2D(_MainTex, i.uv);
				fixed4 color = lerp(_FogColor, tex, factor);
				return color;
			}

			ENDCG
		}
	}

	FallBack off
}

        运行效果:

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

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

相关文章

EventBus

EventBus 文章目录 EventBus1.EventBus的作用2.关于EventBus的概述3.EventBus的使用方法4.EventBus的黏性事件5.EventBus的源码EventBus的构造方法getDefault()源码EventBus()源码 订阅者注册register()源码findSubscriberMethods()源码findUsingInfo()源码findUsingReflection…

TCP的三次握手与四次挥手

TCP的三次握手与四次挥手 1.网络分层 网络分层代表硬件协议/技术特性应用层HTTP,DNS,FTP,SMTP,Telnet协议等应用程序实现的,规定应用程序的数据格式传输层TCP/UDP协议负责两主机之间的数据正确传输主机系统内核实现的网络层路由器IP协议负责地址管理和路由选择(确定对应主机)…

合宙Air724UG Cat.1模块硬件设计指南--SDIO接口

SDIO接口 简介 SDIO(Secure Digital Input and Output)全称为安全数字输入输出接口&#xff0c;在协议上和SPI类似是一种串行的硬件接口&#xff0c;通信的双方一个作为HOST&#xff0c;另一端是Device&#xff0c;所有的通信都是由HOST端发送命令开始的&#xff0c;Device端只…

SpringCloud Alibaba入门3之nacos服务搭建

在前一章的基础上开发:SpringCloud Alibaba入门之用户子模块开发_qinxun2008081的博客-CSDN博客 一、下载nacos-server 从https://github.com/alibaba/nacos/releasesopen in new window 下载nacos-server发行版。 二、启动nacos 进入%path%\nacos\bin文件夹,执行cmd命令st…

阿里组织变革新阶段:蓄力拉弓,一箭向前

自3月28日宣布“16N”分拆、5月18日宣布分业务启动独立融资或上市计划以来&#xff0c;阿里持续推动着这场史无前例的组织变革落地&#xff0c;谋求更高质量发展。 6月20日&#xff0c;阿里巴巴控股集团董事会主席兼CEO张勇通过全员信宣布&#xff0c;他将于今年9月10日卸任现…

[进阶]网络通信:端口和协议

端口 标记正在计算机设备上运行的应用程序的&#xff0c;被规定为一个 16位的二进制&#xff0c;范围是 0~65535。 分类 周知端口&#xff1a;0~1023&#xff0c;被预先定义的知名应用占用&#xff08;如&#xff1a;HTTP占用 80&#xff0c;FTP占用21&#xff09;注册端口&…

java中抽象类和抽象方法

文章目录 前言 一、抽象类和抽象方法是什么&#xff1f; 1.抽象类 2.抽象方法 二、使用方法 1.实操展示 2.注意事项 总结 前言 苹果这个具体的水果&#xff0c;它具有的属性为&#xff0c;红色&#xff1b;它具有的方法为&#xff0c;被啃。那么&#xff0c;水果&#…

system Verilog 验证测试平台编写指南——读书笔记(持续更新)

第一章 验证导论 1、基本测试平台的功能 测试平台的用途在于确定待测设计的正确性。包含下列步骤&#xff1a; &#xff08;1&#xff09;产生激励。 &#xff08;2&#xff09;把激励施加到DUT上. &#xff08;3&#xff09;捕捉响应。 &#xff08;4&#xff09;检验正…

【Visual Studio】开发 Qt 时右键没有自动添加 slots 槽的功能,使用 C++ 语言,配合 Qt 开发串口通信界面

知识不是单独的&#xff0c;一定是成体系的。更多我的个人总结和相关经验可查阅这个专栏&#xff1a;Visual Studio。 文章目录 Ref. 基于 Visual Studio 环境下使用 Qt&#xff0c;发现右键没有自动添加槽的功能&#xff0c;需要自己想办法。我看网上介绍了不止一种方法&#…

Micrometer实战

Micrometer 为基于 JVM 的应用程序的性能监测数据收集提供了一个通用的 API&#xff0c;支持多种度量指标类型&#xff0c;这些指标可以用于观察、警报以及对应用程序当前状态做出响应。 前言 可接入监控系统 监控系统的三个重要特征&#xff1a; 维度&#xff08;Dimensio…

事务的历史与SSI——PostgreSQL数据库技术峰会成都站分享

前言 PostgreSQL数据库技术峰会成都站 近期&#xff08;2023年6月17日&#xff09;&#xff0c;由中国开源软件推进联盟PG分会发起的“PostgreSQL数据库技术峰会成都站”圆满举行。我也有幸作为演讲嘉宾参加了此次峰会&#xff0c;收获很多。 &#xff08;分会回顾和所有pp…

Qt编写监控实时显示和取流回放工具(回放支持切换进度)

一、前言 现在各个监控大厂做的设备&#xff0c;基本上都会支持通过rtsp直接取流显示&#xff0c;而且做的比较好的还支持通过rtsp回放取流&#xff0c;基本上都会约定一个字符串的规则&#xff0c;每个厂家都是不一样的规则&#xff0c;比如回放对应的rtsp地址还要带上时间范…

Java8 List集合如何指定打印分隔符

目录 背景方法一&#xff1a;String.join&#xff08;推荐&#xff09;方法二&#xff1a;Collectors.joining总结 背景 无论是在学习还是日常的应用开发过程中&#xff0c;我们经常会需要使用分隔符将 List 集合打印出来。 如下所示&#xff1a; import java.util.Arrays;pub…

数据结构与算法基础(青岛大学-王卓)(5)

叮叮咚咚&#xff0c;新一期来袭&#xff0c;我还在吃桃子&#xff0c;吃桃子&#xff0c;吃桃子。。。串和python的字符串差不多,数组和广义表像是python的list 文章目录 串(string) - 字符串概念及术语串的类型定义存储结构&#xff08;同线性表&#xff09;串的模式匹配算法…

leetcode46. 全排列(回溯算法-java)

全排列 leetcode46. 全排列题目描述解题思路代码演示 回溯算法专题 leetcode46. 全排列 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/permutations 题目描述 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有…

关于平差中误差方程 v=Bx-l 的探讨

文章目录 Part.I IntroductionPart.II 误差方程的探讨Chap.I 符号表示Chap.II 误差方程的含义Chap.III 误差方程的其他形式Chap.IV 平差的大致流程 Part.III 误差方程的表示形式 Part.I Introduction 在平时阅读文献或者整理笔记时&#xff0c;经常会看到各种各样的有关误差方…

Git安装详细教程(win11)

Git安装详细教程&#xff08;win11&#xff09; 一、下载二、安装三、配置 一、下载 官网下载&#xff1a;点击下载 网盘下载&#xff1a;点击下载 二、安装 双击程序运行&#xff0c;点击next 选择安装路径&#xff0c;我安装在了D盘&#xff0c;如下图所示&#xff0c;…

Server - 测试 GPU 的显卡使用率与张量之间的关系

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/131331049 NVIDIA A100 是一款基于 Ampere 架构的高性能 GPU&#xff0c;专为 AI、数据分析和高性能计算等应用场景设计。NVIDIA A100 具…

【Java入门基础第10天】Java程序结构

Java程序结构 1、Java程序结构什么是Java程序结构&#xff1f; 2、Java注释1、单行注释2、多行注释3、多行注释 1、Java程序结构 什么是Java程序结构&#xff1f; 程序是由程序块组成的&#xff0c;所谓程序块&#xff0c;指的是使用一对{ } 包含若干的代码的总称。程序块是由…

【Python 基础篇】Python 变量与数据类型以及数据类型转换

文章目录 引言一、变量和常见数据类型1. 变量2. 常见数据类型 二、数据类型转换结论 引言 Python 是一种广泛应用于各个领域的高级编程语言&#xff0c;其灵活性和易用性使其成为众多开发者的首选。在 Python 中&#xff0c;变量是程序中存储数据的基本单元&#xff0c;而数据…