可可西

标准光照模型

在1975年,著名越南籍学者裴祥风(Bui Tuong Phone)提出了标准光照模型。

它把进入到摄像机内的光线分为4个部分:环境光(ambient)漫反射(diffuse)高光反射(或称镜面反射,specular)自发光(emissive)

每个部分使用一种方法来计算它的贡献度,然后线性叠加得到最终的光照颜色。

c = cambient + cdiffuse + cspecular + cemissive

 

Phong着色法(Phong Shading)

基于标准光照模型理论,裴祥风(Bui Tuong Phone)给出了Phong着色法Phong Shading)的光照计算的完整实现。

Phong着色法基于这样的观察:

(1)一个物体表面越粗糙,其对光线的反射就越分散,而这部分反射的光构成了物体本身的基础颜色,这部分颜色用漫反射分量表示
(2)一个物体表面越光滑,其对光线的反射就越集中,就越会在某些位置上呈现比较集中明亮的高光,这部分颜色用高光反射分量表示
(3)如果场景中有光源,那么即便一个物体没有直接被光源照亮,我们也还是看到这个物体。事实上,这部分表面接收到了来自四面八方的间接光照,这部分颜色用环境光分量表示

 

Phong着色法虽然是一个经验模型,但它也符合一些基本的物理规律,可以很好地模拟相当广泛的视觉场景。

 

环境光(ambient)

环境光用来近似模拟间接光照(indrect light),即在多个物体之间反射后,进入摄像机的光线。环境光的计算非常简单,它通常是一个全局变量,即场景中所有物体都使用这个环境光。

cambient = gambient

 

自发光emissive)

自发光描述物体表面不经过任何物体的反射,直接进入摄像机的光线。它的计算也很简单,直接使用该材质的自发光颜色,这些自发光的表面并不会真的照亮周围的物体,仅仅是它本身看起来更亮而已。

cemissive = memissive

 

对于漫反射(diffuse)和高光反射(或称镜面反射,specular)的部分,则只关心直接光照(direct light),即那些直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。示意图如下:

L为入射光线,N为表面法线,fd为漫反射光,fs为高光反射光(或镜面反射光)

 

漫反射(diffuse)

漫反射是被物体表面随机散射到各个方向的光线,可以认为在任何反射方向上的分布都是一样的。因此,无论观察者从哪个方向观察,漫反射效果是一样的,所以我们认为漫反射和观察位置是无关的。

但是,入射光线的角度很重要。漫反射的大小取决于表面法线N和光线L的夹角。光线越水平,夹角越大,漫反射分量越小;当夹角接近90度时,漫反射几乎为0。

漫反射光照符合兰伯特定律(Lambert's law):反射光线的强度与表面法线和光源方向之间的夹角的余弦值成正比,即表面法线和光源方向的点乘。

cdiffuse = clight * mdiffuse * max{0, dot(L, N)}

注:N是归一化的表面法线,L是归一化的光源入射方向,mdiffuse是材质的漫反射颜色,clight是光源颜色。

需要注意的是,我们需要防止dot(N, L)的结果为负值,为此,使用取最大值max函数将其截取到0,这可以防止物体被从后面来的光源照亮。

 

这里有一个问题:在光照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面一样,失去了模型细节表现。

虽然我们可以通过添加环境光(ambient)来避免全黑,但仍然无法解决背光面没有明暗变化的问题。为此,有一种改善技术被提出来,即:半兰伯特(Half Lambert)光照模型。

 

半兰伯特(Half Lambert)光照模型是Valve公司在开发游戏《半条命》时提出的一种技术,该技术是在原兰伯特光照模型基础上进行了一个简单的修改。

广义的半兰伯特光照模型的公式如下:

cdiffuse = clight * mdiffuse * (α * dot(L, N) + β)

注:半兰伯特没有使用max函数来防止dot(N, L)的结果为负值。而是对其结果进行了一个α倍的缩放再加上一个β大小的偏移。

绝大多数情况下,αβ会取值0.5,即公式为:

cdiffuse = clight * mdiffuse * (0.5 * dot(L, N) + 0.5)

通过这样的方式,我们把dot(N, L)的结果范围从[-1,1]映射到[0,1]范围内。也就说,对于模型的背光面,在原兰伯特都映射到同一个值0,而在半兰伯特中,背光面映射到了不同的值上,有了明暗的变化。

需要注意的是,半兰伯特是没有物理依据的,仅仅是一个视觉加强技术。

 

高光反射(或称镜面反射,specular)

高光是那些沿着完全镜面反射方向被反射的光线,这可以让物体看起来是由光泽的。高光反射与观察方向是有关系的,在描述其性质时,需要知道观察者位置信息。

现实生活中我们也会注意到,当看到一个物体表面反射了刺眼的光线的时候,只要我们稍稍错开一点位置,就不会再感到刺眼了。

计算高光反射需要知道的信息比较多,如:表面法线、视角方向、光源方向等。反射方向可以通过表面法线和视角方向计算得到,如下。

L + R =  2 * dot(N, L) * N,则:R = 2 * dot(L, N) * N - L   注:L、R、N都是归一化后的单位向量

 

使用Phong模型计算高光反射的部分

cspecular = clight * mspecular * max{0, dot(V, R)}mgloss

注1:clight是光源的颜色和强度。mspecular则是材质的高光反射颜色,用于控制该材质对于高光反射的强度和颜色。

         mgloss是材质的光泽度(gloss),也被称为反光度(shininess),它用于控制高光区域的“亮点”有多宽, mgloss越大,亮点就越小。

注2:dot(V, R)也需要防止为负数。

 

计算结果如下图红框所示:

 

这个公式意味着,反射方向R和观察方向V的夹角一旦超过90度,高光就变成0。

当mgloss较大的时候不会产生太大的影响,因为此时高光衰减很快,还不到90度就已经衰减完了,但是如果mgloss很小,那么高光范围就会很大,我们很容易观察到这个断层。 

 

Phong Shading在unity中的实现如下(Chapter6-SpecularPixelLevel.shader):

Shader "Unity Shaders Book/Chapter 6/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 { 
            Tags { "LightMode"="ForwardBase" }
        
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag

            #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;
                // Transform the vertex from object space to projection space
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                
                // Transform the normal from object space to world space
                o.worldNormal = mul(v.normal, (float3x3)_World2Object);
                // Transform the vertex from object spacet to world space
                o.worldPos = mul(_Object2World, v.vertex).xyz;
                
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                // Get ambient term
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光
                
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                
                // Compute diffuse term
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir)); // 兰伯特漫反射光
                
                // Get the reflect direction in world space
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                // Get the view direction in world space
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                // Compute specular term
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss); // Phong高光反射光
                
                return fixed4(ambient + diffuse + specular, 1.0);
            }
            
            ENDCG
        }
    } 
    FallBack "Specular"
}

 

