写在前面

熟悉Unity的都知道,Unity可以进行基本的雾效模拟。所谓雾效,就是在远离我们视角的方向上,物体看起来像被蒙上了某种颜色(通常是灰色)。这种技术的实现实际上非常简单,就是根据物体距离摄像机的远近,来混合雾的颜色和物体本身的颜色即可。

Unity里设置雾效有两种方式,一种最简单的就是直接开启全局雾效,也就是在Edit->Render Settings里配置,如下图所示:

而我们只需要把“Fog”选项后面的勾选框打开即可。上图包含了一些设置:雾的颜色,模拟雾采用的方法,雾的浓度(只在采用指数方法时有用),受雾影响的距离起点和终点(只在采用线性方法时有效)。其中,比较重要的是模拟雾采用的方法,即“Fog Mode”这一选项。它可以选择三种方法:

还有一种方法就是在shader中用Fog指令设置。这里有官网的说明。

三种模式

Linear、Exponential和Exp2这三种模式实际上是使用了不同的公式计算雾的影响因子。这个影响因子会作为混合雾的颜色和物体原始颜色的参数,来计算最终的混合颜色。例如,我们使用下面的语句来计算:

float3 afterFog = mix(_FogColor.rgb, col.rgb, fogFactor);

如果影响因子为1,则表明完全没有雾效;如果为0,则表示完全被雾覆盖。而三种模式使用的公式分别如下所示:

  • Linear:
    ,其中Dmax和Dmin分别表示受雾影响的距离起点和终点。
  • Exponential:
    ,其中d表示雾的浓度。
  • Exp2:
    ,其中d表示雾的浓度。

三个等式中的z,表示距离摄像机的远近。

为了充分理解雾效的实现原理和这三种方法的不同之处,我们这篇会自己在Fragment Shader中模拟雾效。

Unity模拟的雾效

我们采用如下简单的卡通苹果场景(小苹果真是我的最爱。。。)来检验雾效。原始的场景如图所示:

其中距离相机最远的小苹果的距离大约是25单位。

我们开启Unity的全局雾效后,分别采用三种方法模拟,结果如下:

它们的雾效配置如下所示:

我们在后面会解释这些参数的含义,现在我们只需要知道“Fog Density”仅在“Fog Mode”为“Exponential”或“Exp2”时有用,而“Linear Fog Start”和“Linear Fog End”仅在“Fog Mode”为“Linear”时有用即可。注意,上面的“Linear Fog Start”和“Linear Fog End”参数是基于“距离相机最远的小苹果的距离大约是25单位”这一条件设置的,只是为了让雾效更加明显而已。

现在,我们可以从视觉上了解三种方法的异同。

Fog实现的内部原理

为了充分了解雾效算法,我们现在在小苹果现有的shader里添加雾效算法的实现。

  1. 首先,我们需要在Properties块中添加雾效的几个设置参数:
    	Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _Ramp ("Ramp Texture", 2D) = "white" {}
    _Tooniness ("Tooniness", Range(0.1,20)) = 4
    _Outline ("Outline", Range(0,1)) = 0.1 _FogColor("Fog Color", Color) = (1, 0, 0, 0)
    _FogIntensity("Fog Intensity", float) = 0.1
    _FogStart("Fog Start", float) = 0
    _FogEnd("Fog End", float) = 300
    }
  2. 下面是添加模拟雾效的函数:
    		float4 SimulateFog(float4 pos, float4 col)
    {
    pos.w = 0.0;
    float dist = length(pos);
    // float dist = pos.z;
    // Linear
    // float fogFactor = (_FogEnd - abs(dist)) / (_FogEnd - _FogStart);
    // fogFactor = clamp(fogFactor, 0.0, 1.0); // Exponential
    // float fogFactor = exp(-abs(_FogIntensity * dist));
    // fogFactor = clamp(fogFactor, 0.0, 1.0); // Exp2
    float fogFactor = exp(-(_FogIntensity * dist) * (_FogIntensity * dist));
    fogFactor = clamp(fogFactor, 0.0, 1.0); float3 afterFog = mix(_FogColor.rgb, col.rgb, fogFactor); return float4(afterFog, col.a);
    }

    解释:有了上面的公式,这个函数很好理解。值得说明的是,函数参数pos是指在view space中顶点的位置,因为只有在这个坐标系中,摄像机的位置总是位于原点。在计算距离摄像机远近时,我们有两种可选方式:一种直接使用pos.z得到近似值,一种是使用真正距离摄像机的距离,即计算xyz平方和后开根号的结果。第二种方法由于使用了计算根号这种操作,因此在性能上略微比第一种查一点,但效果也更真实。

