Unity-shader学习笔记(七)

Unity-shader学习笔记(七)

15 更复杂的光照

又是光照,只是比之前更复杂了,体现在哪儿呢?需要处理的光源数目更多、类型也更复杂,例如:如何处理光源和聚光灯,如何处理光照的衰减等等,这些都是接下来会聊到的。

15.1 Unity的渲染路径

渲染路径决定了光照是如何应用到UnityShader中的,所以当我们要使用光源时,我们要为每个Pass指定它使用的渲染光源。

渲染路径可以在Camera的Rendering Pass中进行选择,一共有四种(笔者使用的是2018.4.17f1):

Forward、Deferred、Legacy Vertex Lit、Legacy Deferred(Light prepass)。

①Deferred:延迟渲染,具有最高照明、阴影保真度的渲染路径,如果场景中使用了许多的实时灯光,这是最适合的,但需要一定的硬件支持;

②Forward:前向渲染(或者正向渲染)。最传统的渲染路径,支持所有典型的Unity图形功能;

③Legacy Vertex Lit:正向渲染路径的一个子集,具有最低照明保真度的渲染路径,并且不支持实时阴影;

④Legacy Deferred(Light prepass):传统延迟(轻量级预备),类似于延迟渲染,只是使用的技术与权衡皆不同

下面我们主要介绍前两种。

15.1.1 前向渲染路径

我们将聊聊前向渲染路径的原理以及在Unity中的实现和注意事项。

15.1.1.1 前向渲染路径的原理

每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算颜色缓冲区和深度缓冲区的信息。我们利用深度缓冲区来决定一个片元是否可见,如果可见,就更新颜色缓冲区中的颜色值。

Pass{
    for (each primitive in this model){
        for (each fragment covered by this primitive){
            if (failed in depth test){
                discard;
            }else{
                float4 color = Shading (materialInfo, pos, normal, lightDir, viewDir);
                writeFrameBuffer (fragment, color);
            }
        } 
    }
}

对于每个像素光源,我们都要进行上面一次完整的渲染流程,同时对每个Pass也要计算一个逐像素光源的光照,假设场景中有N个物体,每个物体受M个光源的影响,那么渲染整个场景一共需要N*M个Pass,如果有大量的逐像素光照,那么需要执行的Pass数目也会很大,因此渲染引擎通常会限制每个物体的逐像素光照的数目。

15.1.1.2 Unity中的前向渲染

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

在Unity中,前向渲染路径有三种处理光照的方式:逐顶点处理、逐像素处理、球谐函数处理(SH处理)。决定一个光源的的处理模式取决于它的类型和渲染模式。

光源类型有平行光源和其它类型的;渲染模式是指该光源是否是重要的(Important)。如果我们将一个光源的模式设置为Important,意味着提高它的优先级并按照逐像素光源来处理。

Unity是怎么对光源的重要度进行排序的呢?

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

②渲染模式被设置成Not Important的光源会按逐顶点或者SH处理,其中最多只有四个光源按逐顶点的方式处理;

③渲染模式被设置成Important的光源会按照逐像素处理;

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

在哪儿进行计算呢?Pass中。在前向渲染中有两种Pass,一种是Base Pass,另一种是Additional Pass

在Base Pass中:

Tags {"LightMode" = "ForwardBase"}
#pragma multi_compile_fwdbase
    ......//一个逐像素的平行光以及多有逐顶点和SH光源

可实现的光照效果有:

光照纹理、环境光、自发光、平行光阴影。

在Additional Pass中:

Tags {"LightMode" = "AdditionalBase"}
Blend One One
#pragma multi_compile_fwdadd
    ......//其他影响该物体的逐像素光源,每个光源执行一次Pass

可实现的光照效果有:

默认情况下是不支持阴影的,但可以通过语句:#pragma multi_compile_fwdadd_fullshadow来开启阴影。

15.1.1.3 内置的光照变量和函数

根据物品们使用的渲染路径(LightMode的取值),Unity会将不同的光照变量传递给Shader。

前向渲染的的光照变量有:

_LightColor0 float4 该Pass处理的逐像素光源的颜色
_WorldSpaceLightPos0 float4 _WorldSpaceLightPos0.xyz是该Pass处理的逐像素光源的位置,至于其w分量,若是平行光则为0,其他光则为1
_LightMatrix0 float4 从世界空间到光源空间的变换矩阵,可用于采样cookie和光强衰减纹理
unity_ 4LightPosX0/Y0/Z0 float4 仅用于Base Pass。前4个重要的点光源在世界空间中的位置
unity_ 4LightAtten0 float4 仅用于Base Pass。存储了前4个非重要的点光源的衰减因子
unity_ LightColor half4 仅用于Base Pass。存储了前4个非重要的点光源的颜色

前向渲染的的光照函数有:

float3 WorldSpaceLightDir (float4 v) 仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回世界空间中 从该点到光源的光照方向。内部实现使用了UnityWorldSpaceLightDir函数。没有被归一化
float3 UnityWorldSpaceLightDir (float4 v) 仅可用于前向渲染中。输入一个世界空间中的顶点位置,返回世界空间中 从该点到光源的光照方向。没有被归一化
float3 ObjSpaceLightDir (float4 v) 仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中 从该点到光源的光照方向。没有被归一化
float3 Shade4PointLights (…) 仅可用于前向渲染中。计算四个点光源的光照,它的参数是已经打包进矢量的光照数据,通常就是上表中的内置变量,如unity_ 4LightPosX0、unity_ 4LightPosY0、unity_ 4LightPosZ0、unity_ LightColor和unity_ 4LightAtten0等。前向渲染通常会使用这个函数来计算逐顶点光照
15.1.2 延迟渲染路径

为什么要有延迟渲染?因为当场景中包含大量的实时光源时,前向渲染的性能会急速下降。具体体现在:

当我们在一块区域放置了多个光源时,这些光源影响的区域互相叠加,为了得到最终的光照效果,就需要对该区域内的每个物体执行多个Pass来计算不同光源对该物体的光照结果,然后再颜色缓冲区中进行混合得到最终的光照,这就会导致没执行一个Pass都要渲染一遍物体,重复计算。

在延迟渲染中,除了会使用到前向渲染中使用的颜色缓冲和深度缓冲外,还会利用额外的缓冲区,我们称之为G缓冲,它存储了我们所关心的表面法线、位置、材质属性等信息。

15.1.2.1 延迟渲染路径的原理

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

Pass 1{
    for (each primitive in this model){
        for (each fragment covered bu this primitive){
            if (failed in depth test){
                discard;
            }else{
           		writeGBuffer (materialInfo, pos, normal, lightDir, viewDir);
        	}
        }
    }
}
//利用G-Buffer中的信息进行真正的光照计算
Pass 2{
    for (each pixel in the screen){
        if (the pixel is valid){
            readGBuffer(pixel, materialInfo, pos, normal, lightDir, viewDir);
            float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
            writeFrameBuffer(pixel, color);
        }
    }
}

延迟渲染的效率不依赖场景的复杂度,而是和我们使用的屏幕空间的大小有关,因为,我们需要的信息都存储在缓冲区中,可以将其理解为一张张2D图像,我们的计算实际上就是在这些图像空间中进行的。

15.1.2.2 Unity中的延迟渲染

第一个Pass用于渲染G缓冲。在这个Pass中,我们会将物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中。对于每个物体而言,这个Pass仅执行一次。

第二个Pass用于计算真正的光照模型。使用的是上一个Pass中渲染的数据来计算最终的光照颜色,在存储到帧缓冲中。

默认的G缓冲区包含了以下几个渲染纹理(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-DefferredShading.shader文件。

15.1.2.3 内置变量和函数

这些变量的使用需要头文件UnityDeferredLibrary.cginc的支持

_LightColor float4 光源颜色
_LightMatrix0 float4*4 从世界空间到光源空间的变换矩阵,可用于采样cookie和光强衰减纹理

15.2 Unity的光源类型

Unity一共支持四种光源类型:平行光、点光源、聚光灯、面光源(仅仅在烘培时才会发挥作用)。

其中面光源我们这里不进行描述。

15.2.1 不同的光源会产生怎样的影响

我们将从光源的位置、方向、颜色、强度和衰减这5个属性来聊。

15.2.1.1 平行光

前面用得太多了,就不重复了。

15.2.1.2 点光源

顾名思义,所有光都来自于一个点,它所照亮的空间是有限的,由空间的一个球体所定义。

在Scene中还需要开启光照才能看到点光源是如何影响场景中的物体的。

球体的半径可以由属性面板中的Range来调整,或者直接在Scene视图中进行拖拉放缩;点光源的方向是由其位置减去某点的位置来得到;点光源的颜色和强度可以在Light组件面板中调整。同时,点光源也是会衰减的,随着物体逐渐远离点光源,它接受到的光照强度也会逐渐较小,点光源球心处的光照强度最强,球体边界处的最弱,值为0.其中间的衰减值可以由一个函数来定义。

15.2.1.3 聚光灯

聚光灯是最复杂的一种,它照亮的空间同样有限,但不再是简单的球体,而是由空间中的一块锥形区域定义。可以表示为从一特定位置出发、向特定方向延伸的光。只有一点属性与其他的不同,就是从光源中心向光源边界的光源衰减计算公式更加的复杂,因为要判断一个点是否在锥体中。

15.2.2 在前向渲染中处理不同的光源

在使用前向渲染的基础上,在Unity Shader中访问光源的5个属性:位置、方向、颜色、强度以及光照衰减。

①定义第一个Pass——Base Pass

Pass{
    Tags {"LightMode" = "ForwardBase"}
    
    CGROGRAM
        
    #pragma multi_compile_fwdbase//确保我们在Shader中使用光照衰减等光照变量可以被正确赋值。
}

②在Base Pass的片元着色器中,首先计算场景中的环境光

fixed3 ambient = UNITY_LIGHTMODE_AMBIENT.xyz;

与之相同的还有物体的自发光,都只需要计算一次就好了。

③然后我们在Base Pass中计算场景最重要的平行光。当场景中含有多个平行光时,Unity会选择最亮的平行光传递给Base Pass进行逐像素处理,其他平行光会按照逐顶点或在Additional Pass中按逐像素的方式处理。如果场景中没有任何的光,那么Base Pass会当成全黑的光源处理。对于Base Pass而言,它处理的逐像素光源类型一定是平行光,可以使用_WorldSpaceLightPos0来得到这个平行光方向(位置对于平行光而言是没有任何意义的),使用 _LightColor0来得到它的颜色和强度( _LightColor0已经是颜色和强度相乘后的结果),由于是平行光,所以我们认为是没有衰减的,因此我们直接令衰减值为1.0,代码为:

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

......;

fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(dot(worldNormal, halfDir), _Gloss);

......;

fixed atren = 1.0;

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

④为场景中其他逐像素光源定义Additional Pass

Pass{
    Tags {"LightMode" = "ForwardAdd"}
    
    Blend One One
        
    #pragma multi_compile_fwadd
}

除之前所说的那些内容外,我们还使用Blend命令开启和设置了混合模式,这是因为我们希望Additional Pass计算得到的光照结果可以在帧缓存中与之前的光照结果进行叠加。

⑤通常来说,Additional Pass的光照处理与Base Pass的处理方式是一样的,略微的区别在于:Additional Pass中是没有Base Pass中环境光、自发光、逐顶点光照、SH光照的部分,并添加一些对不同光源类型的支持。我们通过使用#ifdef来进行不同光源类型的计算:

#ifdef USING_DIRECTIONAL_LIGHT
    fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
    fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
#endif

⑥最后处理不同光源的衰减

#ifdef USING_DIRECTIONAL_LIGHT
    fixed3 atten = 1.0;
#else
    float3 lightCoord = mul(_LightMatrix0, float4(i.worldPosition, 1)).xyz;
    fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif

15.3 Unity光照的衰减

在15.2.2中的第六步,你可能会奇怪为什么衰减会用tex2D函数来表示。

如果是平行光,我们当然可以定量的将其设定为1.0,如果是其他的光源类型,那么处理起来会更加的复杂,尽管可以使用诸如开方、出发等数学表达式来计算给定点相对于点光源和聚光灯的衰减,但计算量相对较大,因此Unity选择了使用一张纹理作为查找表(LUT),一再片元着色器中得到光源的衰减。我们首先得到在光源空间下的坐标,然后使用该坐标对衰减纹理进行采样得到衰减值。

这样做可以在一定程度上提升性能,而且得到的效果在大部分情况下都是良好的。

但这样做也是有弊端的:

①需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度;

②不直观,同时也不方便,因为将衰减值存储到查找表中,我们就无法使用其他数学公式来计算衰减值了。

15.3.1 用于光照衰减的纹理

Unity在内部使用了一张名为_LightTexture0的纹理来计算光源衰减为了对 _LightTexture0纹理采样得到给定点到该光源的衰减值,我们首先需要得到该点在光源空间中的位置,这是通过 _LightMatrix0变换矩阵得到的。将这个矩阵与世界空间中的顶点坐标相乘,再将乘后的坐标的模的平方对衰减纹理进行采样,得到衰减值:

float3 lightCoord = mul(_LightMatrix0, float4(i.worldPosition, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
15.3.2 使用数学公式计算衰减

由于基于纹理采样的光照衰减存在种种不利,所以有时候我们还是要使用数学公式来计算光照衰减。例如线性衰减的代码为:


                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值