基于屏幕后处理的全局雾效的关键是,根据深度纹理来重建每个像素在世界空间下的位置。我们在模拟运动模糊时已经实现了这个要求,即构建出当前像素的NDC坐标,再通过当前摄像机的视角*投影矩阵的逆矩阵来得到世界空间下的像素坐标,但是,这样的实现需要在片元着色器中进行矩阵乘法的操作,而这通常会影响游戏性能。在本节中,我们将会学习一个快速从深度纹理中重建世界坐标的方法。这种方法首先对图像空间下的视锥体射线(从摄像机出发,指向图像上的某点的射线)进行插值,这条射线存储了该像素在世界空间下到摄像机的方向信息。然后,我们把该射线和线性化后的视角空间下的深度值相乘,再加上摄像机的世界位置,就可以得到该像素在世界空间下的位置。当我们得到世界坐标后,就可以轻松地使用各个公式来模拟全局雾效了。

1 重建世界坐标

坐标系中的一个顶点坐标可以通过它相对于另-一个顶点坐标的偏移量来求得。重建像素的世界坐标也是基于这样的思想。我们只需要知道摄像机在世界空间下的位置,以及世界空间下该像素相对于摄像机的偏移量,把它们相加就可以得到该像素的世界坐标。整个过程可以使用下面的代码来表示:

float4 worldPos = _WorldSpaceCameraPos + linearDepth * interpolatedRay;

_WorldSpaceCameraPos :摄像机在世界空间下的位置,这可以由Unity的内置变量直接访问得到。

linearDepth * interpolatedRay :计算得到该像素相对于摄像机的偏移量

linearDepth:由深度纹理得到的线性深度值获取深度纹理和法线纹理

interpolatedRay :由顶点着色器输出并插值后得到的射线,它不仅包含了该像素到摄像机的方向,也包含了距离信息。

interpolatedRay来源于对近裁剪平面的4个角的某个特定向量的插值,这4个向量包含了它们到摄像机的方向和距离信息,我们可以利用摄像机的近裁剪平面距离、FOV、横纵比计算而得。下图显示了计算时使用的一些辅助向量。

为了方便计算,我们可以先计算两个向量——toTop 和toRight,它们是起点位于近裁剪平面中心、分别指向摄像机正上方和正右方的向量。它们的计算公式如下:

其中,Near是近裁剪平面的距离,FOV是竖直方向的视角范围,camera.up、 camera.right 分别对应了摄像机的正上方和正右方。

当得到这两个辅助向量后,我们就可以计算4个角相对于摄像机的方向了。我们以左上角为例,它的计算公式如下:


 

注意,上面求得的4个向量不仅包含了方向信息,它们的模对应了4个点到摄像机的空间距离。由于我们得到的线性深度值并非是点到摄像机的欧式距离,而是在z方向上的距离(如上图),因此,我们不能直接使用深度值和4个角的单位方向的乘积来计算它们到摄像机的偏移量,想要把深度值转换成到摄像机的欧式距离也很简单,我们以TL点为例,根据相似三角形原理,TL所在的射线上,像素的深度值和它到摄像机的实际距离的比等于近裁剪平面的距离和TL向量的模的比,即:

由此可得,我们需要的TL距离摄像机的欧氏距离dist:

由于4个点相互对称,因此其他3个向量的模和TL相等,即我们可以使用同一个因子和单位向量相乘,得到它们对应的向量值:

屏幕后处理的原理是使用特定的材质去渲染-一个刚好填充整个屏幕的四边形面片。这个四边形面片的4个顶点就对应了近裁剪平面的4个角。因此,我们可以把上面的计算结果传递给顶点着色器,顶点着色器根据当前的位置选择它所对应的向量,然后再将其输出,经插值后传递给片元着色器得到interpolatedRay, 我们就可以直接利用本节一开始提 到的公式重建该像素在世界空间下的位置了。

2        雾的计算

在简单的雾效实现中,我们需要计算一一个雾效系数 f,作为混合原始颜色和雾的颜色的混合系数:

float3 afterFog = f * fogColor + (1 - f) * origColor;

在Unity内置的雾效实现中支持三种计算方式,在给定距离z后,f的计算公式分别如下:

