【Unity3D】雾效

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{
				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
}

        运行效果:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

驱动开发:基于事件同步的反向通信

在之前的文章中LyShark一直都在教大家如何让驱动程序与应用层进行正向通信&#xff0c;而在某些时候我们不仅仅只需要正向通信&#xff0c;也需要反向通信&#xff0c;例如杀毒软件如果驱动程序拦截到恶意操作则必须将这个请求动态的转发到应用层以此来通知用户&#xff0c;而这…

多模态学习

什么是多模态学习&#xff1f; 模态 模态是指一些表达或感知事物的方式&#xff0c;每一种信息的来源或者形式&#xff0c;都可以称为一种模态 视频图像文本音频 多模态 多模态即是从多个模态表达或感知事物 多模态学习 从多种模态的数据中学习并且提升自身的算法 多…

Nacos架构与原理 - 寻址机制

文章目录 前提设计MemberLookup内部实现单机寻址 StandaloneMemberLookup文件寻址 FileConfigMemberLookup地址服务器寻址 AddressServerMemberLookup 未来可扩展点 前提 Nacos 支持单机部署以及集群部署 针对单机模式&#xff0c;Nacos 只是自己和自己通信&#xff1b;对于集…

关系数据库SQL数据查询

关系数据库SQL数据查询 数据查询 一、单表查询 1.查询仅涉及一个表&#xff0c;选择表中的若干列 [例1] 查询全体学生的学号与姓名。SELECT Sno,SnameFROM Student; [例2] 查询全体学生的姓名、学号、所在系。SELECT Sname,Sno,SdeptFROM Student;查询全部列 选出所有属…

文件系统考古 3:1994 - The SGI XFS Filesystem

在 1994 年&#xff0c;论文《XFS 文件系统的可扩展性》发表了。自 1984 年以来&#xff0c;计算机的发展速度变得更快&#xff0c;存储容量也增加了。值得注意的是&#xff0c;在这个时期出现了更多配备多个 CPU 的计算机&#xff0c;并且存储容量已经达到了 TB 级别。对于这些…

机器学习实践(1.2)XGBoost回归任务

前言 XGBoost属于Boosting集成学习模型&#xff0c;由华盛顿大学陈天齐博士提出&#xff0c;因在机器学习挑战赛中大放异彩而被业界所熟知。相比越来越流行的深度神经网络&#xff0c;XGBoost能更好的处理表格数据&#xff0c;并具有更强的可解释性&#xff0c;还具有易于调参…

SpringCloud微服务(二)网关GateWay、Docker、Dockerfile、Linux操作超详细

目录 统一网关GateWay 搭建网关服务的步骤 1、引入依赖 2、编写路由配置及nacos地址 路由断言工厂Route Oredicate Factory 路由过滤器配置 全局过滤器GlobalFilter 过滤器执行顺序 跨域问题处理 Docker ​编辑 Docker与虚拟机 镜像和容器 Docker的安装 启动docke…

MSP432学习笔记11:定时器A的结构\基地址\函数汇总理解

今日得以继续我的电赛MSP432学习之路&#xff1a;所用开发板MSP432P401R 定时器是任何单片机开发板十分重要的模块&#xff0c;在几日的学习使用过程中&#xff0c;本人也对其使用原理等产生过许多疑问&#xff0c;他究竟是怎么存储计数值、捕获值的&#xff1f;一个定时器四个…

8.2 电压比较器(1)

电压比较器是对输入信号进行鉴幅与比较的电路&#xff0c;是组成非正弦波发生电路的基本单元电路&#xff0c;在测量和控制中有着相当广泛的应用。 一、概述 1、电压比较器的电压传输特性 电压比较器的输出电压 u O u_{\scriptscriptstyle O} uO​ 与输入电压 u I u_{\scr…

网络层:虚拟专用网VPN和网络地址转换NAT

