基础光照模型

模拟正式光照环境生成图像时所需了解的相关概念

模拟真实光照环境生成图像的过程主要分为如下3个步骤

  • 光源发射光线
  • 光线与场景中物体相交,一些光线被吸收,一些光线散射到其他方向
  • 摄像机吸收一些光,产生图像

光源

辐照度:光源发射的光的数量
平行光的辐照度:垂直于光的方向的单位面积上单位时间内穿过的能量

吸收

吸收:改变密度和颜色

散射

散射:改变方向
分为2类

  • 折射:射到物体内部 漫反射
  • 反射:射到外部 高光反射

出射度:出射光线的数量和方向(根据入射光线的数量和方向计算)

着色

根据材质属性和光源信息,使用一个等式(光照模型)计算沿某观察方向的出射度

标准光照模型(Phong光照模型)

只关心直接光照
直接光照:直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光效
基本方法:把进入摄像机的光线分为4部分,各部分使用自己的方法计算贡献度

  • 自发光(emissive):给定一个方向,某表面本身会向该方向发射的辐射量
  • 高光反射(specular):光线从光源照射到模型表面时,表面会在完全镜面反射方向散射的辐射量
  • 漫反射(diffuse):光线从光源照射到模型表面时,该表面会向各方向散射的辐射量
  • 环境光(ambient):其他所有间接光照

逐像素和逐顶点

  • 片元着色器中计算光照模型:逐像素光照
    Phong着色:在面片之间对顶点发现进行插值
  • 顶点着色器中计算光效模型:逐顶点光照
    高洛德着色:在各顶点计算光照,在渲染图元内进行线性插值,输出成像素颜色

Unity中的光

环境光

在Windows/Lighting/Settings中的AmbientOcclusion中设置
在Shader中通过UNITY_LIGHTMODEL_AMBIENT得到环境光的颜色和强度

自发光

在片元着色器输出最后的颜色之前,将材质的自发光颜色添加到输出颜色上

Unity中的漫反射光照模型

漫反射值 = 光的颜色 * 材质的漫反射系数 * 表面法线与光源方向的点积
最终的颜色 = 环境光值+漫反射值

逐顶点光照

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

// 逐顶点的漫反射光照
Shader "Fan/Diffuse Vertex-Level"
{
    Properties
    {
        // 控制材质的漫反射颜色 
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        // 顶点/片元着色器的代码要写在Pass中(而非SubShader中)
        Pass
        {
            // 指定该Pass的光照模式(定义该Pass在Unity的光照流水线中的角色)
            // 定义了LightMode后,可得到Unity内置的光照变量
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // 要使用的Unity内置变量包含在如下内置文件中
            #include "Lighting.cginc"

            // 与声明的属性相匹配的变量
            fixed4 _Diffuse;

            struct a2v
            {
                float4 vertex : POSITION;
                // 将模型顶点的法线信息存储到该变量中
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 pos : SV_POSITION;
                // 将在顶点着色器中算得的光照颜色存储在该变量中,传递给片元着色器
                float3 color : COLOR;
            };

            v2f vert(a2v v)
            {
                v2f o;
                // 将顶点位置从模型空间转换到裁剪空间
                o.pos = UnityObjectToClipPos (v.vertex);
                // 环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                // 将顶点法线转到世界中间中(原来在模型空间中)  归一化处理
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3) unity_WorldToObject));
                // 将光源方向归一化
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                // 计算漫反射光照结果                              saturate:将参数截取到范围0~1内
                // _LightColor0是内置变量,表示该Pass处理的光源的颜色和强度信息(要定义合适的LightMode标签)
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

                o.color = ambient + diffuse;
                
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(i.color, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

逐像素光照

更平滑,但是在光照无法到达的区域,模型的外观全黑且无明暗变化
顶点着色器将世界空间下的法线传给片元着色器,在片元着色器中计算漫反射光照模型

半兰伯特模型

                fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;

改善模型背光面明暗一样的问题
对于模型的背光面,兰伯特光照模型的点积结果都会映射到0,而在半兰伯特模型中背光面有明暗变化(不同的点积结果映射到不同值)
该模型无物理依据,仅是视觉加强技术
在片元着色器计算漫反射光照的部分中,用半兰伯特模型代替兰伯特模型

Unity中的高光反射光照模型

高光反射值 = 光的颜色 * 材质的高光反射系数 * 视角方向与反射方向的点积的高光参数次方
高光参数影响高光区域的大小
通过reflect(i,n),根据入射方向和法线方向计算反射方向

逐顶点光照

逐像素光照

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

// 逐顶点的高光反射光照
Shader "Fan/Specular Pixel-Level"
{
    Properties
    {
        // 控制材质的漫反射颜色 
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular ("Specular", Color) = (1, 1, 1, 1)
        // 控制高光区域大小, 成反比
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }
    SubShader
    {
        // 顶点/片元着色器的代码要写在Pass中(而非SubShader中)
        Pass
        {
            // 指定该Pass的光照模式(定义该Pass在Unity的光照流水线中的角色)
            // 定义了LightMode后,可得到Unity内置的光照变量
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // 要使用的Unity内置变量包含在如下内置文件中
            #include "Lighting.cginc"

            // 与声明的属性相匹配的变量
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex : POSITION;
                // 将模型顶点的法线信息存储到该变量中
                float3 normal : NORMAL;
            };
            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 = normalize(mul(v.normal, (float3x3) unity_WorldToObject));
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                // 环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                
                // 漫反射
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
                
                // 高光
                // 入射光线方向关于表面法线的反射方向
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                // 视角方向:将顶点位置从模型空间变换到世界空间,世界中间中摄像机的位置减去顶点位置
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);


                return fixed4(ambient + diffuse + specular, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Specular"
}

顶点着色器计算世界空间下的法线方向和顶点坐标
将高光相关计算移动到片元着色器中,得到的结果更平滑

Blinn-Phong光照模型

// 高光
                // 入射光线方向关于表面法线的反射方向
                // fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                // 视角方向:将顶点位置从模型空间变换到世界空间,世界中间中摄像机的位置减去顶点位置
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                // 用于替代发射方向的向量
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                // fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);

引入新的向量代替反射方向
点积:法线方向与新向量的点积
新的向量:对视角方向和光照方向相加后的向量归一化
计算简单:仅需一次向量乘法
结果:高光反射部分更大更亮
这是经验模型

上述几种光照模型的实际效果

各种光照模型的效果.png

Unity提供的在计算光照时可用的内置函数

Unity提供了常用计算函数

输入各空间中的顶点位置

  • 输出对应空间中该点到摄像机的方向
  • 输出世界空间中从该点到光源的光照方向(只能用于前向渲染(Pass的渲染标签中设置),因为只有在前向渲染中光的位置(内置变量_WorldSpaceLightPos0)才能被正确赋值)

将方向向量转到对应空间

未归一化

内置函数输出的方向没有归一化(需开发者主动使用normalize进行归一化)

你可能感兴趣的:(基础光照模型)