#pragma multi_compile_fwdbase

来源:互联网 发布:淘宝朋友代付退款 编辑:程序博客网 时间:2024/05/29 19:17

ForwardBase

Tags { "LightMode" = "ForwardBase" }
  • 1
  • 1

告诉渲染管线,这个pass作为ForwardBase处理,缺少它将画不出任何东西。

#pragma multi_compile_fwdbase
  • 1
  • 1

unity官方文档,只有multi_compile的说明,对于multi_compile_fwdbase可谓只字未提,网上唯一能搜到的一篇也是一语带过(虽然不甚详细,但是非常感谢作者分享)。后来打开unity shader面板的Variants里看到:

DIRECTIONAL LIGHTMAP_OFF DIRLIGHTMAP_OFF SHADOWS_OFFDIRECTIONAL LIGHTMAP_ON DIRLIGHTMAP_OFF SHADOWS_OFFDIRECTIONAL LIGHTMAP_ON DIRLIGHTMAP_ON SHADOWS_OFFDIRECTIONAL LIGHTMAP_OFF DIRLIGHTMAP_OFF SHADOWS_SCREENDIRECTIONAL LIGHTMAP_ON DIRLIGHTMAP_OFF SHADOWS_SCREENDIRECTIONAL LIGHTMAP_ON DIRLIGHTMAP_ON SHADOWS_SCREENDIRECTIONAL LIGHTMAP_OFF DIRLIGHTMAP_OFF SHADOWS_OFF VERTEXLIGHT_ONDIRECTIONAL LIGHTMAP_OFF DIRLIGHTMAP_OFF SHADOWS_SCREEN VERTEXLIGHT_ONDIRECTIONAL LIGHTMAP_OFF DIRLIGHTMAP_OFF SHADOWS_SCREEN SHADOWS_NATIVEDIRECTIONAL LIGHTMAP_ON DIRLIGHTMAP_OFF SHADOWS_SCREEN SHADOWS_NATIVEDIRECTIONAL LIGHTMAP_ON DIRLIGHTMAP_ON SHADOWS_SCREEN SHADOWS_NATIVEDIRECTIONAL LIGHTMAP_OFF DIRLIGHTMAP_OFF SHADOWS_SCREEN SHADOWS_NATIVE VERTEXLIGHT_ON
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

很多看过unity生成的vertex/fragment代码的同学,会感到很困惑,代码里充斥着各种条件编译代码,然而unity也没有给出文档,到底有哪些keyword?各自的作用是什么? 
看到这个就非常清楚了,unity为forwardbase pass定义的keyword都在这里,而multi_compile_fwdbase就是unity专门为forwardbase预定义的multi_compile。 
如果再打开unity最终编译成的glsl代码的话,可以看到,unity为每一组keywards都生成了单独的代码。 
如果缺少这行代码的话,那么unity默认会编译

DIRECTIONAL LIGHTMAP_ON DIRLIGHTMAP_ON SHADOWS_OFF
  • 1
  • 1

这一组条件,碰到其他的情况,那么渲染就会出错。 
我们看到,上述每一组条件,都是DIRECTIONAL,那么如果场景中没有平行光呢?点光源或者聚光灯还会起作用么? 
答案是不会。forwardbase pass只能以逐像素的方式处理平行光,点光源和聚光灯都会被忽略掉,对应的_LightColor0都将是黑色。注意上面黑体的逐像素,因为可能有人会说,我场景里只有一个点光源,可以把场景照亮。是的,但它是以逐顶点的方式照亮的,后面将会细说。

// vertex-to-fragment interpolation data#ifdef LIGHTMAP_OFFstruct v2f_surf {  float4 pos : SV_POSITION;  float2 pack0 : TEXCOORD0;  fixed3 normal : TEXCOORD1;  fixed3 vlight : TEXCOORD2;  LIGHTING_COORDS(3,4)};#endif#ifndef LIGHTMAP_OFFstruct v2f_surf {  float4 pos : SV_POSITION;  float2 pack0 : TEXCOORD0;  float2 lmap : TEXCOORD1;  LIGHTING_COORDS(2,3)};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

unity定义了v2f结构,有无lightmap的两个版本。

LIGHTING_COORDS(3,4)
  • 1
  • 1