1.网络层&#xff1a;虚拟专用网VPN和网络地址转换NAT 笔记来源&#xff1a; 湖科大教书匠&#xff1a;虚拟专用网VPN和网络地址转换NAT 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 1.1 虚拟专用网VPN 专用网和公用网的特点 专用网络&#xff…

Springboot集成magic-api

目录 1、前言 2、springboot集成magic-api 2.1、添加maven依赖 2.2、application.yml配置 2.3、编写测试接口 2.4、启动程序&#xff0c;访问接口 2.5、magic-api脚本 3、magic-api其他语法 4、注意事项 1、前言 今天项目中遇到一个问题&#xff0c;springboot后端项目…

探索ChatGPT:了解语言模型在对话系统中的应用

第一章&#xff1a;引言 在当今数字化时代&#xff0c;人工智能技术的迅猛发展使得对话系统成为一个备受关注的领域。随着语言模型的进步&#xff0c;像ChatGPT这样的模型正在改变我们与计算机进行交流的方式。本文将探索ChatGPT作为一种语言模型在对话系统中的应用&#xff0…

简化 Hello World:Java 新写法要来了

OpenJDK 的 JEP 445 提案正在努力简化 Java 的入门难度。 这个提案主要是引入 “灵活的 Main 方法和匿名 Main 类” &#xff0c;希望 Java 的学习过程能更平滑&#xff0c;让学生和初学者能更好地接受 Java 。 提案的作者 Ron Pressler 解释&#xff1a;现在的 Java 语言非常…

Flutter 笔记 | Flutter 核心原理(三)布局(Layout )过程

布局过程 Layout&#xff08;布局&#xff09;过程主要是确定每一个组件的布局信息&#xff08;大小和位置&#xff09;&#xff0c;Flutter 的布局过程如下&#xff1a; 父节点向子节点传递约束&#xff08;constraints&#xff09;信息&#xff0c;限制子节点的最大和最小宽…

【实战与杂谈】本地搭建自己的游戏王卡片生成器

声明&#xff1a; 1.游戏王卡片制作器本身就是由【kooriookami】开发的&#xff0c;用于DIY卡片因此我只是原有功能再现并不会追加新功能 2.其次数据和卡图均来源于网络&#xff0c;因此我也只提供网络能获取该内容的途径&#xff0c;并不会预先准备好 最近一直没有时间看回复…

SpringBoot的配置环境属性

SpringBoot的配置环境属性 在本文中&#xff0c;我们将讨论SpringBoot的配置环境属性。我们将了解如何使用这些属性来配置我们的应用程序&#xff0c;以便在不同的环境中运行。我们还将了解如何使用SpringBoot的配置文件来管理这些属性。最后&#xff0c;我们将介绍一些最佳实…

激活函数ReLU和SiLU的区别

文章目录 前言ReLU&#xff08;Rectified Linear Unit&#xff09;Leaky ReLUFReLU&#xff08;Flatten ReLU&#xff09;SiLU&#xff08;Sigmoid Linear Unit&#xff09;总结 前言 在这里&#xff0c;我就简单写一下两个激活函数的概念以及区别&#xff0c;详细的过程可以看…

【C++详解】——红黑树

目录 红黑树的概念 红黑树的性质 红黑树节点的定义 红黑树的结构 红黑树的插入操作 情况一 情况二 情况三 红黑树的验证 红黑树的查找 红黑树与AVL树的比较 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示…

校园网WiFi IPv6免流上网

ipv6的介绍 IPv6是国际协议的最新版本&#xff0c;用它来取代IPv4主要是为了解决IPv4网络地址枯竭的问题&#xff0c;也在其他很多方面对IPv4有所改进&#xff0c;比如网络的速度和安全性。 IPv4是一个32位的地址&#xff0c;随着用户的增加在2011年国家报道说IPv4的网络地址即…

SpringBoot整合模板引擎Thymeleaf(2)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 概述 Thymeleaf十分类似于JSP中使用的EL表达式。整体而言&#xff0c;Thymeleaf简洁、优雅、高效&#xff1b;非常适合小型项目的快速开发。 Thymeleaf常用标签简述 在此…