使用上述代码的三种模式效果如下:
上图中使用苹果主体部分使用了fog shader,叶子和梗没有使用哦~可以看出,和Unity模拟的效果基本相同。
最后,完整的代码如下:
Shader "Custom/FogSimulation" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Ramp ("Ramp Texture", 2D) = "white" {}
_Tooniness ("Tooniness", Range(0.1,20)) = 4
_Outline ("Outline", Range(0,1)) = 0.1 _FogColor("Fog Color", Color) = (1, 0, 0, 0)
_FogIntensity("Fog Intensity", float) = 0.1
_FogStart("Fog Start", float) = 0
_FogEnd("Fog End", float) = 300
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200 CGINCLUDE
#include "UnityCG.cginc" sampler2D _MainTex;
sampler2D _Ramp; float4 _MainTex_ST; float _Tooniness;
float _Outline; float4 _FogColor;
float _FogIntensity;
float _FogStart;
float _FogEnd; float4 SimulateFog(float4 pos, float4 col)
{
pos.w = 0.0;
float dist = length(pos);
// float dist = pos.z;
// Linear
// float fogFactor = (_FogEnd - abs(dist)) / (_FogEnd - _FogStart);
// fogFactor = clamp(fogFactor, 0.0, 1.0); // Exponential
// float fogFactor = exp(-abs(_FogIntensity * dist));
// fogFactor = clamp(fogFactor, 0.0, 1.0); // Exp2
float fogFactor = exp(-(_FogIntensity * dist) * (_FogIntensity * dist));
fogFactor = clamp(fogFactor, 0.0, 1.0); float3 afterFog = mix(_FogColor.rgb, col.rgb, fogFactor); return float4(afterFog, col.a);
} ENDCG Pass {
Tags { "LightMode"="ForwardBase" } Cull Front
Lighting Off
ZWrite On CGPROGRAM #pragma vertex vert
#pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
}; struct v2f
{
float4 pos : POSITION;
float4 viewSpacePos : TEXCOORD0;
}; v2f vert (a2v v)
{
v2f o; float4 pos = mul( UNITY_MATRIX_MV, v.vertex);
float3 normal = mul( (float3x3)UNITY_MATRIX_IT_MV, v.normal);
normal.z = -0.5;
pos = pos + float4(normalize(normal),0) * _Outline;
o.pos = mul(UNITY_MATRIX_P, pos); o.viewSpacePos = mul( UNITY_MATRIX_MV, v.vertex); return o;
} float4 frag(v2f i) : COLOR
{
return SimulateFog(i.viewSpacePos, float4(0, 0, 0, 1));
} ENDCG
} Pass {
Tags { "LightMode"="ForwardBase" } Cull Back
Lighting On CGPROGRAM #pragma vertex vert
#pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "UnityShaderVariables.cginc" struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
}; struct v2f
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
float4 viewSpacePos : TEXCOORD2;
LIGHTING_COORDS(3,4)
}; v2f vert (a2v v)
{
v2f o; //Transform the vertex to projection space
o.pos = mul( UNITY_MATRIX_MVP, v.vertex);
o.normal = mul((float3x3)_Object2World, SCALED_NORMAL);
//Get the UV coordinates
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); o.viewSpacePos = mul( UNITY_MATRIX_MV, v.vertex); // pass lighting information to pixel shader
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
} float4 frag(v2f i) : COLOR
{
//Get the color of the pixel from the texture
float4 c = tex2D (_MainTex, i.uv);
//Merge the colours
c.rgb = (floor(c.rgb*_Tooniness)/_Tooniness); //Based on the ambient light
float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz; //Work out this distance of the light
float atten = LIGHT_ATTENUATION(i);
//Angle to the light
float diff = dot (normalize(i.normal), normalize(_WorldSpaceLightPos0.xyz));
diff = diff * 0.5 + 0.5;
//Perform our toon light mapping
diff = tex2D(_Ramp, float2(diff, 0.5));
//Update the colour
lightColor += _LightColor0.rgb * (diff * atten);
//Product the final color
c.rgb = lightColor * c.rgb * 2; return SimulateFog(i.viewSpacePos, c);
} ENDCG
}
}
FallBack "Diffuse"
}

写在最后

Unity文档中写道:
Note that if you use fragment programs, Fog settings of the shader will still be applied. On platforms where there is no fixed function Fog functionality, Unity will patch shaders at runtime to support the requested Fog mode.
也就是说,即便我们是使用自己编写的Fragment Shader,但还是会受到Unity Render Settings的影响。即便是Unity里自定义的Vertex & Fragment Shader,应该也是不知道被Unity封装了多少层以后的上层函数。如果开启了全局雾效,那么Unity会在背后大概使用了固定渲染流水线中的fog指令,来模拟雾效。因此,如果对于不支持固定管线的雾效函数的平台,它就会使用自己编写shader(类似我们上面那样),来模拟雾效。
参考:《OpenGL 4 Sharding Language Cookbook》