线性:           dmin和dmax分别表示受雾影响的最小距离和最大距离。

指数:                d是控制雾的浓度的参数

指数的平方:        d是控制雾的浓度的参数

在本节中,我们将使用类似线性雾的计算方式,计算基于高度的雾效。具体方法是,当给定一点在世界空间下的高度y后,f的计算公式为:

Hstart和Hend分别表示受雾影响的起始高度和终止高度。

3        实现

后处理代码:

using UnityEngine;
using System.Collections;public class FogWithDepthTexture : PostEffectsBase {public Shader fogShader;private Material fogMaterial = null;public Material material {  get {fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial);return fogMaterial;}  }//需要获取摄像机的相关参数,所以用两个变量存储摄像机的Camera组件和Transform组件private Camera myCamera;public Camera camera {get {if (myCamera == null) {myCamera = GetComponent<Camera>();}return myCamera;}}private Transform myCameraTransform;public Transform cameraTransform {get {if (myCameraTransform == null) {myCameraTransform = camera.transform;}return myCameraTransform;}}[Range(0.0f, 3.0f)]public float fogDensity = 1.0f;    //控制雾的浓度public Color fogColor = Color.white;    //控制雾的颜色public float fogStart = 0.0f;    //控制雾的起始高度public float fogEnd = 2.0f;    //控制雾的终止高度//设置获取摄像机的深度纹理时摄像机的相应状态void OnEnable() {camera.depthTextureMode |= DepthTextureMode.Depth;}void OnRenderImage (RenderTexture src, RenderTexture dest) {if (material != null) {Matrix4x4 frustumCorners = Matrix4x4.identity;float fov = camera.fieldOfView;float near = camera.nearClipPlane;float aspect = camera.aspect;float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);Vector3 toRight = cameraTransform.right * halfHeight * aspect;Vector3 toTop = cameraTransform.up * halfHeight;Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;float scale = topLeft.magnitude / near;topLeft.Normalize();topLeft *= scale;Vector3 topRight = cameraTransform.forward * near + toRight + toTop;topRight.Normalize();topRight *= scale;Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;bottomLeft.Normalize();bottomLeft *= scale;Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop;bottomRight.Normalize();bottomRight *= scale;frustumCorners.SetRow(0, bottomLeft);frustumCorners.SetRow(1, bottomRight);frustumCorners.SetRow(2, topRight);frustumCorners.SetRow(3, topLeft);material.SetMatrix("_FrustumCornersRay", frustumCorners);material.SetFloat("_FogDensity", fogDensity);material.SetColor("_FogColor", fogColor);material.SetFloat("_FogStart", fogStart);material.SetFloat("_FogEnd", fogEnd);Graphics.Blit (src, dest, material);} else {Graphics.Blit(src, dest);}}
}

OnRenderlmage首先计算了近裁剪平面的四个角对应的向量,并把它们存储在一一个矩阵类型的变量(frustumCormners)中。我们按一定顺序把这四个方向存储到了frustumCormers 不同的行中,这个顺序是非常重要的,因为这决定了我们在顶点着色器中使用哪一行作为该点的待插值向量。随后,我们把结果和其他参数传递给材质,并调用Graphics.Blit (src, dest, material)把渲染结果显示在屏幕上。

Shader代码:

Shader "Unity Shaders Book/Chapter 13/Fog With Depth Texture" {Properties {_MainTex ("Base (RGB)", 2D) = "white" {}_FogDensity ("Fog Density", Float) = 1.0_FogColor ("Fog Color", Color) = (1, 1, 1, 1)_FogStart ("Fog Start", Float) = 0.0_FogEnd ("Fog End", Float) = 1.0}SubShader {CGINCLUDE#include "UnityCG.cginc"float4x4 _FrustumCornersRay;sampler2D _MainTex;half4 _MainTex_TexelSize;sampler2D _CameraDepthTexture;    //深度纹理half _FogDensity;fixed4 _FogColor;float _FogStart;float _FogEnd;struct v2f {float4 pos : SV_POSITION;half2 uv : TEXCOORD0;half2 uv_depth : TEXCOORD1;float4 interpolatedRay : TEXCOORD2;    //存储插值后的像素向量};v2f vert(appdata_img v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = v.texcoord;o.uv_depth = v.texcoord;#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0)o.uv_depth.y = 1 - o.uv_depth.y;#endifint 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;}#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0)index = 3 - index;#endifo.interpolatedRay = _FrustumCornersRay[index];return o;}fixed4 frag(v2f i) : SV_Target {float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); fogDensity = saturate(fogDensity * _FogDensity);fixed4 finalColor = tex2D(_MainTex, i.uv);finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);return finalColor;}ENDCGPass {ZTest Always Cull Off ZWrite OffCGPROGRAM  #pragma vertex vert  #pragma fragment frag  ENDCG  }} FallBack Off
}

顶点着色器:我们对深度纹理的采样坐标进行了平台差异化处理。更重要的是,我们要决定该点对应了4个角中的哪个角。我们采用的方法是判断它的纹理坐标。在Unity中,纹理坐标的(0, 0)点对应了左下角,而(1, 1)点对应了右上角。我们据此来判断该顶点对应的索引,这个对应关系和我们在脚本中对frustumCorners 的赋值顺序是一致的。实际上,不同平台的纹理坐标不一定是满足上面的条件的,例如DirectX和Metal这样的平台,左上角对应了(0, 0)点,但大多数情况下Unity会把这些平台下的屏幕图像进行翻转,因此我们仍然可以利用这个条件。但如果在类似DirectX的平台,上开启了抗锯齿,Unity就不会进行这个翻转。为了此时仍然可以得到相应顶点位置的索引值,我们对索引值也进行了平台差异化处理,以便在必要时也对索引值进行翻转。最后,我们使用索引值来获取_FrustumCornersRay 中对应的行作为该顶点的interpolatedRay值。

片元着色器:首先,我们需要重建该像素在世界空间中的位置。为此,我们首先使用SAMPLE_DEPTH_TEXTURE对深度纹理进行采样,再使用LinearEyeDepth得到视角空间下的线性深度值。之后,与interpolatedRay相乘后再和世界空间下的摄像机位置相加,即可得到世界空间下的位置。得到世界坐标后,模拟雾效就变得非常容易。在本例中,我们选择实现基于高度的雾效模拟。我们根据材质属性_FogEnd 和_FogStart 计算当前的像素高度worldPos.y对应的雾效系数fogDensity, 再和参数_FogDensity 相乘后,利用saturate 函数截取到[0, 1]范围内,作为最后的雾效系数。然后,我们使用该系数将雾的颜色和原始颜色进行混合后返回。


加更:利用噪声纹理生成全局雾效

后处理代码:

using UnityEngine;
using System.Collections;public class FogWithNoise : PostEffectsBase {public Shader fogShader;private Material fogMaterial = null;public Material material {  get {fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial);return fogMaterial;}  }private Camera myCamera;public Camera camera {get {if (myCamera == null) {myCamera = GetComponent<Camera>();}return myCamera;}}private Transform myCameraTransform;public Transform cameraTransform {get {if (myCameraTransform == null) {myCameraTransform = camera.transform;}return myCameraTransform;}}[Range(0.1f, 3.0f)]public float fogDensity = 1.0f;public Color fogColor = Color.white;public float fogStart = 0.0f;public float fogEnd = 2.0f;public Texture noiseTexture;[Range(-0.5f, 0.5f)]public float fogXSpeed = 0.1f;[Range(-0.5f, 0.5f)]public float fogYSpeed = 0.1f;[Range(0.0f, 3.0f)]public float noiseAmount = 1.0f;void OnEnable() {GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;}void OnRenderImage (RenderTexture src, RenderTexture dest) {if (material != null) {Matrix4x4 frustumCorners = Matrix4x4.identity;float fov = camera.fieldOfView;float near = camera.nearClipPlane;float aspect = camera.aspect;float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);Vector3 toRight = cameraTransform.right * halfHeight * aspect;Vector3 toTop = cameraTransform.up * halfHeight;Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;float scale = topLeft.magnitude / near;topLeft.Normalize();topLeft *= scale;Vector3 topRight = cameraTransform.forward * near + toRight + toTop;topRight.Normalize();topRight *= scale;Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;bottomLeft.Normalize();bottomLeft *= scale;Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop;bottomRight.Normalize();bottomRight *= scale;frustumCorners.SetRow(0, bottomLeft);frustumCorners.SetRow(1, bottomRight);frustumCorners.SetRow(2, topRight);frustumCorners.SetRow(3, topLeft);material.SetMatrix("_FrustumCornersRay", frustumCorners);material.SetFloat("_FogDensity", fogDensity);material.SetColor("_FogColor", fogColor);material.SetFloat("_FogStart", fogStart);material.SetFloat("_FogEnd", fogEnd);material.SetTexture("_NoiseTex", noiseTexture);material.SetFloat("_FogXSpeed", fogXSpeed);material.SetFloat("_FogYSpeed", fogYSpeed);material.SetFloat("_NoiseAmount", noiseAmount);Graphics.Blit (src, dest, material);} else {Graphics.Blit(src, dest);}}
}