这个定义在AutoLight.cginc中,它定义了光照和阴影坐标。因为forwardbase只支持平行光,因为平行光的特性,受到的光照强度是一样的,所以并不需要光照坐标,如果投射阴影的话,只需要阴影坐标来采样shadow map。(稍后再forwardadd章节中,会详细讨论不同光源的光照计算) 
现在只要知道,这个宏的定义类似如下所示。参数3,4就是下个可用的TEXCOORD序号。

#define LIGHTING_COORDS(idx1,idx2) float3 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)
  • 1
  • 1

接下去就是填充v2f结构,计算齐次空间坐标,uv坐标,normal或者lightmap的uv坐标。就如我们平时写vertex/fragment shader那样,就不累述了。

float3 shlight = ShadeSH9 (float4(worldN,1.0));
  • 1
  • 1

这里是计算球谐光照,没有作为像素光照和顶点光照处理的光源,都在这里计算,包括light probe。

o.vlight += Shade4PointLights (    unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,    unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,    unity_4LightAtten0, worldPos, worldN );
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

这里计算顶点光照,unity只能处理4个顶点光源。unity在上层处理掉了很多东西。如果光源影响到该物体,那么光源的属性(位置,原色,衰减)都会被设置,反之则会直接被忽略。具体计算过程在UnityCG.cginc中,如下所示:

float3 Shade4PointLights (    float4 lightPosX, float4 lightPosY, float4 lightPosZ,    float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,    float4 lightAttenSq,    float3 pos, float3 normal){    // to light vectors    float4 toLightX = lightPosX - pos.x;    float4 toLightY = lightPosY - pos.y;    float4 toLightZ = lightPosZ - pos.z;    // squared lengths    float4 lengthSq = 0;    lengthSq += toLightX * toLightX;    lengthSq += toLightY * toLightY;    lengthSq += toLightZ * toLightZ;    // NdotL    float4 ndotl = 0;    ndotl += toLightX * normal.x;    ndotl += toLightY * normal.y;    ndotl += toLightZ * normal.z;    // correct NdotL    float4 corr = rsqrt(lengthSq);    ndotl = max (float4(0,0,0,0), ndotl * corr);    // attenuation    float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);    float4 diff = ndotl * atten;    // final color    float3 col = 0;    col += lightColor0 * diff.x;    col += lightColor1 * diff.y;    col += lightColor2 * diff.z;    col += lightColor3 * diff.w;    return col;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

可以看到,顶点光照就是用简单的Lambert光照方程。

float4 corr = rsqrt(lengthSq);ndotl = max (float4(0,0,0,0), ndotl * corr);
  • 1
  • 2
  • 1
  • 2

需要注意的是,因为法向量是归一化的,而光源方向未归一化,所以要乘以顶点离光源距离的平方根的倒数,得到光源和法线夹角的余弦。 
lightAttenSq这个值也是由unity设定的,平行光恒为1,点光源和聚光灯随着距离增大而递减。

TRANSFER_VERTEX_TO_FRAGMENT(o);
  • 1
  • 1

这个的定义也是在AutoLight.cginc中,它计算之前定义的光照和阴影坐标的值。 
点光源宏的定义如下:

#define TRANSFER_VERTEX_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)).xyz; TRANSFER_SHADOW(a)
  • 1
  • 1

实际就是把顶点坐标转换到光源空间中。

fixed atten = LIGHT_ATTENUATION(IN);
  • 1
  • 1

这个也是定义在AutoLight.cginc中,它就是把之前得到的光照和阴影坐标,采样光照图和shadow map,来得出光源的最终衰减值。 
点光源的宏定义如下:

#define LIGHT_ATTENUATION(a)    (tex2D(_LightTexture0, dot(a._LightCoord,a._LightCoord).rr).UNITY_ATTEN_CHANNEL * SHADOW_ATTENUATION(a))
  • 1
  • 1

那么这里的_LightTexture0又是什么?在哪里设置的?别急,在forwardadd章节会讲到,这里对这个宏定义的作用有个认识就可以了。

c = LightingLambert (o, _WorldSpaceLightPos0.xyz, atten);
  • 1
  • 1

unity定义的光照方程都在Lighting.cginc这个文件中,需要注意的是,因为forwarbase只支持平行光,而如果光源类型是平行光,那么_WorldSpaceLightPos0这个变量保存的直接是光源方向。所以这里直接作为第二个参数,传入光照方程。当然也可以定义自己的光照方程,具体戳这里。

ForwardAdd

Tags { "LightMode" = "ForwardAdd" }
  • 1
  • 1

同样告诉渲染管线,这个pass作为forwardadd处理。这里处理额外的像素光源。

#pragma multi_compile_fwdadd
  • 1
  • 1

这个也是unity为forwardadd pass定制的multi_compile,打开Viriants看到如下:

POINTDIRECTIONALSPOTPOINT_COOKIEDIRECTIONAL_COOKIE
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

unity会把forwardadd pass分别编译成适用于上述5种不同光源类型的版本。如果缺少这一行代码,那么unity只会编译DIRECTIONAL这一种情况。

ZWrite Off Blend One One Fog { Color (0,0,0,0) }
  • 1
  • 1

注意一下add pass有别于base pass的地方,因为之前base写过深度了,所以add就不用再次写深度了,以叠加的方式渲染到缓存,并且不受雾效影响。 
整个add pass相较于base pass显得精简很多,它只考虑实时的光照计算,没有顶点光照,SH,lightmap,阴影这一系列东西。但是,它支持点光源,聚光灯以及cookie。接下去分别讲下,不同光源的光照处理(这里不讨论阴影部分的处理)。

平行光

平行光可以说是最简单的,因为它没有衰减。在AutoLight.cginc中找到unity中对平行光光照的定义:

#ifdef DIRECTIONAL    #define LIGHTING_COORDS(idx1,idx2) SHADOW_COORDS(idx1)    #define TRANSFER_VERTEX_TO_FRAGMENT(a) TRANSFER_SHADOW(a)    #define LIGHT_ATTENUATION(a)    SHADOW_ATTENUATION(a)#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到,只有对阴影部分处理的代码,光照部分是空的。因此,不考虑阴影的话,平行光的衰减值永远是1。

点光源

在AutoLight.cginc中找到unity中对点光源光照的定义:

#ifdef POINT#define LIGHTING_COORDS(idx1,idx2) float3 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)uniform sampler2D _LightTexture0;uniform float4x4 _LightMatrix0;#define TRANSFER_VERTEX_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)).xyz; TRANSFER_SHADOW(a)#define LIGHT_ATTENUATION(a)    (tex2D(_LightTexture0, dot(a._LightCoord,a._LightCoord).rr).UNITY_ATTEN_CHANNEL * SHADOW_ATTENUATION(a))#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

_LightMatrix0是由unity设置的变换矩阵,把世界空间的顶点,变换到光源空间中。而_LightTexture0是一张由unity生成的渐变图,8位的alpha图,如下图所示: 
这里写图片描述 
[图-1 点光源的_LightTexture0贴图]

UNITY_ATTEN_CHANNEL 也就是alpha通道。最终用光照坐标来采样_LightTexture0得到光照的衰减值。

聚光灯

同样在AutoLight.cginc中找到对聚光灯的光照定义:

#ifdef SPOT#define LIGHTING_COORDS(idx1,idx2) float4 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)uniform sampler2D _LightTexture0;uniform float4x4 _LightMatrix0;uniform sampler2D _LightTextureB0;#define TRANSFER_VERTEX_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)); TRANSFER_SHADOW(a)inline fixed UnitySpotCookie(float4 LightCoord){    return tex2D(_LightTexture0, LightCoord.xy / LightCoord.w + 0.5).w;}inline fixed UnitySpotAttenuate(float3 LightCoord){    return tex2D(_LightTextureB0, dot(LightCoord, LightCoord).xx).UNITY_ATTEN_CHANNEL;}#define LIGHT_ATTENUATION(a)    ( (a._LightCoord.z > 0) * UnitySpotCookie(a._LightCoord) * UnitySpotAttenuate(a._LightCoord.xyz) * SHADOW_ATTENUATION(a) )#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可以看到,聚光灯相较于点光源,新增了一张光照贴图,用来表示聚光灯的光照范围,如下图所示: 
这里写图片描述 
[图-2 聚光灯的_LightTexture0贴图]

这里写图片描述 
[图-3 聚光灯的_LightTextureB0贴图] 
这也符合聚光灯的特性,除了距离的衰减之外,投射到平面的光照范围再通过采样[图-2]来确定,哪里应该被照亮。

总结

以上对于由unity把一个最简单的surface shader转换为vertex/fragment shader之后的代码,做了具体的分析。但也并非面面俱到。限于作者的水平,对于SH,为何用dot(LightCoord, LightCoord).xx来作为uv坐标采样[图-1]光照图,用LightCoord.xy / LightCoord.w + 0.5来采样[图-2],也不理解,如果有大牛知道,还望告知,不胜感激。

原创粉丝点击