Fork me on GitHub

标准光照模型之漫反射学习

标准光照模型之漫反射

逐片元漫反射光照实现

/*
    简单的逐片元漫反射光照
*/
Shader "Volume 01/Diffuse/Simple Diffuse Per Frag" {
    Properties {
        // 材质本身的漫反射颜色 
        _Diffuse("Diffuse Color",Color) = (1, 1, 1, 1)
    }
    SubShader {

        // 设置渲染类型为不透明物体,渲染队列也按不透明物体来
        Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }

        Pass {
            // 设置渲染路径,此处使用前向渲染路径,用于让Unity底层渲染引擎填充内置光照变量
            Tags{ "LightMode" = "ForwardBase" }

            CGPROGRAM    

            // 定义顶点/片元着色器
            #pragma vertex vert
            #pragma fragment frag

            // 要使用_LightColor0(场景平行光光源颜色)变量,
            // 需要导入Lighting包
            #include "Lighting.cginc"

            // 材质漫反射颜色
            fixed4 _Diffuse;

            // 输入结构体,Application To Vertex
            struct a2v{
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            // 顶点着色器的输出结构体,Vertex To Fragment
            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 = UnityObjectToWorldDir(v.vertex).xyz;

                return o;
            }

            fixed4 frag(v2f i) : SV_TARGET{
                // 归一化法线
                fixed3 worldNormal = normalize(i.worldNormal);
                // 获得归一化光源方向(通过设置渲染路径获得)
                fixed3 worldDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                // 环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;

                // 计算漫反射光照
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldDir));

                return fixed4(ambient+diffuse,1);
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
    
}

疑难解答

1. 为什么漫反射光照结果计算时,要使用归一化的法线以及单位光源方向?

首先明确一点,光照的辐照度与cosΘ成正相关,cosΘ表示单位法线与光源方向的夹角,详情如下图。

辐照度通过计算垂直于光的方向的单位面积上单位时间穿过的能量来计算,左图是光源方向垂直于物体的情况,但大多数情况,物体不会与光源垂直,此时情况如右图。

可以明显观察到右图因为光线之间距离变大(由原来的d变为d/cosθ),光线变少,即辐照度变少。

辐照度与d/cosθ成反比,故与cosθ成正比。

当计算漫反射光照结果时,需要用到cosθ,故此时使用法线与光源方向点乘获得cosθ,计算公式如下。

2. 为什么Pass中要有Tags{ "LightMode" = "ForwardBase" }这一行?作用是什么?

这一行的作用是设置该Pass的渲染路径。

渲染路径,其决定了光照是如何应用到Unity Shader中的,Unity内置光照的变量随着设置的渲染路径不同而不同。在这个简单的diffuse shader中,其用于填充_LightColor0变量以及正确的光源方向的赋值。

如果不对渲染路径进行设置,会出现错误结果,下图总结了这个错误的结果。

3. 为什么要使用max(0,reuslt)来约束漫反射光照结果?

  1. max(0,result)用于约束cosθ的结果为正数,需要注意的是,当光源处于物体(某一点)背面时,该点法线与光源方向点乘结果为负数,在最终结果diffuse+ambient中,因为diffuse是负数,会导致物体变暗。

  1. 第二点,在《Unity Shader 入门精要》中提到:

如果不对结果约束为正数,物体会被后面来的光照照亮。

实际测试中发现。。。。。貌似并没有这种现象的产生,在这里我推测一下,有可能是Unity版本不同导致的结果。根据乐乐大神在书中描述的现象,我不负责任的推测一下,可能是由于以前的Unity版本,在光照计算中遇到的负数全部都会自动变成其对应的正数。

下图中,使用了abs函数将光照结果取正值,可以发现物体的确被背面来的光照亮了。

关于这个第二点,只是我的一个个人的小猜测。。。因为在目前查阅的资料了都描述了“会被后面来的光照亮”这一现象。。。。但是我没有遇到,所以想可能是Unity版本的原因。如果大家知道是怎么回事的话,跪求在评论区告诉楼主ORZ超级感激不尽。

参考资料

《Unity Shader入门精要》

【UnityShader从零开始】漫反射效果 http://gad.qq.com/article/detail/11725

posted @ 2018-12-26 21:23  sword_magic  阅读(675)  评论(0编辑  收藏  举报