shader代码:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'Shader "Unity Shaders Book/Chapter 15/Fog With Noise" {Properties {_MainTex ("Base (RGB)", 2D) = "white" {}_FogDensity ("Fog Density", Float) = 1.0_FogColor ("Fog Color", Color) = (1, 1, 1, 1)_FogStart ("Fog Start", Float) = 0.0_FogEnd ("Fog End", Float) = 1.0_NoiseTex ("Noise Texture", 2D) = "white" {}_FogXSpeed ("Fog Horizontal Speed", Float) = 0.1_FogYSpeed ("Fog Vertical Speed", Float) = 0.1_NoiseAmount ("Noise Amount", Float) = 1}SubShader {CGINCLUDE#include "UnityCG.cginc"float4x4 _FrustumCornersRay;sampler2D _MainTex;half4 _MainTex_TexelSize;sampler2D _CameraDepthTexture;half _FogDensity;fixed4 _FogColor;float _FogStart;float _FogEnd;sampler2D _NoiseTex;half _FogXSpeed;half _FogYSpeed;half _NoiseAmount;struct v2f {float4 pos : SV_POSITION;float2 uv : TEXCOORD0;float2 uv_depth : TEXCOORD1;float4 interpolatedRay : TEXCOORD2;};v2f vert(appdata_img v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = v.texcoord;o.uv_depth = v.texcoord;#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0)o.uv_depth.y = 1 - o.uv_depth.y;#endifint 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;}#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0)index = 3 - index;#endifo.interpolatedRay = _FrustumCornersRay[index];return o;}fixed4 frag(v2f i) : SV_Target {float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);float noise = (tex2D(_NoiseTex, i.uv + speed).r - 0.5) * _NoiseAmount;float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); fogDensity = saturate(fogDensity * _FogDensity * (1 + noise));fixed4 finalColor = tex2D(_MainTex, i.uv);finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);return finalColor;}ENDCGPass {              CGPROGRAM  #pragma vertex vert  #pragma fragment frag  ENDCG}} FallBack Off
}

顶点着色器除变量设置外没有更改。

片元着色器:

我们首先根据深度纹理来重建该像素在世界空间中的位置。然后,我们利用内置的_ Time.y变量和_ FogXSpeed、_ FogYSpeed 属性计算出当前噪声纹理的偏移量,并据此对噪声纹理进行采样,得到噪声值。我们把该值减去0.5, 再乘以控制噪声程度的属性NoiseAmount, 得到最终的噪声值。随后,我们把该噪声值添加到雾效浓度的计算中,得到应用噪声后的雾效混合系数fogDensity。最后,我们使用该系数将雾的颜色和原始颜色进行混合后返回。

