Shader学习笔记(三):Shader中的光照

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/needmorecode/article/details/82193441

这篇文章讨论shader中用到的光照。

理解光照

Unity中的光照技术包括:

  • 实时光照(realtime lighting)
  • 烘焙后的光照贴图(baked lightmaps)
  • 预计算的实时全局光照(precomputed realtime global illumination)
实时光照

Unity最基础的光照方式,能够随光线和物体移动实时变化。但是只能处理直射光,无法处理反射光,所以只是一种局部光照。

烘焙后的光照贴图

Unity可以将静态物体的光照信息(包括直射光和反射光)预先烘焙到一张贴图(lightmap)上,供运行时使用,从而避免动态计算。

lightmap的原理是:预先计算整个光照路径(只除去物体表面到摄像机的一段)。

它的优点是省去了运行时计算的代价,缺点是运行时无法改变。

另外一种烘焙方式叫光照探针(light probe)。与lightmap的不同点在于:lightmap烘焙的是射到物体表面的光;而light probe烘焙的是空间中的传播光,可用于静态物体到动态物体的反射。

Unity将光照数据存储在名为Lighting Data Asset的文件中。它是Unity 5.3新增的文件,替代原来的Snapshot。用于存储GI数据,以及render、lightmap、light probe等的引用。

预计算的实时全局光照

预计算静态物体之间所有可能的反射并编码,在运行时再生成间接光照。运行时可以随光源位置、方向、颜色的改变而改变。Unity 5.0支持的新功能,需要设置开启。

优点是既能随光源动态改变,也能降低运行时消耗;缺点是只能针对静态物体。

各种光照技术比较

三种技术典型的应用场景包括:

  • 实时光照:移动的角色。
  • 烘焙后的光照贴图:场景中静态的物体。
  • 预计算的实时全局光照:一天之内的阳光,随时间变化而角度、强度各不相同。

在移动设备上适合用baked lightmaps,在PC上适合用precomputed realtime GI。

光源类型

名字 英文名 解释
点光源 point light 从一个点向四面八方发射,类似电灯泡
锥形光 spot light 从一个点以锥形发出,类似手电筒
有向光 directional light 以固定方向平行发出,类似太阳
区域光 area light 光线限制在一个矩形区域内,固定从一侧以任意角度发出
环境光 ambient light 光在场景中无处不在,不从特定物体发出

理解阴影

unity中的阴影分为实时阴影和阴影贴图。

  • 实时阴影:实时光产生的阴影。要使用这种阴影,需要在产生的物体上开启投射(cast),在投影到的物体上开启接收(receive)。
  • 阴影贴图:以贴图方式实现的阴影。这种方式可以降低运行时消耗,但是阴影的大小、形状都是固定的。

shader中的光照

光照是如何影响我们的视觉的呢?事实上,我们之所以能看到物体,除去那些能自发光的物体外(如灯泡),都是因为光线射到物体表面再反射到我们眼睛中。一个物体之所以呈现不同颜色,那是因为它对光的不同颜色分量的反射率不一样。例如,红色的物体对于红光几乎全部反射,而对于绿光和蓝光几乎全部吸收。所以,物体最终呈现在我们眼中的颜色,取决于反射光和自发光的叠加。

不同的光照模型对于反射光的处理大不一样。Unity中内置了两种光照模型:Lambert光照是漫反射,光线射过去向四面八方反射,适合于普通较粗糙的材质;BlingPhong光照是镜面反射,适合光滑如镜的材质。这两种光照就可以涵盖绝大多数情况。除此之外,我们也可以根据需求自己定制光照模型。

Unity中可以使用Surface Shader或者Vertex/Fragment Shader来处理光照。对于前者,需要自定义光照处理函数;对于后者,则需要在vert函数中做varying变量的赋值,而在frag函数中处理光照。对于Surface Shader和光照函数在渲染管线中的位置,可以看图(来自知乎):
这里写图片描述

可以看到,surf函数和光照都位于Fragment Shader中,两者是紧挨着的关系。

实例解析

以下均为Unity Manual中的例子。

Surface Shader中的光照
  • DiffuseTexture:与Lambert光照等效的实现。自定义光照类型SimpleLambert,对应的函数在surf之后执行。计算顶点颜色涉及到的参数:片元的法向量、光入射角度、光的rgba和衰减、片元的Albedo。

这里写图片描述

   Shader "Example/Diffuse Texture" {
        Properties {
            _MainTex ("Texture", 2D) = "white" {}
        }
        SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
          #pragma surface surf SimpleLambert

          half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten) {
              half NdotL = dot (s.Normal, lightDir);
              half4 c;
              c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
              c.a = s.Alpha;
              return c;
          }

        struct Input {
            float2 uv_MainTex;
        };

        sampler2D _MainTex;

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
        }
        Fallback "Diffuse"
    }
  • SimpleSpecular:与BlingPhong光照等效的实现。计算顶点颜色涉及到的参数:视角、片元的法向量、光入射角度、光的rgba和衰减、片元的Albedo。

这里写图片描述

    ...ShaderLab code...
    CGPROGRAM
    #pragma surface surf SimpleSpecular

    half4 LightingSimpleSpecular (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) {
        half3 h = normalize (lightDir + viewDir);

        half diff = max (0, dot (s.Normal, lightDir));

        float nh = max (0, dot (s.Normal, h));
        float spec = pow (nh, 48.0);

        half4 c;
        c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * atten;
        c.a = s.Alpha;
        return c;
    }

    struct Input {
        float2 uv_MainTex;
    };

    sampler2D _MainTex;

    void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
    }
    ENDCG
    ...ShaderLab code...
Vertex/Fragment Shader中的光照

要使用光照,首先需要在pass tag中设置LightMode,从而定义各种rendering path。常见的选择有:

关键字 意义 解释
ForwardBase forward rendering 使用关键光照信息立即渲染
Deferred deferred shading 使用所有光照信息延迟渲染
ShadowCaster shadow caster 投射阴影所必需,通常与ForwardBase或Deferred配合使用
  • SimpleDiffuse:与Lambert光照等效。需要在vert和frag中做处理。

这里写图片描述

Shader "Lit/Simple Diffuse"
{
    Properties
    {
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            // indicate that our pass is the "base" pass in forward
            // rendering pipeline. It gets ambient and main directional
            // light data set up; light direction in _WorldSpaceLightPos0
            // and color in _LightColor0
            Tags {"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc" // for UnityObjectToWorldNormal
            #include "UnityLightingCommon.cginc" // for _LightColor0

            struct v2f
            {
                float2 uv : TEXCOORD0;
                fixed4 diff : COLOR0; // diffuse lighting color
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                // get vertex normal in world space
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                // dot product between normal and light direction for
                // standard diffuse (Lambert) lighting
                half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
                // factor in the light color
                o.diff = nl * _LightColor0;
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
                // sample texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // multiply by lighting
                col *= i.diff;
                return col;
            }
            ENDCG
        }
    }
}
  • ShadowCasting:实现阴影投射。定义了两个Pass,LightMode分别是ForwardBase和ShadowCaster。

这里写图片描述

Shader "Lit/Shadow Casting"
{
    SubShader
    {
        // very simple lighting pass, that only does non-textured ambient
        Pass
        {
            Tags {"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct v2f
            {
                fixed4 diff : COLOR0;
                float4 vertex : SV_POSITION;
            };
            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                // only evaluate ambient
                o.diff.rgb = ShadeSH9(half4(worldNormal,1));
                o.diff.a = 1;
                return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                return i.diff;
            }
            ENDCG
        }

        // shadow caster rendering pass, implemented manually
        // using macros from UnityCG.cginc
        Pass
        {
            Tags {"LightMode"="ShadowCaster"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster
            #include "UnityCG.cginc"

            struct v2f { 
                V2F_SHADOW_CASTER;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
}

猜你喜欢

转载自blog.csdn.net/needmorecode/article/details/82193441