您的位置:首页 > 其它

Shader入门精要学习记录5

2018-02-10 14:55 381 查看
在Unity里,渲染路径(Rendering Path)决定了光照是如何应用到Unity shader中的。因此如果要和光源打交道,我们需要为每个Pass指定它使用的渲染路径,只有这样Shader的光照计算才能被正确执行。

注意 是每个pass

Unity支持多种类型的渲染路径。在5.0版本之前,主要有3种:前向渲染路径(Forward Rendering Path),延迟渲染路径(Deferred Rendering Path),顶点照明渲染路径(Vertex Lit Rendering Path)。但在5.0之后主要有两个变化:1.顶点照明渲染路径已经被Unity舍弃(但目前仍然可以对之前使用了顶点照明渲染路径的Unity Shader兼容);2.新的延迟渲染路径代替了原来的延迟渲染路径(同样,目前也提供了对较旧版本的兼容)。

注意: 两个变化

大多数情况下,一个项目只使用一种渲染路径,因此我们可以为整个项目设置渲染时的渲染路径。默认是前向渲染。

但有时我们希望可以使用多个渲染路径,例如摄像机A使用前向渲染,摄像机B使用延迟渲染,我们可以在每个摄像机的渲染路径中设置该摄像机使用的渲染路径,以覆盖Project Setting中的设置。如果当前的显卡并不支持所选择的渲染路径,Unity会自动使用更低一级的渲染路径。例如:一个GPU不支持延迟渲染,那么Unity就会使用前向渲染。

Pass{
Tags{"LightMode"="ForwardBase"}


上面的pass使用前向渲染路径中的ForwardBase.而前向渲染路径还有一种路径叫做ForwardAdd。

LightMode支持的渲染路径设置选项和意义;

Always 不管使用哪种渲染路径,该Pass总是会被渲染,但不会计算任何光照

ForwardBase 用于前向渲染。改pass会计算环境光,最重要的平行光,逐顶点/SH光源和Lightmaps

ForwardAdd 用于前向渲染。改Pass会计算额外的逐像素光源,每个Pass对应一个光源

Deferred 用于延迟渲染。 该Pass会渲染G缓冲(Gbuffer)

ShaderCaster 把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中。

PrepassBase 用于遗留的延迟渲染。该Pass会渲染法线和高光反射的指数部分。

PrepassFinal 用于遗留的延迟渲染。该Pass通过合并纹理,光照和自发光来渲染得到最后的颜色

Vertex,VertexLMRGBM和VertexLM 用于遗留的顶点照明渲染。

如果我们没有指定任何前向渲染适合的标签,就会被当成一个和顶点照明渲染路径等同的Pass。

Unity 前向渲染路径

前向渲染路径是传统的渲染方式,也是我们最常用的一种渲染路径。我们首先会概括前向渲染路径的原理,然后再给出Unity对于前向渲染路径的实现细节和要求。最后给出Unity Shader哪些内置变量是用于前向渲染的。

前向渲染路径的原理

没进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲区,一个是深度缓冲区。 我们利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区中的颜色值。我们可以用下面的伪代码来描述前向渲染路径的大致过程:



对于每个逐像素光源,我们都需要进行上面一次完整的渲染流程。如果一个物体在多个逐像素光源的影响与区内,那么该物体就需要执行多少个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。假设,场景中有N个物体,每个物体受M个光源的影响,那么渲染整个场景一共需要M*N个Pass。可以看出,如果有大量逐像素光照,那么需要执行的Pass数目也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照的数目。

Unity中的前向渲染

事实上,一个Pass不仅仅可以用来计算逐像素光照,它也可以用来计算逐顶点等其他光照。这取决于光照计算所处流水线以及计算时使用的数据模型。当我们渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式。

在Unity中,前向渲染路径有3
ee1b
种处理光照的方式:逐顶点处理,逐像素处理,球谐函数(Spherical Harmonics,SH)处理。
而决定一个光源使用哪种处理模式取决于它的类型和渲染模式。光源类型指的是该光源是平行光还是其他类型的光源,而光源的渲染模式指的是该光源是否是重要的。如果我们把一个光照的模式设置为Important,意味着我们告诉Unity,把这个光源当成一个逐像素光源来处理!在Lighting组件可以设置这些属性。



在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近,光照强度等)对这些光源进行一个重要度排序。其中,一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。Unity使用的判断规则如下。

场景中最亮的平行光总是按逐像素处理的。

渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理。

渲染模式被设置成Important的光源,会按逐像素处理。

如果根据以上规则得到的逐像素光源数量小于Quality Setting中的逐像素光源数量(Pixel Light Count),会有更多的光源以逐像素的方式进行渲染。

那么,在哪里进行光照计算呢? 当然在Pass里,前面提到过,前向渲染有两种Pass:Base Pass和Additional Pass。通常来说,这两种Pass进行的标签和渲染设置以及常规光照计算如图:



注意:

在图中的渲染设置中除了设置Pass标签外,还使用了#pragma multi_compile_fwdbase这样的编译指令。虽然#pragma multi_compile fwdbase#pragma multi_compile_fwdadd在官方文档并未说明,但实验表明,只有分别为Base Pass和Additional Pass使用这两个编译指令,我们才可以在相关的Pass中得到一些正确的光照变量,列如光照衰减值等。