【Unity Shaders】Unity里的雾效模拟的更多相关文章

  1. 【Unity Shaders】ShadowGun系列之二——雾和体积光

    写在前面 体积光,这个名称是God Rays的中文翻译,感觉不是很形象.God Rays其实是Crepuscular rays在图形学中的说法,而Crepuscular rays的意思是云隙光.曙光. ...

  2. Unity中雾效的开启

    原文:https://blog.csdn.net/Rhett_Yuan/article/details/54425236 1.对于雾效的开启在新版的Unity中通过界面菜单Windows->Li ...

  3. Re:《Unity Shader入门精要》13.3全局雾效--如何从深度纹理重构世界坐标

    如何从深度纹理重构世界坐标 游戏特效,后处理是必不可少的,而后处理经常需要我们得到当前画面的像素对应世界空间的所有信息. 思路 通过深度纹理取得NDC坐标,然后再通过NDC坐标还原成世界空间坐标 // ...

  4. 【Unity Shaders】学习笔记——渲染管线

    [Unity Shaders]学习笔记——Shader和渲染管线 转载请注明出处:http://www.cnblogs.com/-867259206/p/5595924.html 写作本系列文章时使用 ...

  5. 【Unity Shaders】学习笔记——SurfaceShader(八)生成立方图

    [Unity Shaders]学习笔记——SurfaceShader(八)生成立方图 转载请注明出处:http://www.cnblogs.com/-867259206/p/5630261.html ...

  6. 【Unity Shaders】学习笔记——SurfaceShader(七)法线贴图

    [Unity Shaders]学习笔记——SurfaceShader(七)法线贴图 转载请注明出处:http://www.cnblogs.com/-867259206/p/5627565.html 写 ...

  7. 【Unity Shaders】学习笔记——SurfaceShader(四)用纹理改善漫反射

    [Unity Shaders]学习笔记——SurfaceShader(四)用纹理改善漫反射 转载请注明出处:http://www.cnblogs.com/-867259206/p/5603368.ht ...

  8. 【Unity Shaders】法线纹理(Normal Mapping)的实现细节

    写在前面 写这篇的目的是为了总结我长期以来的混乱.虽然题目是"法线纹理的实现细节",但其实我想讲的是如何在shader中编程正确使用法线进行光照计算.这里面最让人头大的就是各种矩阵 ...

  9. 【Unity Shaders】游戏性和画面特效——创建一个夜视效果的画面特效

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

随机推荐

  1. 洛谷mNOIP模拟赛Day1-斐波那契

    题目背景 大样例下发链接:http://pan.baidu.com/s/1c0LbQ2 密码:jigg 题目描述 小 C 养了一些很可爱的兔子. 有一天,小 C 突然发现兔子们都是严格按照伟大的数学家 ...

  2. 使用python+requests+unittest实现接口自动化测试

    这两天一直在找直接用python做接口自动化的方法,在网上也搜了一些博客参考,今天自己动手试了一下. 一.整体结构 上图是项目的目录结构,下面主要介绍下每个目录的作用. Common:公共方法:主要放 ...

  3. Linux(CentOs6.3)网络配置

    新装好的虚拟机往往还无法连接网络,本文描述了如何在CentOs6.3系统上配置网络信息 1.windows系统下快捷键windows+r,输入cmd并确定,打开黑窗口 2.黑窗口中输入ipconfig ...

  4. 图像融合之拉普拉斯融合(laplacian blending)

    一.拉普拉斯融合基本步骤 1. 两幅图像L,R,以及二值掩模mask,给定金字塔层数level. 2. 分别根据L,R构建其对应的拉普拉斯残差金字塔(层数为level),并保留高斯金字塔下采样最顶端的 ...

  5. 如何使用 TeamViewer 配置QuickConnect按钮?

    QuickConnect作为TeamViewer中一个比较重要的部分,得到了很多用户的认可.那么在实际运用中,怎么才能设置网页或单个程序的QuickConnect呢?所以小编以此问题为例,教大家如何配 ...

  6. SecureFX连接Linux后文件夹中文乱码问题解决(转)

    在使用SecureFX 连接Linux 时,发现文件夹显示乱码,一直尝试各种配置,现将方法整理一下!供大家参考! 首先在选项中设置字符编码为UTF-8 然后在全局选项中找到Securefx的配置文件 ...

  7. Weekly Contest 75题解

    Q1. Rotate String(796) We are given two strings, A and B. A shift on A consists of taking string A a ...

  8. 从Openvswitch代码看网络包的旅程

    我们知道,Openvwitch可以创建虚拟交换机,而网络包可以通过虚拟交换机进行转发,并通过流表进行处理,具体的过程如何呢? 一.内核模块Openvswitch.ko的加载 OVS是内核态和用户态配合 ...

  9. 修改hosts不必重启 立刻生效

    打开命令提示符窗口执行以下命令: 显示DNS缓存内容 ipconfig /displaydns 删除DNS缓存内容 ipconfig /flushdns ps.电脑卡的话,先关机再开机(别直接重启)

  10. 搭建 RabbitMQ Server 高可用集群

    阅读目录: 准备工作 搭建 RabbitMQ Server 单机版 RabbitMQ Server 高可用集群相关概念 搭建 RabbitMQ Server 高可用集群 搭建 HAProxy 负载均衡 ...