Unity的ps混合选项的快捷键树点击compute position选项后不会自动设置PosX、Y

Unity Shader-后处理:景深
景深一直是我最喜欢的效果之一,最早接触CE3的时候,发现CE引擎默认就支持景深的效果,当时感觉这个效果特别酷炫,如今投身于Unity的怀抱中,准备用Unity实现以下传说中的景深效果。
所谓景深,是摄影的一个专业术语:在聚焦完成后,在焦点前后的范围内都能形成清晰的像,这一前一后的距离范围,便叫做景深,也是被摄物体能清晰成像的空间深度。在景深范围内景物影像的清晰度并不完全一致,其中焦点上的清晰度是最高的,其余的影像清晰度随着它与焦点的距离成正比例下降。先附上一张正常的照片和使用景深控制的照片:
通过左右两张照片的对比,我们很容易发现,通过景深处理的照片,我们可以很容易地抓住照片的重点部分。这也就是景深最大的用处,能够突出主题,并且可以使画面更有层次感。
在摄影技术中的景深,是通过调整相机的焦距,光圈来控制景深的,这里就不多说了。而我们的游戏中要想出现这种效果,就需要下一番功夫了。首先拆分一下图像的效果,图像中主要分为两部分,后面的模糊背景和前面清晰的&主题&部分。后面的背景模糊我们可以通过前面的两篇文章,来实现,而前景部分就是一张清晰的场景图,最后通过一定的权值将两者混合,离摄像机(准确地说是焦距)越远的部分,模糊图片的权重越高,离摄像机越近的部分,清晰图片的权重越高。那么问题来了,我们怎么知道哪个部分离摄像机更近呢?
二.Camera Depth Texture
上面说到,我们要怎么得到摄像机渲染的这个场景的图片中哪个部分离我们更远,哪个部分离我们更近?答案就是Camera Depth Texture这个东东。从字面上理解这个就是相机深度纹理。在Unity中,相机可以产生深度,深度+法线或者运动方向纹理,这是一个简化版本的G-Buffer纹理,我们可以用这个纹理进行一些后处理操作。这张纹理图记录了屏幕空间所有物体距离相机的距离。深度纹理中每个像素所记录的深度值是从0 到1 非线性分布的。精度通常是 24 位或16 位,这主要取决于所使用的深度缓冲区。当读取深度纹理时,我们可以得到一个0-1范围内的高精度值。如果你需要获取到达相机的距离或者其他线性关系的值,那么你需要手动计算它。
关于这张图是怎么样产生的,在Unity5之前是通过一个叫做Shader ReplaceMent的操作完成的。这个操作简单来说就是临时把场景中所有的shader换成我们想要的shader,然后渲染到张图上,我们通过Shader ReplaceMent操作,将场景中的对象shader换成按照深度写入一张纹理图。我们可以在5.X版本之前Unity自带的Shader中找到这个生成深度图的shader:Camera-DepthTexture.shader,这里我摘出一小段Tags中RenderType为Opaque的subshader:
Tags { &RenderType&=&Opaque& }
#pragma vertex vert
#pragma fragment frag
#include &UnityCG.cginc&
struct v2f
float4 pos : POSITION;
#ifdef UNITY_MIGHT_NOT_HAVE_DEPTH_TEXTURE
float2 depth : TEXCOORD0;
v2f vert( appdata_base v )
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
UNITY_TRANSFER_DEPTH(o.depth);
fixed4 frag(v2f i) : COLOR {
UNITY_OUTPUT_DEPTH(i.depth);
我们看到,当物体的渲染Tag为Opaque也就是不透明的时候,会写入深度纹理。而这个文件中其他的几个subshader也分别对针对不同类型的type,比如RenderType为TransparentCutout的subshader,就增加了一句下面的判断,去掉了所有应该透明的地方:
clip( texcol.a*_Color.a - _Cutoff );
而且这个shader中没有出现RnderType为Transparent类型的,因为透明的物体不会写入我们的深度贴图,也就是说我们开启了alpha blend类型的对象是不会写入深度的。
上面的代码中有几个宏定义,我们可以从UnityCG.cginc文件中找到这几个宏定义的实现:
// Depth render texture helpers
#if defined(UNITY_MIGHT_NOT_HAVE_DEPTH_TEXTURE)
#define UNITY_TRANSFER_DEPTH(oo) oo = o.pos.zw
#define UNITY_OUTPUT_DEPTH(i) return i.x/i.y
#define UNITY_TRANSFER_DEPTH(oo)
#define UNITY_OUTPUT_DEPTH(i) return 0
结合上面shader的使用,我们看出:UNITY_TRANSFER_DEPTH宏将传入vertex shader中的position的最后两位返回,也就是z坐标和w坐标,在unity里面也就是从屏幕向里看的那个方向就是z轴的方向。而UNITY_OUTPUT_DEPTH通过将z/w将真正的深度返回。UNITY_MIGHT_NOT_HAVE_DEPTH_TEXTURE是如果没有深度图的意思,也就是说,仅当没有获得深度图的时候,才会通过这个计算来计算深度,否则就无操作或者返回0。那么,这种情况下,深度信息从哪里来呢?我们看一下Unity的文档就知道了:
UNITY_TRANSFER_DEPTH(o): computes eye space depth of the vertex and outputs it ino(which must be a float2). Use it in a vertex program when rendering into a depth texture. On platforms with native depth textures this macro does nothing at all, because Z buffer value is rendered implicitly.
UNITY_OUTPUT_DEPTH(i): returns eye space depth fromi(which must be a float2). Use it in a fragment program when rendering into a depth texture. On platforms with native depth textures this macro always returns zero, because Z buffer value is rendered implicitly.
也就是说,如果硬件支持硬件深度的话,也就是直接从z buffer取深度,那么这个宏就没有必要了,因为这样的话,z buffer的深度是隐式渲染的。
关于深度纹理,深度法线纹理,运动方向纹理有很好的介绍,我们就不多说了,下面我们看一下怎么在Unity中开启深度的渲染。通过Camera.DepthTextureMode这个变量我们就可以控制是否开启深度的渲染,默认这个值是None,我们可以将其设为None,Depth,DepthNormals三种类型。只要开启了Depth模式,我们就可以在shader中通过_CameraDepthTexture来获得屏幕深度的纹理了。中也有详细介绍。下面我们通过一个后处理来实现一个最简单的输出屏幕深度的效果:
using UnityE
using System.C
[ExecuteInEditMode]
public class DepthTextureTest : PostEffectBase
void OnEnable()
GetComponent().depthTextureMode |= DepthTextureMode.D
void OnDisable()
GetComponent().depthTextureMode &= ~DepthTextureMode.D
void OnRenderImage(RenderTexture source, RenderTexture destination)
if (_Material)
Graphics.Blit(source, destination, _Material);
shader部分:
Shader &Custom/DepthTest& {
#include &UnityCG.cginc&
//仍然要声明一下_CameraDepthTexture这个变量,虽然Unity这个变量是unity内部赋值
sampler2D _CameraDepthT
sampler2D _MainT
_MainTex_TexelS
struct v2f
float4 pos : SV_POSITION;
: TEXCOORD0;
v2f vert(appdata_img v)
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.texcoord.
fixed4 frag(v2f i) : SV_Target
//直接根据UV坐标取该点的深度值
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, 1 - i.uv);
//将深度值变为线性01空间
depth = Linear01Depth(depth);
return float4(depth, depth, depth, 1);
ZWrite Off
Fog{ Mode Off }
#pragma vertex vert
#pragma fragment frag
找一个场景,将该脚本挂在摄像机并赋予材质。(注:PostEffectBase类为后处理基类,在中有详细实现,此处不予贴出),找到一个场景,我们测试一下:
原始场景效果:
开启输出深度后处理效果:
恩,场景图变成了一张黑白图像,越远的地方越亮,越近的地方越暗,也就是我们shader中所写的,直接按照深度值来输出了一幅图片。不过注意,这张图中,我把摄像机的远裁剪面调整到了50这一比较小的距离,这样,图中的远近信息显示得更加明显,而如果摄像机的远裁剪面距离很大,那么这张图的输出就会整体偏黑,因为离我们较近的物体距离占远裁剪面的距离太小了,几乎为0,所以就是黑的,如下图所示,当远裁剪面改为1000时深度图,仅有窗户的位置能看到白色:
关于CameraDepthTexture,在Unity4中CameraDepthTexture仍然是通过上面我们说的shader替换技术实现的,所以,一旦我们开启深度渲染,会导致DrawCall翻倍!而在Unity5中,这个CameraDepthTexture与Shadow Caster使用的是一套DepthTexture,通过带有Shadow Caster的对象才会被渲染到深度缓存中。关于Unity5和Unity4中深度缓存的渲染,介绍得很详细,可以进行参考。
三.景深效果实现
终于到了这篇文章的主题了,我们通过shader实现一个景深的效果。思路上面已经说过了,通过两张图片,一张清晰的,一张经过高斯模糊的,然后根据图片中每个像素的深度值在两张图片之间差值,就可以达到景深的效果了。下面附上景深效果代码:
shader部分:
Shader &Custom/DepthOfField& {
Properties{
_MainTex(&Base (RGB)&, 2D) = &white& {}
_BlurTex(&Blur&, 2D) = &white&{}
#include &UnityCG.cginc&
struct v2f_blur
float4 pos : SV_POSITION;
: TEXCOORD0;
float4 uv01 : TEXCOORD1;
float4 uv23 : TEXCOORD2;
float4 uv45 : TEXCOORD3;
struct v2f_dof
float4 pos : SV_POSITION;
: TEXCOORD0;
float2 uv1 : TEXCOORD1;
sampler2D _MainT
float4 _MainTex_TexelS
sampler2D _BlurT
sampler2D_float _CameraDepthT
float _focalD
float _nearBlurS
float _farBlurS
//高斯模糊 vert shader(上一篇文章有详细注释)
v2f_blur vert_blur(appdata_img v)
_offsets *= _MainTex_TexelSize.
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord.
o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);
o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;
o.uv45 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 3.0;
//高斯模糊 pixel shader(上一篇文章有详细注释)
fixed4 frag_blur(v2f_blur i) : SV_Target
fixed4 color = fixed4(0,0,0,0);
color += 0.40 * tex2D(_MainTex, i.uv);
color += 0.15 * tex2D(_MainTex, i.uv01.xy);
color += 0.15 * tex2D(_MainTex, i.uv01.zw);
color += 0.10 * tex2D(_MainTex, i.uv23.xy);
color += 0.10 * tex2D(_MainTex, i.uv23.zw);
color += 0.05 * tex2D(_MainTex, i.uv45.xy);
color += 0.05 * tex2D(_MainTex, i.uv45.zw);
//景深效果 vertex shader
v2f_dof vert_dof(appdata_img v)
//mvp矩阵变换
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//uv坐标传递
o.uv.xy = v.texcoord.
o.uv1.xy = o.uv.
//dx中纹理从左上角为初始坐标,需要反向
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y & 0)
o.uv.y = 1 - o.uv.y;
fixed4 frag_dof(v2f_dof i) : SV_Target
//取原始清晰图片进行uv采样
fixed4 ori = tex2D(_MainTex, i.uv1);
//取模糊普片进行uv采样
fixed4 blur = tex2D(_BlurTex, i.uv);
//取当位置对应的深度值
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
//将深度值转化到01线性空间
depth = Linear01Depth(depth);
//如果depth小于焦点的物体,那么使用原始清晰图像,否则使用模糊的图像与清晰图像的差值,通过差值避免模糊和清晰之间明显的边界,结果为远景模糊效果
fixed4 final = (depth &= _focalDistance) ? ori : lerp(ori, blur, clamp((depth - _focalDistance) * _farBlurScale, 0, 1));
//上面的结果,再进行一次计算,如果depth大于焦点的物体,使用上面的结果和模糊图像差值,得到近景模糊效果
final = (depth & _focalDistance) ? final : lerp(ori, blur, clamp((_focalDistance - depth) * _nearBlurScale, 0, 1));
//焦点位置是清晰的图像,两边分别用当前像素深度距离焦点的距离进行差值,这样就达到原理焦点位置模糊的效果
//上面的?在编译时会被编译成if语句,GPU并不擅长分支计算,而且如果有分支,两个分支都要跑。这里给了一个更优化一些的计算方式,不过语法比较晦涩
//float focalTest = clamp(sign(depth - _focalDistance),0,1);
//fixed4 final = (1 - focalTest) * ori + focalTest * lerp(ori, blur, clamp((depth - _focalDistance) * _farBlurScale, 0, 1));
//final = (focalTest)* final + (1 - focalTest) * lerp(ori, blur, clamp((_focalDistance - depth) * _nearBlurScale, 0, 1));
//pass 0: 高斯模糊
ZWrite Off
Fog{ Mode Off }
#pragma vertex vert_blur
#pragma fragment frag_blur
//pass 1: 景深效果
ZWrite Off
Fog{ Mode Off }
ColorMask RGBA
#pragma vertex vert_dof
#pragma fragment frag_dof
using UnityE
using System.C
[ExecuteInEditMode]
public class DepthOfFiled : PostEffectBase {
[Range(0.0f, 100.0f)]
public float focalDistance = 10.0f;
[Range(0.0f, 100.0f)]
public float nearBlurScale = 0.0f;
[Range(0.0f, 1000.0f)]
public float farBlurScale = 50.0f;
public int downSample = 1;
public int samplerScale = 1;
private Camera _mainCam =
public Camera MainCam
if (_mainCam == null)
_mainCam = GetComponent();
return _mainC
void OnEnable()
//maincam的depthTextureMode是通过位运算开启与关闭的
MainCam.depthTextureMode |= DepthTextureMode.D
void OnDisable()
MainCam.depthTextureMode &= ~DepthTextureMode.D
void OnRenderImage(RenderTexture source, RenderTexture destination)
if (_Material)
//首先将我们设置的焦点限制在远近裁剪面之间
Mathf.Clamp(focalDistance, MainCam.nearClipPlane, MainCam.farClipPlane);
//申请两块RT,并且分辨率按照downSameple降低
RenderTexture temp1 = RenderTexture.GetTemporary(source.width && downSample, source.height && downSample, 0, source.format);
RenderTexture temp2 = RenderTexture.GetTemporary(source.width && downSample, source.height && downSample, 0, source.format);
//直接将场景图拷贝到低分辨率的RT上达到降分辨率的效果
Graphics.Blit(source, temp1);
//高斯模糊,两次模糊,横向纵向,使用pass0进行高斯模糊
_Material.SetVector(&_offsets&, new Vector4(0, samplerScale, 0, 0));
Graphics.Blit(temp1, temp2, _Material, 0);
_Material.SetVector(&_offsets&, new Vector4(samplerScale, 0, 0, 0));
Graphics.Blit(temp2, temp1, _Material, 0);
//景深操作,景深需要两的模糊效果图我们通过_BlurTex变量传入shader
_Material.SetTexture(&_BlurTex&, temp1);
//设置shader的参数,主要是焦点和远近模糊的权重,权重可以控制插值时使用模糊图片的权重
_Material.SetFloat(&_focalDistance&, FocalDistance01(focalDistance));
_Material.SetFloat(&_nearBlurScale&, nearBlurScale);
_Material.SetFloat(&_farBlurScale&, farBlurScale);
//使用pass1进行景深效果计算,清晰场景图直接从source输入到shader的_MainTex中
Graphics.Blit(source, destination, _Material, 1);
//释放申请的RT
RenderTexture.ReleaseTemporary(temp1);
RenderTexture.ReleaseTemporary(temp2);
//计算设置的焦点被转换到01空间中的距离,以便shader中通过这个01空间的焦点距离与depth比较
private float FocalDistance01(float distance)
return MainCam.WorldToViewportPoint((distance - MainCam.nearClipPlane) * MainCam.transform.forward + MainCam.transform.position).z / (MainCam.farClipPlane - MainCam.nearClipPlane);
由于上面的原理&代码的注释已经比较清楚,这里不多介绍。景深效果是一个复合效果,其中的模糊效果前面的文章也有介绍,这篇文章的重点也就是通过DepthTexture来混合清晰和模糊的图像,来达到我们想要的&重点&清晰,&陪衬&模糊的效果。
大部分的景深效果是前景清晰,远景模糊,这也是景深的标准用法,不过有时候也有需要近景模糊,远景清晰的效果,或者前后都模糊,中间焦点位置清晰,在实现上我们通过像素点深度到达焦点的距离作为参数,在清晰和模糊图像之间插值,先计算远景的,结果与模糊图片再进行插值,得到最终的效果。
四.效果展示
在MainCamera上挂上DepthOfField脚本,将DepthOfFileld.shader赋给shader槽,即可看见景深的效果。
首先我们看一下清晰的场景图:
开启远景模糊的景深效果:
远近同时模糊的效果,只有焦点距离的对象清晰:不怨天,不尤人,下学而上达,知我者其天乎!
Unity的渲染路径
&&&&& 在Unity里,渲染路径(Rendering Path)决定了光照是如何应用到Unity Shader中的。因此,如果要和光源打交道,我们需要为每个Pass指定它使用的渲染路径,只有这样我们才能在Unity Shader中访问到Unity为我们准备好的光照信息。
&&&&& Unity支持多种类型的渲染路径。在Unity5.0版本之前,主要有3种:前向渲染路径(Forward RenderingPath)、延迟渲染路径(Deferred Rendering Path)和顶点照明渲染路径(Vertex Lit Rendering Path)。但在Unity5.0版本以后,Unity做了很多更改,主要有两个变化:首先,顶点照明渲染路径已经被Unity抛弃(但目前仍然可以对之前使用了顶点照明渲染路径的Unity Shader兼容);其次,新的延迟渲染路径代替了原来的延迟渲染路径(同样,目前也提供了对较旧版本的兼容)。
为整个项目设置渲染路径:
Edit-&Project Settings-&Player-&Other Settings-&Rendering Path
但有时,我们希望可以使用多个渲染路径,例如摄像机A渲染的物体使用前向渲染路径,而摄像机B渲染的物体使用延迟渲染路径。这时,我们可以在每个摄像机的渲染路径设置中设置该摄像机使用的渲染路径,以覆盖Project Setting中的设置。如图所示。
需要注意的是,如果当前的显卡并不支持所选择的渲染路径,Unity会自动使用更低一级的渲染路径。例如,如果一个GPU不支持延迟渲染,那么Unity就会使用前向渲染。完成了渲染路径的设置后,我们就可以在每个Pass中使用标签来指定该Pass使用的渲染路径。这是通过设置Pass的LightMode标签实现的。不同类型的渲染路径可能会包含多种标签设置。例如:
&&&& //告诉Unity该Pass使用前向渲染路径中的ForwardBase路径。
&&&& Tags { "LightMode" = "ForwardBase" }
Unity5.x LightMode标签支持的渲染路径设置选项
&不管使用哪种渲染路径,该Pass总是会被渲染,但不会计算任何光照
&ForwardBase
&用于前向渲染。该Pass会计算环境光、最重要的平行光、逐顶点/SH光源和Lightmaps
&ForwardAdd
&用于前向渲染。该Pass会计算额外的逐像素光源,每个Pass对应一个光源
&用于延迟渲染。该Pass会渲染G缓冲(G-buffer)
&ShadowCaster
&把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中
&PrepassBase
&用于遗留的延迟渲染。该Pass会渲染法线和高光反射的指数部分
&PrepassFinal
&用于遗留的延迟渲染。该Pass通过合并纹理、光照和自发光来渲染得到最后的颜色
&Vertex、VertexLMRGBM和VertexLM
&用于遗留的顶点照明渲染
&&&&&&只有指定了渲染路径,Unity才会为内置光照变量赋上正确的值。我们才能在Unity Shader中访问这些属性值。如果我们没有指定任何渲染路径(实际上,在unity5.x版本中如果使用了前向渲染又没有为Pass指定任何前向渲染适合的标签,就会被当成一个和顶点照明渲染路径等同的Pass),那么一些光照变量很可能不会被正确赋值,我们计算出的效果也就很有可能是错误的。
前向渲染路径
前向渲染路径是传统的渲染方式,也是我们最常用的一种渲染路径。
1. 前向渲染路径的原理
每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲区,一个是深度缓冲区。我们利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区中的颜色值。我们可以用下面的伪代码来描述前向渲染路径的大致过程:
&&& for (each primitive in this model) {
&&&&&& for (each fragment covered by this primitive) {
&&&&&&&&&& if (failed in depth test) {
&&&&&&&&&&&&& //如果没有通过深度测试,说明该片元是不可见的
&&&&&&&&&&&&&
&&&&&&&&&& } else {
&&&&&&&&&&&&& //如果该片元可见
&&&&&&&&&&&&& //就进行光照计算
&&&&&&&&&&&&& float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
&&&&&&&&&&&&& //更新帧缓冲
&&&&&&&&&&&&& writeFrameBuffer(fragment, color);
&&&&&&&&&& }
对于每个逐像素光源,我们都需要进行上面一次完整的渲染流程。如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。假设,场景中有N个物体,每个物体受M个光源影响,那么要渲染整个场景一共需要N*M个Pass。可以看出,如果有大量逐像素光照,那么需要执行的Pass数目也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照的数目。
Unity中的前向渲染
&&&&& 事实上,一个Pass不仅仅可以用来计算逐像素光照,它也可以用来计算逐顶点等其他光照。这取决于光照计算所处流水线阶段以及计算时使用的数学模型。当我们渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式。
&&&&& 在Unity中,前向渲染路径有3种处理光照(即照亮物体)的方式;逐顶点处理、逐像素处理、球谐函数(Spherical Harmonics, SH)处理。而决定一个光源使用哪种处理模式取决于它的类型和渲染模式。光源类型指的是该光源是平行光还是其他类型的光源,而光源的渲染模式指的是该光源是否是重要的(Important)。如果我们把一个光照的模式设置为Important,意味着我们告诉Unity,“嘿老兄,这个光源很重要,我希望你可以认真对待它,把它当成一个逐像素光源来处理!”我们可以在光源的Light组件中设置这些属性,如图:
&&&&& 在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。其中,一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。Unity使用的判断规则如下:
场景中最亮的平行光总是按逐像素处理的。
渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理。
渲染模式被设置成Important的光源,会按逐像素处理。
如果根据以上规则得到的逐像素光源数量小于Quality Setting中的逐像素光源数量(Pixel Light Count),会有更多的光源以逐像素的方式进行渲染。
那么,在哪里进行光照计算呢?当然是在Pass里。前面提到过,前向渲染有两种Pass:Base Pass和Additional Pass。
前向渲染的两种Pass
1、Base Pass
可实现的光照效果:
阴影(平行光的阴影)
(1)渲染设置
Tags { "LightMode"="ForwardBase" }
#pragma multi_compile_fwdbase
(2)光照计算
一个逐像素的平行光以及所有逐顶点和SH光源
2、Additional Pass
可实现的光照效果:
默认情况下不支持阴影,但可以通过使用#pragma multi_compile_fwdadd_fullshadows编译指令来开启阴影
(1)渲染设置
Tags { "LightMode"="ForwardAdd" }
Blend One One
#pragma multi_compile_fwdadd
(2)光照计算
其他影响该物体的逐像素光源,每个光源执行一次Pass
有几点需要说明的地方:
首先,可以发现在渲染设置中,我们除了设置了Pass的标签外,还使用了#pragma multi_compile_fwdbase这样的编译指令。虽然#pragma multi_compile_fwdbase和#pragma multi_compile_fwdadd在官方文档中还没有给出相关说明,但实验表明,只有分别为Base Pass和Additional Pass使用这两个编译指令,我们才可以在相关的Pass中得到一些正确的光照变量,例如光照衰减值等。
在Base Pass中,我们可以访问光照纹理(lightmap)。
Base Pass中渲染的平行光默认是支持阴影的(如果开启了光源的阴影功能),而Additional Pass中渲染的光源默认情况下是没有阴影效果的,即便我们在它的Light组件中设置了有阴影的Shadow Type。但我们可以在Additional Pass中使用#pragma multi_compile_fwdadd_fullshadows代替#pragma multi_compile_fwdadd编译指令,为点光源和聚光灯开启阴影效果,但这需要Unity在内部使用更多的Shader变种。
环境光和自发光也是在Base Pass中计算的。这是因为,对于一个物体来说,环境光和自发光我们只希望计算一次即可,而如果我们在Additional Pass中计算这两种光照,就会造成叠加多次环境光和自发光,这不是我们想要的。
在Additional Pass的渲染设置中,我们还开启和设置了混合模式。这是因为,我们希望每个AdditionalPass可以与上一次的光照结果在帧缓存中进行叠加,从而得到最终的有多个光照的渲染效果。如果我们没有开启和设置混合模式,那么Additional Pass的渲染结果会覆盖掉之前的渲染结果,看起来就好像该物体只受该光源的影响。通常情况下,我们选择的混合模式是Blend One One。
对于前向渲染来说,一个Unity Shader通常会定义一个Base Pass(Base Pass也可以定义多次,例如需要双面渲染等情况)以及一个Additional Pass。一个Base Pass仅会执行一次(定义了多个Base Pass的情况除外),而一个Additional Pass会根据影响该物体的其他逐像素光源的数目被多次调用,即每个逐像素光源会执行一次Additional Pass。
&&&&& 上面给出的光照计算是通常情况下我们在每种Pass中进行的计算。实际上,渲染路径的设置用于告诉Unity该Pass在前向渲染路径中的位置,然后底层的渲染引擎会进行相关计算并填充一些内置变量(如_LightColor0等),如何使用这些内置变量进行计算完全取决于开发者的选择。例如,我们完全可以利用Unity提供的内置变量在Base Pass中只进行逐顶点光照;同样,我们也完全可以在Additional Pass中按逐顶点的方式进行光照计算,不进行任何逐像素光照计算。
Unity5.x中前向渲染可以使用的内置光照变量
_LightColor0
该Pass处理的逐像素光源的颜色
_WorldSpaceLightPos0
_WorldSpaceLightPos0.xyz是该Pass处理的逐像素光源的位置。如果该光源是平行光,那么_WorldSpaceLightPos0.w是0,其他光源类型w值为1
_LightMatrix0
从世界空间到光源空间的变换矩阵。可以用于采样cookie和光强衰减(attenuation)纹理
unity_4LightPosX0,unity_4LightPosY0,
unity_4LightPosZ0
仅用于Base Pass。前4个非重要的点光源在世界空间中的位置
unity_4LightAtten0
仅用于Base Pass。存储了前4个非重要的点光源的衰减因子
unity_LightColor
仅用于Base Pass。存储了前4个非重要的点光源的颜色
&Unity5.x中前向渲染可以使用的内置光照函数
float3 WorldSpaceLightDir(float4 v)
仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。内部实现使用了UnityWorldSpaceLightDir函数。没有被归一化
float3 UnityWorldSpaceLightDir(float4 v)
仅可用于前向渲染中。输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化
float3 ObjSpaceLightDir(float4 v)
仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化
float3 Shade4PointLights(...)
仅可用于前向渲染中。计算四个点光源的光照,它的参数是已经打包进矢量的光照数据,通常就是前向渲染可以使用的内置光照变量,如unity_4LightPosX0、unity_4LightPosY0、unity_4LightPosZ0、unity_LightColor0和unity_4LightAtten0等。前向渲染通常会使用这个函数来计算逐顶点光照
需要说明的是,上表给出的变量和函数并不是完整的,一些前向渲染可以使用的内置变量和函数官方文档中并没有给出说明。
顶点照明渲染路径
&&&&& 顶点照明渲染路径是对硬件配置要求最少、运算性能最高,但同时也是得到的效果最差的一种类型,它不支持那些逐像素才能得到的效果,例如阴影、法线映射、高精度的高光反射等。实际上,它仅仅是前向渲染路径的一个子集,也就是说,所有可以在顶点照明渲染路径中实现的功能都可以在前向渲染路径中完成。就如它的名字一样,顶点照明渲染路径只是使用了逐顶点的方式来计算光照,并没有什么神奇的地方。实际上,我们在上面的前向渲染路径中也可以计算一些逐顶点的光源。但如果选择使用顶点照明渲染路径,那么Unity会只填充那些逐顶点相关的光源变量,意味着我们不可以使用一些逐像素光照变量。
1. Unity中的顶点照明渲染
&&&&& 顶点照明渲染路径通常在一个Pass中就可以完成对物体的渲染。在这个Pass中,我们会计算我们关心的所有光源对该物体的照明,并且这个计算是按逐顶点处理的。这是Unity中最快速的渲染路径,并且具有最广泛的硬件支持(但是游戏机上并不支持这种路径)。
&&&&& 由于顶点照明渲染路径仅仅是前向渲染路径的一个子集,结果是,Unity5中将顶点照明渲染路径作为一个遗留的渲染路径,在未来的版本中,顶点照明渲染路径的相关设定可能会被移除。
2. 可访问的内置变量和函数
&&&&& 在Unity中,我们可以在一个顶点照明的Pass中最多访问到8个逐顶点光源。如果我们只需要渲染其中两个光源对物体的照明,可以仅使用下表中内置光照数据的前两个。如果影响该物体的光源数目小于8,那么数组中剩下的光源颜色会设置成黑色。
Unity5.x中顶点照明渲染路径中可以使用的内置变量
&unity_LightColor
&unity_LightPosition
&float4[8]
xyz分量是视角空间中的光源位置。如果光源是平行光,那么z分量值为0,其他光源类型z分量值为1
&unity_LightAtten
光源衰减因子。如果光源是聚光灯,x分量是cos(spotAngle/2),y分量是1/cos(spotAngle/4);如果是其他类型的光源,x分量是-1,y分量是1。z分量是衰减的平方,w分量是光源范围开根号的结果
&unity_SpotDirection
&float4[8]
如果光源是聚光灯的话,值为视角空间的聚光灯的位置;如果是其他类型的光源,值为(0,0,1,0)
可以看出,一些变量我们同样可以在前向渲染路径中使用,例如unity_LightColor。但这些变量数组的维度和数值在不同渲染路径中的值是不同的。
Unity5.x中顶点照明渲染路径中可以使用的内置函数
&float3 ShadeVertexLights(float4 vertex, float3 normal)
输入模型空间中的顶点位置和法线,计算四个逐顶点光源的光照以及环境光。内部实现实际上调用了ShadeVertexLightsFull函数
&float3 ShadeVertexLightsFull(float4 vertex, float3 normal, int lightCount, bool spotLight)
输入模型空间中的顶点位置和法线,计算lightCount个光源的光照以及环境光。如果spotLight值为true,那么这些光源会被当成聚光灯来处理,虽然结果更精确,但计算更加耗时; 否则,按点光源处理
延迟渲染路径
&&&&&&前向渲染的问题是:当场景中包含大量实时光源时,前向渲染的性能会急速下降。例如,如果我们在场景的某一块区域放置了多个光源,这些光源影响的区域互相重叠,那么为了得到最终的光照效果,我们就需要为该区域内的每个物体执行多个Pass来计算不同光源对该物体的光照结果,然后在颜色缓存中把这些结果混合起来得到最终的光照。然而,每执行一个Pass我们都需要重新渲染一遍物体,但很多计算实际上是重复的。
&&&&& 延迟渲染是一种更古老的渲染方法,但由于上述前向渲染可能造成的瓶颈问题,近几年又流行起来。除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还会利用额外的缓冲区,这些缓冲区也被称为G缓冲(G-buffer),其中G是英文Geometry的缩写。G缓冲区存储了我们所关心的表面(通常指的是离摄像机最近的表面)的其他信息,例如该表面的法线、位置、用于光照计算的材质属性等。
1. 延迟渲染的原理
&&&&& 延迟渲染主要包含两个Pass。在第一个Pass中,我们不进行任何光照计算,而是仅仅计算哪些片元是可见的,这主要是通过深度缓冲技术来实现,当发现一个片元是可见的,我们就把它的相关信息存储到G缓冲区中。然后,在第二个Pass中,我们利用G缓冲区的各个片元信息,例如表面法线、视角方向、漫反射系数等,进行真正的光照计算。
延迟渲染的过程大致可以用下面的伪代码来描述:
&&&& //第一个Pass不进行真正的光照计算
&&&& //仅仅把光照计算需要的信息存储到G缓冲中
&&&& for (each primitive in this model) {
&&&&&&&&& for (each fragment covered by this primitive) {
&&&&&&&&&&&&&& if (failed in depth test) {
&&&&&&&&&&&&&&&&&& //如果没有通过深度测试,说明该片元是不可见的
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&& } else {
&&&&&&&&&&&&&&&&&& //如果该片元可见
&&&&&&&&&&&&&&&&&& //就把需要的信息存储到G缓冲中
&&&&&&&&&&&&&&&&&& writeGBuffer(materialInfo, pos, normal, lightDir, viewDir);
&&&&&&&&&&&&&& }
&&&&&&&&& }
&&&&& //利用G缓冲中的信息进行真正的光照计算
&&&&& for (each pixel in the screen) {
&&&&&&&&&& if (the pixel is valid) {
&&&&&&&&&&&&& //如果该像素是有效的
&&&&&&&&&&&&& //读取它对应的G缓冲中的信息
&&&&&&&&&&&&& readGBuffer(pixel, materialInfo, pos, normal, lightDir, viewDir);
&&&&&&&&&&&&& //根据读取到的信息进行光照计算
&&&&&&&&&&&&& float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
&&&&&&&&&&&&& //更新帧缓冲
&&&&&&&&&&&&& writeFrameBuffer(pixel, color);
&&&&&&&&&& }
可以看出,延迟渲染使用的Pass数目通常就是两个,这跟场景中包含的光源数目是没有关系的。换句话说,延迟渲染的效率不依赖于场景的复杂度,而是和我们使用的屏幕空间的大小有关。这是因为,我们需要的信息都存储在缓冲区中,而这些缓冲区可以理解成是一张张2D图像,我们的计算实际上就是在这些图像空间中进行的。
2. Unity中的延迟渲染
&&&&&&Unity有两种延迟渲染路径,一种是遗留的延迟渲染路径,即Unity5之前使用的延迟渲染路径,而另一种是Unity5.x中使用的延迟渲染路径。如果游戏中使用了大量的实时光照,那么我们可能希望选择延迟渲染路径,但这种路径需要一定的硬件支持。
&&&&& 新旧延迟渲染路径之间的差别很小,只是使用了不同的技术来权衡不同的需求。例如,较旧版本的延迟渲染路径不支持Unity5的基于物理的Standard Shader。以下我们仅讨论Unity5后使用的延迟渲染路径。对于遗留的延迟渲染路径,可以在官方文档()找到更多资料。
&&&&& 对于延迟渲染路径来说,它最适合在场景中光源数目很多、如果使用前向渲染会造成瓶颈的情况下使用。而且,延迟渲染路径中的每个光源都可以按逐像素的方式处理。但是,延迟渲染也有一些缺点。
不支持真正的抗锯齿(anti-aliasing)功能。
不能处理半透明物体。
对显卡有一定要求。如果要使用延迟渲染的话,显卡必须支持MRT(Multiple Render Targets)、Shader Mode 3.0及以上、深度渲染纹理以及双面的模板缓冲。
当使用延迟渲染时,Unity要求我们提供两个Pass。
(1) 第一个Pass用于渲染G缓冲。在这个Pass中,我们会把物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中。对于每个物体来说,这个Pass仅会执行一次。
(2) 第二个Pass用于计算真正的光照模型。这个Pass会使用上一个Pass中渲染的数据来计算最终的光照颜色,再存储到帧缓冲中。
&&&&& 默认的G缓冲区(注意,不同Unity版本的渲染纹理存储内容会有所不同)包含了以下几个渲染纹理(Render Texture, RT)。
RT0: 格式是ARGB32,RGB通道用于存储漫反射颜色,A通道没有被使用。
RT1: 格式是ARGB32,RGB通道用于存储高光反射颜色,A通道用于存储高光反射的指数部分。
RT2: 格式是ARGB2101010,RGB通道用于存储法线,A通道没有被使用。
RT3: 格式是ARGB32(非HDR)或ARGBHalf(HDR),用于存储自发光+lightmap+反射探针(reflection probes)。
深度缓冲和模板缓冲。
&&&&&&当在第二个Pass中计算光照时,默认情况下仅可以使用Unity内置的Standard光照模型。如果我们想要使用其他的光照模型,就需要替换掉原有的Internal-DeferredShading.shader文件。更详细的信息可以访问官方文档()。
&Unity5.x延迟渲染路径中可以使用的内置变量
&_LightColor
&_LightMatrix0
&从世界空间到光源空间的变换矩阵。可以用于采样cookie和光强衰减纹理
选择哪种渲染路径?
&&&&& Unity的官方文档()中给出了4种渲染路径(前向渲染路径、延迟渲染路径、遗留的延迟渲染路径和顶点照明渲染路径)的详细比较,包括它们的特性比较(是否支持逐像素光照、半透明物体、实时阴影等)、性能比较以及平台支持。
&&&&& 总体来说,我们需要根据游戏发布的目标平台来选择渲染路径。如果当前显卡不支持所选渲染路径,那么Unity会自动使用比其低一级的渲染路径。
Flash游戏设计:
Unity游戏设计:}

我要回帖

更多关于 ps高级混合选项 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信