写在前面

熟悉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. UVA4731:Cellular Network

    根据排序不等式可知,逆序和最小(就是两个向量坐标一个递增一个递减,那么乘起来就最小) 所以排一下序,然后做一下线性dp即可 #include<cstdio> #include<cst ...

  2. hdu 5514 Frogs(容斥)

    Frogs Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submi ...

  3. 【LSGDOJ 1333】任务安排 dp

    题目描述 N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务.从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti.在每批任务开始 ...

  4. 【QAQ的Minecraft】

    树套树被QAQ用木斧挖了,只剩二维RMQ了. 题目:      QAQ最近爱上了一款很平凡的游戏,叫做<Minecraft>.目前游戏更新到了1.12版本,他发现了一条新的指令:/fill ...

  5. 阿里云负载均衡SSL证书配置

    阿里云负载均衡SSL证书 转载请注明地址:http://www.cnblogs.com/funnyzpc/p/8908461.html 好久了呢,距上篇博客的这段时间中:考试.搬家.工作赶工.业务考察 ...

  6. typedef的基本用法

    1. 四个用途 用途一: 定义一种类型的别名,而不只是简单的宏替换.可以用作同时声明指针型的多个对象.比如: char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针 ...

  7. Win7删除文件夹提示找不到该项目无法删除

    在使用win7操作系统的过程中,有一些朋友会遇到这种情况,因为某种不明原因,硬盘里面某一个文件夹无法删除,表现为在删除的时候,系统提示找不到该项目,即找不到该文件夹,就像它变成了幽灵一样,看得到却摸不 ...

  8. python3全栈开发-并发编程,多进程的基本操作

    一 .multiprocessing模块介绍 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程. ...

  9. WEB中间件--Jboss未授权访问,

    1,Jboss未授权访问部署木马 发现存在Jboss默认页面,点进控制页 点击 Jboss.deployment 进入应用部署页面 也可以直接输入此URL进入 http://www.ctfswiki. ...

  10. 地址四级联动的vue组件

    一.效果图如下: 二.思路 主要在vue中结合 mint-ui组件的Picker和Popup方法,负责对json地址进行展示: 三.代码地址 四.说明 address4.json最好是在点击父组件的地 ...