Blinn-Phong着色法(Blinn-Phong Shading)

需要强调的是,Blinn-Phong着色法在环境光(ambient)、漫反射(diffuse)和自发光(emissive)部分的计算方式和Phong着色法完全一致。

Blinn针对上面Phong高光反射问题提出了Blinn-Phong反射模型,并很好地解决了高光断层问题。

Blinn-Phong模型在大部分情况下看起来会更自然一点,特别是低高光的区域。也是早期固定渲染管线时代时OpenGL所采用的光照模型。

Phong和Blinn都是经验模型,我们不应该认为Blinn模型是对“正确的”Phong模型的近似。

 

使用Blinn-Phong模型计算高光反射的部分

Blinn-Phong的基本思想是,避免计算反射方向R。为此,Blinn模型引入了一个新的向量H(半程向量,Halfway Vector),它通过对V和L取平均后再归一化得到:

H = (V + L) / ‖ V + L ‖    注:‖ V + L ‖为对V + L向量和取模,即求V + L向量和的长度

然后,使用N和H之间的夹角进行计算,而非V和R之间的夹角。  注:只要在平面的同一侧,N和H之间的夹角就不会超过90度,从而规避掉了Phong反射模型的问题

cspecular = clight * mspecular * max{0, dot(N, H)}mgloss

 

Blinn-Phong与Phong的结果对比:

 

Blinn-Phong Shading在unity中的实现如下(Chapter6-BlinnPhong.shader):

Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
    Properties {
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular ("Specular", Color) = (1, 1, 1, 1)
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }
    SubShader {
        Pass { 
            Tags { "LightMode"="ForwardBase" }
        
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #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;
                // Transform the vertex from object space to projection space
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                
                // Transform the normal from object space to world space
                o.worldNormal = mul(v.normal, (float3x3)_World2Object);
                
                // Transform the vertex from object spacet to world space
                o.worldPos = mul(_Object2World, v.vertex).xyz;
                
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                // Get ambient term
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光
                
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                
                // Compute diffuse term
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); // 兰伯特漫反射光
                
                // Get the view direction in world space
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                // Get the half direction in world space
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                // Compute specular term
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); // Blinn-Phong高光反射光
                
                return fixed4(ambient + diffuse + specular, 1.0);
            }
            
            ENDCG
        }
    } 
    FallBack "Specular"  // 上述SubShader都失败后,回调内置名为Specular的Unity Shader
}

 

unity实现效果对比:

 

参考

LearnOpenGL(Base Lighting)

LearnOpenGL(基础光照)

LearnOpenGL(Advanced Lighting)

LearnOpenGL(高级光照)

List of common shading algorithms(wikipedia)

Unity_Shaders_Book(candycat1992)

《Unity Shader入门精要》随书彩色插图

WebGL Example: Phong / Blinn Phong Shading

图形学入门(三):基础着色

Bump map Blinn-Phong(ShaderFrog)

【玉兔|图形学与游戏开发】关于游戏中的光照|入门必备的基础光照知识

posted on 2022-01-20 00:18  可可西  阅读(823)  评论(1编辑  收藏  举报

导航