Base Pass旁边的注释给出了Base 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的渲染设置中,我们还开启和设置了混合模式。这是因为,我们希望每个 Additional Pass可以与上一次的光照结果在帧缓冲中进行叠加,从而得到最终的有多个光照的渲染效果。如果我们没有开启和设置混合模式,那么Additional Pass的渲染结果会覆盖掉之前的渲染结果,看起来就好像该物体只受该光源的影响。通常情况下,我们选择的混合模式是Blend One One.

对于前向渲染来说,一个shader通常会定义一个Base Pass 以及一个Addtional pass。一个Base Pass仅会执行一次(定义了多个Base Pass的情况除外),而一个Additional Pass会根据影响该物体的其它逐像素光源的数目被多次调用,即每个逐像素光源会执行一次Additional Pass。

上图给出的光照计算是在通常情况下我们在每种Pass中进行的计算。实际上,渲染路径的设置用于告诉Unity该Pass在前向渲染中的位置,然后底层的渲染引擎会进行相关计算并填充一些内置变量(如——lightcolor0等),如何使用这些内置变量进行计算完全取决于开发者的选择。

* 内置的光照变量和函数*





顶点照明渲染路径

顶点照明渲染路径是对硬件配置要求最少,运算性能最高,但同时也是得到的效果最差的一种类型,它不支持那些逐像素才能得到的效果,例如 阴影、法线映射、高精度的高光反射。实际上,它仅仅是前向渲染路径的一个子集,也就是说,所有可以在顶点照明渲染路径中实现的功能都可以在前向渲染路径中完成。 顶点照明渲染路径只是使用了逐顶点的方式来计算光照,并没有神奇的地方, **实际上我们在前向渲染路径中也可以计算一些逐顶点的光源。但如果选择使用顶点照明渲染路径,那么Unity会只填充那些逐顶点相关的光源变量,意味着我们不可以使用一些逐像素光照变量。

**

Unity的顶点照明渲染。

顶点照明渲染通常在一个Pass模块就可以完成对物体的渲染,在这个Pass中,我们会计算我们关心的所有光源对该物体的照明,并且这个计算是按逐顶点处理的。这是Unity中最快速的渲染路径,并且具有广泛的硬件支持(游戏机不支持)。
顶点照明渲染路径作为一个遗留的渲染路径,在未来的版本中,顶点照明渲染路径的相关设置可能会被移除。


可访问的内置变量和函数



延迟渲染路径

前向渲染的问题是:当场景中包含大量实时光源时,前向渲染的性能会急速下降。

如果我们在场景的某一块区域内放置了多个光源,这些光源影响的区域会互相重叠,那么为了得到最终的光照效果,我们就需要为该区域的每个物体执行多个Pass来计算不同光源对该物体的光照结果,然后在颜色缓冲中把这些结果混合起来得到最终的光照。然而每执行一个Pass我们都需要重新渲染一遍物体,很多计算实际上是重复的。

延迟渲染是一种更古老的渲染方法,但由于上述前向渲染可能造成的瓶颈问题,近几年又流行起来。除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还会利用额外的缓冲区,这些缓冲区也被统称为G缓冲(G-buff),其中G为Geometry的缩写。G缓冲区储存了我们所关心的表面的其他信息,例如该表面的法线、位置、用于光照计算的材质属性等。

延迟渲染的原理

延迟渲染主要包含了两个Pass。在第一个Pass中我们不进行任何光照计算,而仅仅计算哪些片元是可见的,主要是通过深度缓冲技术来实现,当发现一个片元是可见的,我们就把它的相关信息储存到G缓冲区中。然后,在第二个Pass中,我们利用G缓冲区的各个片元信息,例如表面法线。视角方向。漫反射系数等,进行真正的光照计算。

延迟渲染的过程大致可以用下面的伪代码来描述:





可以看出延迟渲染使用的Pass数目通常就是两个,这跟场景中包含的光源数目是没有关系的。换句话来说,延迟渲染的效率不依赖于场景的复杂度,而是和我们使用的屏幕空间的大小有关。这是因为,我们需要的信息都储存在缓冲区中,而这些缓冲区可以理解成一张张2D图像,我们的计算实际上就是在这些图像空间中进行的。



Unity的光源类型

平行光 点光源 聚光源

在Shader中访问它们的5种属性:

位置

方向

颜色

强度

衰减

Shader 前向渲染多个光源

Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }

Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }

CGPROGRAM

// Apparently need to add this declaration
#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.worldNormal = UnityObjectToWorldNormal(v.normal);

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

fixed atten = 1.0;

return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}

ENDCG
}

Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }

Blend One One

CGPROGRAM

// Apparently need to add this declaration
#pragma multi_compile_fwdadd

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"
#include "AutoLight.cginc"

fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.worldNormal = UnityObjectToWorldNormal(v.normal);

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else
fixed atten = 1.0;
#endif
#endif

return fixed4((diffuse + specular) * atten, 1.0);
}

ENDCG
}
}
FallBack "Specular"
}


Unity的光照衰减

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: