• 第八章 更复杂的光照(5)


    @

    让物体接收阴影

    为了让阴影出现在正方体上,我们对代码做一些修改。
    (1)首先,我们在Base Pass中包含进一个新的内置文件

    #include "AutoLight.cginc"
    

    这是因为,我们下面计算阴影时所用的宏都是在这个文件中声明的。
    (2)首先,我们在顶点着色器的输出结构体v2f中添加一个内置宏SHADOW_COORDS;

    struct v2f{
    float4 pos:SV_POSITION;
    float3 worldNormal:TEXCOORD0;
    float3 worldPos:TEXCOORD1;
    SHADOW_COORDS(2);
    };
    

    这个宏的作用很简单,就是声明一个用于对阴影纹理采样的坐标。需要注意的是,这个宏的参数需要是下一个可用的插值寄存器的索引值,在上面的例子中就是2。
    (3)然后,我们在顶点着色器返回之前添加另一个内置宏TRANSFER_SHADOW:
    v2f vert(a2v v){
    v2f o;
    ...
    //Pass shadow coordinates to pixel shader
    TRANSFER_SHADOW(o);
    return o;
    };
    这个宏用于在顶点着色器中计算上一步中声明的阴影纹理坐标。
    (4)接着,我们在片元着色器中计算阴影值,这同样使用了一个内置宏SHADOW_ATTENUATION:

    //Use shadow coordinates to sample shadow map
    fixed shadow=SHADOW_ATTENUATION(i);
    

    SHADOW_COORDS、TRANSFER_SHADOW和SHADOW_ATTENUATION是计算阴影时的“三剑客”。我们可以在AutoLight.cginc中找到它们的声明:

    // ----------
    //Shadow helpers
    //--------
    //------Screen space shadows
    #if defined(SHADOWS_SCREEN)
    UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
    #define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord:TEXCOORD##idx1;
    #if defined (UNITY_NO_SCREENSPACE_SHADOWS)
    #define TRANSFER_SHADOW_SHADOW(a)a._ShadowCoord=mul(unity_World2Shadow[0],mul(_Object2World,v.vertex));
    inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
    {
    ...
    }
    #else//UNITY_NO_SCREENSPACE_SHADOWS
    #define TRANSFER_SHADOW(a) a._ShadowCoord=ComputeScreenPos(a.pos);
    inline fixed unitySampleShadow(unityShadowCoord4 ShadowCoord)
    {
    fixed shadow=tex2DProj(_ShadowMapTexture,UNITY_PROJ_COORD(shadowCoord)).r;
    return shadow;
    }
    #endif
    #define SHADOW_ATTENUATION(a)unitySampleShadow(a._ShadowCoord)
    #endif
    //---Spot light shadows
    #if defined (SHADOWS_DEPTH)&&defined(SPOT)
    ...
    #endif
    //---Point light shadows
    #if defined(SHADOWS_CUBE)
    ...
    #endif
    //---Shadows off
    #if !defined(SHADOWS_SCREEN)&&!defined(SHADOWS_DEPTH)&&!defined(SHADOWS_CUBE)
    #define SHADOW_COORDS(idx1)
    #define TRANSFER_SHADOW(a)
    #define SHADOW_ATTENUATION(a)1.0
    #endif
    

    上面的代码看起来很多很复杂,实际上只是Unity为了处理不同的光源类型、不同平台而定义了多个版本的宏。在前向渲染中,宏SHADOW_COORDS实际上就是声明了一个名为_ShadowCoord的阴影纹理坐标变量。而TRANSFER_SHADOW的实现会根据平台不同而有所差异。如果当前平台可以使用屏幕空间的阴影映射技术(通过判断是否定义了UNITY_NO_SCREENSPACE_SHADOWS来得到),TRANSFER_SHADOW会调用内置的ComputePos函数来计算_ShadowCoord;如果该平台不支持屏幕空间的阴影映射技术,就会使用传统的阴影映射技术,TRANSFER_SHADOW会把顶点坐标从模型空间变换到光源空间后存储到_ShadowCoord中。然后SHADOW_ATTENUATION负责使用_ShadowCoord对相关纹理进行采样,得到阴影信息。
    注意到,上面的内置代码的最后定义了在关闭阴影时的处理代码,可以看出,当关闭阴影后,SHADOW_COORDS和TRANSFER_SHADOW实际没有任何作用,而SHADOW_ATTENUATION会直接等于数值1。
    需要读者注意的是,由于这些宏会使用上下文变量来进行相关计算,例如TRANSFER_SHADOW会使用v.vertex或a.pos来计算坐标,因此为了能够让这些宏正确工作,我们需要保证自定义的变量名和这些宏中使用的变量名相匹配。我们需要保证:a2f结构体中顶点坐标变量名必须是vertex,顶点着色器的输出结构体v2f必须命名为v,且v2f中的顶点位置变量必须命名为pos。
    (5)在完成了上面的所有操作后,我们只需要把阴影值shadow和漫反射和漫反射以及高光颜色相乘即可。得到的结果如下图所示。
    在这里插入图片描述
    需要注意的是,在上面的代码里我们只更改了Base Pass中的代码,使其可以得到阴影效果,而没有对Additional Pass做任何更改。大体上,Additional Pass的阴影处理和Base Pass是一样的。我们将在后面看到如何处理这些阴影。本节实现的代码仅是为了解释如何让物体接收阴影,但不可以直接应用到项目中。我们会在后面给出包含了完整的光照处理的Unity Shader。

    使用帧调试器查看阴影绘制过程

    尽管我们在上面描述了阴影的产生过程,但如果有直观的方式看到阴影一步步的绘制过程那就太好了。幸运的是,Unity5添加了新的调试工具——帧调试器。我们曾在前面利用它查看过Pass的绘制过程,本节我们会通过它查看阴影的绘制过程。
    首先,我们要在Window->Frame Debugger中打开帧调试器。下图给出了在帧调试器中的分析结果
    在这里插入图片描述
    在上图中可以看出,绘制该场景共需要花费20个渲染事件。这些渲染事件可以分为4个部分:UpdateDepthTexture,即更新摄像机的深度纹理;RenderShadowmap,即渲染的得到平行光的阴影映射纹理;CollectShadows,即根据深度纹理和阴影映射纹理得到屏幕空间的阴影图;最后绘制渲染结果。
    我们首先来看第一个部分:更新摄像机的深度纹理,这是前4个渲染事件的工作。我们可以单击这些事件查看它们的绘制结果。下图给出了正方体对深度纹理的更新结果。
    在这里插入图片描述
    从调试器右侧的面板我们可以了解这一渲染事件的详细信息。从上图我们可以发现,Unity调用了Shader来更新深度纹理,即上一节中的第三个Pass。尽管上一节只定义了两个Pass,但正如我们之前所说,Unity会在它的Fallback中找到第三个Pass,即LightMode为ShadowCaster的Pass来更新摄像机的深度纹理。同样,在第二个部分,即渲染得到平行光的阴影映射纹理的过程中,Unity也是调用了这个Pass来得到光源的阴影映射纹理。
    在第三个部分中,Unity会根据之前两部的结果得到屏幕空间的阴影图,如下图所示:
    在这里插入图片描述
    这张图已经包含了最终屏幕上所有阴影区域的阴影。在最后一个部分中,如果物体所使用的的Shader包含了对这张阴影图的采样就会得到阴影效果。下图给出了这个部分Unity是如何一步步绘制出有阴影的画面效果的。
    在这里插入图片描述

  • 相关阅读:
    【转】 矩阵构造方法
    CODEVS1187 Xor最大路径 (Trie树)
    POJ2001 Shortest Prefixes (Trie树)
    CODEVS1079 回家 (最短路)
    CODEVS2144 砝码称重2 (哈希表)
    CODEVS1380 没有上司的舞会 (树形DP)
    JAVA 多态和异常处理作业——动手动脑以及课后实验性问题
    再读大道之简第七章第八章
    JAVA 接口与继承作业——动手动脑以及课后实验性问题
    再读大道至简第六章
  • 原文地址:https://www.cnblogs.com/xiegaosen/p/12004790.html
Copyright © 2020-2023  润新知