OpenGL 光照基础

颜色与光照的关系

  • 我们看到的物体的颜色,实际是光照射物体后发射的光进入眼睛后感受到的颜色,而不是物体实际材料的颜色。
  • 光照射到物体上,一部分会被物体吸收,一部分被发射进入眼睛,我们看到的颜色就是反射后进入我们眼睛的颜色。
  • 颜色吸收和反射的过程可以表示为:LightIntensity * ObjectColor = ReflectColor.
  • 计算为:(R, G, B) * (X, Y, Z) = (XR, YG, ZB)

Phong Reflection Model

  • Phong Reflection Model是经典的光照模型,它计算光照为:环境光 + 漫反射光 + 镜面光

环境光

  • 环境光是场景中光源给定或者全局给定的一个光照常量。
  • 它一般很小,主要是为了模拟计时场景中没有光照时,也不是全部黑屏的效果。
  • 环境光 = 光源的环境光颜色 * 物体的环境光材质颜色
```
varying vec3 objectColor;
void main() {
    // 环境光成分,至少有10%的光照到物体的所有面
    float = ambientStrength = 0.1f;
    // 环境光颜色
    vec3 ambient = ambientStrength * lightColor * objectColor;
    gl_FragColor = vec4(ambient, 1.0); 
}
```

漫反射光

  • 漫反射光强度与光线入射方向和物体表面的法向量之间的夹角θ相关。


    漫反射.png
  • 需要计算的两个向量为:

    • 光源和顶点位置之间的向量L.
    • 法向量N 可以通过顶点属性指定,但顶点经过模型视图变换后需要重新计算。
  • 在世界坐标系中,计算L时,光源lightPos是在世界坐标系中指定的位置,直接使用即可。

    • 顶点位置需要变换到世界坐标系中,利用模型(Model)矩阵进行变换:FragPos = vec3(model * vec4(position, 1.0)).

    • 计算N时,不能直接使用Model * normal来换取变换后的法向量,需要使用:Normal = mat3(transpose(inverse(model))) * normal.

    • 其中inverse()为求矩阵的逆,transpose()求矩阵的转置。

  • 漫反射颜色 = 光源的漫反射颜色 * 物体的漫反射材质颜色 * DiffuseFactor,其中 DiffuseFactor = max(0, dot(N,L))

    顶点着色器代码:

    #version 330
    layout(location = 0) in vec3 position;
    layout(location = 1) in vec2 textCoord;
    layout(location = 2) in vec3 normal;
    
    out vec3 FragPos;
    out vec2 TextCoord;
    out vec3 FragNormal;
    
    uniform mat4 projection;
    uniform mat4 view;
    uniform mat4 model;
    
    void main() {
        gl_Position = projection * view * model * vec4(positon, 1.0);
        FragPos = vec3(model * vec4(position, 1.0));
        TextCoord = textCoord;
        mat3 normalMatrix = mat3(transpose(inverse(model)));
        FragNormal = normalMatrix * normal;
    }
    

    片元着色器代码:

    #version 330
    
    precision mediump float;
    
    uniform vec3 lightPos;  // 光源位置
    uniform vec3 lightColor;
    uniform vec3 objectColor;
    
    in vec3 FragPos;       // 模型变换的顶点位置
    in vec2 TextCoord;
    in vec3 FragNormal;
    
    void main() {
    
        vec3 lightDir = normalize(lightPos - FragPos);
        vec3 normal = normalize(FragNormal);
        float diffuseFactor = max(dot(lightDir, normal), 0.0);
        vec3 diffuse = diffuseFactor * lightColor * objectColor;
        gl_FragColor = vec4(diffuse, 1.0);
    }
    

镜面反射光

  • 镜面光模拟的是物体表面光滑时反射的高亮光,镜面光反射的通常是光的颜色,而不是物体的颜色。
  • 计算镜面光,需要考虑光源和顶点位置之间的向量L、法向量N、反射方向R、观察者和顶点位置之间的向量V之间的关系。


    镜面光.png
  • 当R和V的夹角θ越小时,人眼观察到的镜面光成分越明显。镜面反射系数定义为:specFactor = cos(θ)^s, 其中s表示镜面高光系数(shininess),它的值一般取2的整数幂,值越大则高光部分越集中。

  • 镜面反射颜色 = 光源的镜面光颜色 * 物体的镜面光材质颜色 * specFactor

    #version 330
    
    precision mediump float;
        
    uniform vec3 lightPos;  // 光源位置
    uniform vec3 lightColor;
    uniform vec3 objectColor;
    uniform vec3 viewPos;
        
    in vec3 FragPos;       // 模型变换的顶点位置
    in vec2 TextCoord;
    in vec3 FragNormal;
    
    void main() {
        // 镜面反射成分
        float specularStrength = 0.5f;
        vec3 reflectDir = normalize(reflect(-lightDir, normal));
        vec3 viewDir = normalize(viewPos - FragPos);
        float specFactor = pow(max(dot(reflectDir, viewDir), 0.0), 32); //32为镜面高光系数
        vec3 specular = specularStrength * specFactor * lightColor * objectColor;
        gl_FragColor = vec4(specular, 1.0); 
    }
    
    

    利用reflect函数计算光的出射方向时,要求入射方向指向物体表面位置,因此这里翻转了lightDir

