• 标准光照模型


    在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

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

  • 相关阅读:
    nginx添加新模块
    nginx rewrite规则last与break的区别
    tomcat调优的几个方面
    Servlet 中文乱码问题及解决方案剖析
    Nginx 启动脚本/重启脚本
    Nginx的启动、停止、平滑重启
    轻量级HTTP服务器Nginx(配置与调试Nginx)
    Nginx配置文件详细说明
    提高Java代码质量的Eclipse插件之Checkstyle的使用详解
    你会用shuffle打乱列表吗?
  • 原文地址:https://www.cnblogs.com/kekec/p/15808379.html
Copyright © 2020-2023  润新知