UnityShader入门精要——全局雾效相关推荐

  1. unity shader全局雾效

    深度纹理 深度纹理存储高精度的深度值,范围为[0,1],而且通常是非线性的. 深度值计算 在顶点变化中,最终会变换到裁剪空间NDC空间下,裁剪空间是一个[-1,1]的线性空间,在NDC空间下我们可以轻 ...

  2. UnityShader基础案例(八)——全局雾效

    雾公式 (Direct3D 9) - Win32 apps | Microsoft Learn 这个参考一下吧. 一.基于高度 雾的高度什么的要看场景,根据场景调. 脚本: using System; ...

  3. UnityShader入门精要——表面着色器

    表面着色器的CG代码是直接而且也必须写在SubShader块中,Unity 会在背后为我们生成多个Pass.当然,可以在SubShader一开始处使用Tags来设置该表面着色器使用的标签.我们也可以使 ...

  4. 《UnityShader入门精要》总结(1)理论篇

    紫色:大类概念或简短有力的总结 蓝色:细分概念或重要部分 红色:重要的补充注释 第二章:渲染流程与流程分工 渲染的流程分三个阶段: 应用阶段(开发者控制阶段) 由开发者全权进行管理,控制场景内摄像机位 ...

  5. UnityShader入门精要-9

    目录 1. Unity的渲染路径 前向渲染路径 Unity中的前向渲染 延迟渲染 Unity的光源类型 Unity的光照衰减 Unity的阴影 1. Unity的渲染路径 Unity 5.0之前,有3 ...

  6. UnityShader入门精要-3.3 UnityShader的结构

    一个UnityShader的基础结构如下所示: Shader "ShaderName"{Properties{//属性 } SubShader{//显卡A使用的子着色器 }SubS ...

  7. UnityShader入门精要——立方体纹理

    在图形学中,立方体纹理(Cubemap)是环境映射(Environment Mapping)的一种实现方法.环境映射可以模拟物体周围的环境,而使用了环境映射的物体可以看起来像镀了层金属一样反射出周围的 ...

  8. UnityShader入门精要笔记1——顶点/片元着色器结构与BRDF(基本光照模型)——实现漫反射

    文章目录 BRDF(基本光照模型) 实现漫反射 光线强度的计算 好现在开始写Shader 新建Shader 添加一个Properties语义块 添加SubShader和Pass. 使用CG/HLSL语 ...

  9. UnityShader入门精要——运动模糊

    运动模糊是真实世界中的摄像机的一种效果.如果在摄像机曝光时,拍摄场景发生了变化,就会产生模糊的画面. 运动模糊的实现有多种方法.一种实现方法是利用一块累积缓存(accumulation buffer ...

最新文章

  1. 德勤:2018年科技、传媒和电信行业未来趋势预测
  2. Android数据适配器Adapter简介
  3. 【干货】深入B端SaaS产品设计核心理念
  4. linux decode函数,Oracle 中 decode 函数用法
  5. 人类一败涂地显示服务器,人类一败涂地怎么开服务器 | 手游网游页游攻略大全...
  6. 两台邮件服务器共用一个公网地址,两个不同域邮件服务器的互通
  7. android删除打开方式,Android 打开方式选定后默认了改不回来?解决方法(三星s7为例)...
  8. iOS使用新浪微博、微信官方SDK分享内容
  9. Java-当前对象this
  10. mysql按首数字分表_MySql基础-数据分表
  11. Vijos P1448 校门外的树【多解,线段树,树状数组,括号序列法+暴力优化】
  12. MAC OS(U盘启动教程)
  13. 电池测试系统连接服务器失败,电池测试系统服务器的设计与应用
  14. 加一度分享:快手PK抖音,谁更有优势
  15. android版本兼容API24,Android 7.0 (API 24) 适配
  16. 知网文献免费下载方法
  17. ABR与ASBR区别
  18. 游戏中常用的设计模式
  19. 【linux】僵尸进程(Defunct进程)的产生与避免
  20. python循环语句打印三角形_python循环输出三角形图案的例子

热门文章

  1. 默认网关填了不能保存问题秒解
  2. 手机qpython3使用教程爬书_5.Python3爬虫入门实践——爬取名著
  3. Java工具 :SQL解析
  4. 决策树:什么是基尼系数(“杂质 增益 指数 系数”辨析)
  5. Java empty、null、blank 还傻傻分不清楚?
  6. 小Y的Python学习日志--数据类型
  7. ResultMap 中collection
  8. 在代码里面故意留个漏洞,违法吗?
  9. python 列表间隔取值_程序从Python中的间隔列表中查找最长间隔的长度
  10. 翻译:Fully Convolutional Networksfor Semantic Segmentation