材质属性

  • 不同物体对光有不同的反映,要模拟不同物体接受光照后的效果,需要考虑物体的材质属性。

  • 为物体指定材质属性时,可以为物体指定这三个不同成分的光的强度作为材质属性,同时加上高光系数shininess。

  • 同时可以为光源不同成分指定不同的强度。片元着色器的代码为:

    #version 330
    
    in vec3 FragPos;
    in vec2 TextCoord;
    in vec3 FragNormal;
    
    out vec4 color;
    
    // 材质属性结构体
    struct MaterialAttr
    {
        vec3 ambient;   // 环境光
        vec3 diffuse;    // 漫反射光
        vec3 specular;   // 镜面光
        float shininess; //镜面高光系数
    };
    // 光源属性结构体
    struct LightAttr
    {
        vec3 position;
        vec3 ambient;
        vec3 diffuse;
        vec3 specular;
    };
    
    uniform MaterialAttr material;
    uniform LightAttr light;
    uniform vec3 viewPos;
    
    void main()
    {   
        // 环境光成分
        vec3    ambient = light.ambient * material.ambient;
    
        // 漫反射光成分 此时需要光线方向为指向光源
        vec3    lightDir = normalize(light.position - FragPos);
        vec3    normal = normalize(FragNormal);
        float   diffFactor = max(dot(lightDir, normal), 0.0);
        vec3    diffuse = diffFactor * light.diffuse * material.diffuse;
    
        // 镜面反射成分 此时需要光线方向为由光源指出
        float   specularStrength = 0.5f;
        vec3    reflectDir = normalize(reflect(-lightDir, normal));
        vec3    viewDir = normalize(viewPos - FragPos);
        float   specFactor = pow(max(dot(reflectDir, viewDir), 0.0), material.shininess);
        vec3    specular = specFactor * light.specular * material.specular;
    
        vec3    result = ambient + diffuse + specular;
        color   = vec4(result , 1.0f);
    }
    

光源类型

方向光源

  • 方向光源的方向几乎是平行,只有一个方向。

  • 不考虑光的衰减,它与光源的具体位置无关,只需要为它指定方向即可。

  • 一般我们指定方向光源的方向时,习惯从光源指向物体,而在计算光照时,又需要从物体指向光源的方向,因此需要做一个翻转。

    // 方向光源属性结构体
    struct DirLightAttr
    {
        vec3 direction; // 方向光源
        vec3 ambient;
        vec3 diffuse;
        vec3 specular;
    };
    
  • 计算光照时,直接使用direction
    vec3 lightDir = normalize(-light.direction)

点光源

  • 物体与光源的距离d增大时,光照的强度将会减弱
  • 光照强度的衰减系数Fatt与距离d之间的关系为:
    衰减因子.png

点光源结构体:
```
// 点光源属性结构体
struct PointLightAttr
{
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;

    float constant; // 衰减常数
    float linear;   // 衰减一次系数
    float quadratic; // 衰减二次系数
};
```

计算光照强度后乘以衰减系数

```
  // 计算衰减因子
float distance = length(light.position - FragPos); // 在世界坐标系中计算距离
float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * distance * distance);
vec3 result = (ambient + diffuse + specular) * attenuation;
color   = vec4(result , 1.0f);
```

聚光灯光源

  • 聚光灯光源的特点是光只在一个指定的范围内发散。


    聚光灯.png
  • 聚光灯需要指定3个属性:

    • SpotDir 聚光灯的灯轴的方向
    • LightPos 聚光灯的位置
    • Cutoff 聚光灯的张角 即图中的ϕ
  • 计算聚光灯的光照效果时需要计算的量为:

    • lightDir 物体的位置和光源位置之差构成的光照射方向
    • θ是lightDir与SpotDir之间的夹角
      // 聚光灯光源属性结构体
    struct SpotLightAttr
    {
        vec3 position;  // 聚光灯的位置
        vec3 direction; // 聚光灯的spot direction
        float cutoff;   // 聚光灯张角的余弦值
        vec3 ambient;
        vec3 diffuse;
        vec3 specular;
    
        float constant; // 衰减常数
        float linear;   // 衰减一次系数
        float quadratic; // 衰减二次系数
    };
    
    

    片元着色器实现思路:

       void main()
    {   
       // 环境光成分
       vec3 lightDir = normalize(light.position - FragPos);
       // 光线与聚光灯spotDir夹角余弦值
       float theta = dot(lightDir, normalize(-light.direction));
        if(theta > light.cutoff)    
        {
            // 在聚光灯张角范围内 计算漫反射光成分 镜面反射成分 
        }
        else
        {
            // 不在张角范围内时只有环境光成分
        }
    }
    

参考博客

OpenGL学习脚印:光源类型和使用多个光源(Light source and multiple lights)
OpenGL学习脚印: 光照基础(basic lighting)
OpenGL学习脚印: 光照中材质和lighting maps使用(material and lighting maps)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容