Unity内置的雾效需要在每个shader中分别编写,造成了极大的不便。这里利用屏幕后处理产生可单独控制且自由度更高的雾效。

屏幕后雾效的本质在于,通过深度纹理重构出每个像素在世界空间中的位置,根据得到的世界坐标计算出雾效系数,最后利用雾效系数与雾的颜色相乘并与原始颜色进行插值运算得出最终效果。

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

上面的插值运算中f代表雾效系数,

它有多种计算方法:

1.线性运算:

f=(dmax-Abs(z))/dmax-dmin;

其中dmax和dmin分别代表受雾影响的最大和最小距离,z为给定的距离位置(像素位置)

2.指数运算:

f=pow(e,-d*Abs(z));

其中d控制雾的浓度,e为数学常量

3.二次指数:

f=pow(e,-pow(d*z,2));

为了更方便的对参数进行控制,需要重构每个像素在世界空间中的位置,常规实现方法如下:

1.构建像素的NDC坐标然后用VP矩阵的逆矩阵反向推导

2.通过向量的基本运算求得

方法1需要在片元着色器中进行矩阵乘法,若想得到性能更优的实现方式,考虑使用方法2。

向量的基本运算方式如下:

float4 worldPos=_WorldSpaceCameraPos+linearDepth*interpolatedRay;

_WorldSpaceCameraPos表示摄像机在世界空间中的位置,linearDepth*interpolatedRay是为了求得世界空间下的像素相对于摄像机的偏移量。根据向量的加法,就可以求出该像素在世界空间中的位置。

linearDepth线性深度值可以利用摄像机的深度纹理来求,关键在于求一个插值射线interpolatedRay。

分析interpolatedRay的含义可以知道,它主要表示该像素到摄像机的方向向量,可以由顶点着色器的各个顶点输出并插值得到。

基于这一点,可以直接在C#脚本中计算出屏幕四个顶点(左上,左下,右上,右下)的向量,传值给顶点着色器即可,这样避免在Shader中进行繁杂的数学运算。

参数控制脚本,同时计算顶点相对于摄像机的方向向量。此脚本挂载在摄像机上:

 using UnityEngine;

 public class FogWithDepthTexCtrl : ScreenEffectBase
{
private const string _FrustumCornersRay = "_FrustumCornersRay"; private const string _FogDensity = "_FogDensity";
private const string _FogColor = "_FogColor";
private const string _FogUnderStart = "_FogUnderStart";
private const string _FogTopEnd = "_FogTopEnd"; private Camera myCamera;
public Camera MyCamera
{
get
{
if (myCamera == null)
myCamera = GetComponent<Camera>();
return myCamera;
}
} private Transform myCameraTran;
public Transform MyCameraTran
{
get
{
if (myCameraTran == null)
myCameraTran = MyCamera.transform;
return myCameraTran;
}
} [Range(, )]
public float fogDensity = 1.0f;//控制雾的浓度
public Color fogColor = Color.white;
public float fogUnderStart = 0.0f;//雾起始高度
public float fogTopEnd = 2.0f;//雾结束高度 private void OnEnable()
{
MyCamera.depthTextureMode |= DepthTextureMode.Depth;
} private void OnDisable()
{
MyCamera.depthTextureMode &= ~DepthTextureMode.Depth;
} private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (Material != null)
{
//需要传递的四个角相对于摄像机的方向向量,这里用矩阵的每一行来表示
Matrix4x4 frustumCorners = Matrix4x4.identity; float fov = MyCamera.fieldOfView;
float near = MyCamera.nearClipPlane;
float aspect = MyCamera.aspect; //计算近裁剪平面三个标准方向
float halfHeight = near * Mathf.Tan(fov * .5f * Mathf.Deg2Rad);
Vector3 toTop = halfHeight * MyCameraTran.up;
Vector3 toRight = halfHeight * MyCameraTran.right * aspect;
Vector3 toForward = near * MyCameraTran.forward; //用三个标准方向重构四个顶点关于摄像机的向量
Vector3 topRight = toForward + toRight + toTop;
topRight /= near; Vector3 topLeft = toForward - toRight + toTop;
topLeft /= near; Vector3 bottomRight = toForward + toRight - toTop;
bottomRight /= near; Vector3 bottomLeft = toForward - toRight - toTop;
bottomLeft /= near; //用矩阵的每一行来存储这些向量,这里的顺序要与之后解析的顺序对应
frustumCorners.SetRow(, topLeft);
frustumCorners.SetRow(, topRight);
frustumCorners.SetRow(, bottomLeft);
frustumCorners.SetRow(, bottomRight); //传递向量矩阵和对应的参数
Material.SetMatrix(_FrustumCornersRay, frustumCorners); Material.SetFloat(_FogDensity, fogDensity);
Material.SetColor(_FogColor, fogColor);
Material.SetFloat(_FogUnderStart, fogUnderStart);
Material.SetFloat(_FogTopEnd, fogTopEnd); Graphics.Blit(source, destination, Material);
}
else
Graphics.Blit(source, destination);
}
}

基类见:

https://www.cnblogs.com/koshio0219/p/11131619.html

Shader脚本:

 Shader "MyUnlit/FogWithDepthTex"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
ZTest Always Cull Off ZWrite Off CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" //对应四个顶点的射线矩阵
float4x4 _FrustumCornersRay; sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
half _FogDensity;
fixed4 _FogColor;
float _FogUnderStart;
float _FogTopEnd; struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
}; struct v2f
{
half4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
//顶点着色器输出的插值射线
float4 interpolatedRay:TEXCOORD1;
}; v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.uv;
o.uv.zw=v.uv;//zw存深度纹理 //对插值射线的索引进行解析,判定该顶点是四个角中的哪一个
int idx=;
if(v.uv.x>.5f&&v.uv.y>.5f)
idx=;
else if(v.uv.x<.5f&&v.uv.y<.5f)
idx=;
else if(v.uv.x>.5f&&v.uv.y<.5f)
idx=; //主纹理外的纹理要进行平台差异化处理,同时对顶点的索引也需要进行处理(左上对左下,右上对右下)
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y<){
o.uv.w=-o.uv.w;
idx=idx<?idx+:idx-;
}
#endif //按照解析的索引值得到需要传递的插值射线
o.interpolatedRay=_FrustumCornersRay[idx]; return o;
} fixed4 frag (v2f i) : SV_Target
{
//计算像素在世界空间中的位置
float linearDepth=LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv.zw));
float3 worldPos=_WorldSpaceCameraPos+linearDepth*i.interpolatedRay.xyz; //计算雾效系数,这里主要用的关于世界空间高度的线性雾计算
float fogDensity=(_FogTopEnd-worldPos.y)/(_FogTopEnd-_FogUnderStart);
fogDensity=saturate(fogDensity*_FogDensity); //插值得到最终雾效
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb=lerp(col.rgb,_FogColor.rgb,fogDensity); return col;
}
ENDCG
}
}
}

效果如下:

Unity Shader 屏幕后效果——全局雾的更多相关文章

  1. Unity Shader 屏幕后效果——颜色校正

    屏幕后效果指的是,当前整个场景图已经渲染完成输出到屏幕后,再对输出的屏幕图像进行的操作. 在Unity中,一般过程通常是: 1.建立用于处理效果的shader和临时材质,给shader脚本传递需要控制 ...

  2. Unity Shader 屏幕后效果——边缘检测

    关于屏幕后效果的控制类详细见之前写的另一篇博客: https://www.cnblogs.com/koshio0219/p/11131619.html 这篇主要是基于之前的控制类,实现另一种常见的屏幕 ...

  3. Unity Shader 屏幕后效果——高斯模糊

    高斯模糊是图像模糊处理中非常经典和常见的一种算法,也是Bloom屏幕效果的基础. 实现高斯模糊同样用到了卷积的概念,关于卷积的概念和原理详见我的另一篇博客: https://www.cnblogs.c ...

  4. Unity Shader 屏幕后效果——景深

    景深效果的原理是,在摄像机的近裁剪平面和远裁剪平面之间可以设置一个焦距,在这个距离所在的平面上的物体最为清晰,而这个距离之前或之后的物体成像是一种模糊状态(根据距离逐渐模糊,最终达到最为模糊的状态). ...

  5. Unity Shader 屏幕后效果——Bloom外发光

    Bloom的原理很简单,主要是提取渲染图像中的亮部区域,并对亮部区域进行模糊处理,再与原始图像混合而成. 一般对亮部进行模糊处理的部分采用高斯模糊,关于高斯模糊,详见之前的另一篇博客: https:/ ...

  6. Unity Shader 屏幕后效果——摄像机运动模糊(速度映射图实现)

    速度映射图主要是为了得到每个像素相对于前一帧的运动矢量,其中一种方法是使用摄像机的深度纹理来推导. 推导过程如下: 先由深度纹理逆推出NDC(归一化的设备坐标)下的顶点坐标,利用VP矩阵(视角*投影矩 ...

  7. Unity Shader实现描边效果

    http://gad.qq.com/article/detail/28346 描边效果是游戏里面非常常用的一种效果,一般是为了凸显游戏中的某个对象,会给对象增加一个描边效果.本篇文章和大家介绍下利用S ...

  8. Unity Shader 之 透明效果

    透明效果 透明效果一般有两种实现方法: 第一种,使用透明度测试(Alpha Test) 第二种,使用透明度混合(Alpha Blending) 透明度测试和透明度混合机制: 透明度测试(Alpha T ...

  9. Unity实现屏幕抖动效果(通过Camera Viewpoint实现)

    由于游戏死亡时一般都需要屏幕抖一下下. 所以百度了下相关写法,发现方法很多~~~ 找来找去,找到个简单粗暴地,啥都不需要,一个脚本拖动到Camera上就可以了 略微修改了一点点,share一下 usi ...

随机推荐

  1. linux 常用配置

    linux mysql yum 配置 https://blog.csdn.net/wngpenghao/article/details/78862923 linux jdk1. yum install ...

  2. C# windows服务,解决应用程序开机自启问题

    最近在东营做一个超市购物的项目,业务体量很小,是仅供内部员工使用的内网应用程序,其中涉及一个商品数据同步的winform应用程序,有一个问题就是服务器重启后,必须登录服务器操作系统,手动启动才行,于是 ...

  3. 【前端知识体系-NodeJS相关】浅谈NodeJS中间件

    1. 中间件到底是个什么东西呢? [!NOTE] 中间件其是一个函数,在响应发送之前对请求进行一些操作 function middleware(req,res,next){ // 做该干的事 // 做 ...

  4. C++入门到理解阶段二基础篇(4)——C++运算符

    目录 算术运算符(进行四则运算) 赋值运算符(表达式的值赋给变量) 比较运算符(表达是比较,返回一个真值或假值) 逻辑运算符(返回表格式的结果真或假) 位运算符 杂项运算符 C++ 中的运算符优先级 ...

  5. Java学习笔记 DbUtils数据库查询和log4j日志输出 使用

    DbUtils使用 QueryRunner DbUtils中定义了一个数据库操作类QueryRunner,所有的数据库操作CRUD都是通过此类来完成. 此类是线程安全的 方法名 对应sql语句 exc ...

  6. Z从壹开始前后端分离【 .NET Core2.0/3.0 +Vue2.0 】框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存

    本文梯子 本文3.0版本文章 代码已上传Github+Gitee,文末有地址 大神反馈: 零.今天完成的深红色部分 一.AOP 之 实现日志记录(服务层) 1.定义服务接口与实现类 2.在API层中添 ...

  7. php 利用curl_*测试数据并发

    工作时遇到一个数据并发问题,因为上线之前没有测试数据并发,导致有时候网络比较差的时候导致数据重复插入数据库 , 所以利用curl_*函数专门写了一个测试数据并发的测试用例,如下: function t ...

  8. Java之线程与进程

    一.线程与进程 线程:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务.多线程是多任务的一种特别形式,但多线程使用了更小的资源开销. 进程:一个进程包括 ...

  9. SSM框架之Mybatis(1)入门

    Mybatis(1)入门 1.mybatis的概述 mybatis是一个持久层框架,用java编写的. 它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等 ...

  10. 微信小程序踩坑日记3——上传照片至服务器

    0. 引言 主要解决将小程序端获取的图片保存在服务器上.亲测可用的服务端脚本. 1. 获取照片 通过wx.chooseImage()方法,获取到图片,使用wx.uploadFile()上传图片. wx ...