07 - OpenGL ES学习之基本光照

在学习光照之前,我们先要学习一下基础知识。
示例代码在本系列文章中第一篇里的github仓库。

颜色

在现实世界中,我们看到一个物体的颜色,是由于它能反射这种颜色的光,比如我们看见一个红色的苹果,是因为光照经过苹果表面时,苹果表面吸收了除了红色之外的光,而红光反射后进入人眼,就能看见红色。

现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是我们有限的计算能力所无法模拟的。因此OpenGL的光照使用的是简化的模型,对现实的情况进行近似,这样处理起来会更容易一些,而且看起来也差不多一样。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。


截屏2021-12-07 上午10.40.15.png
  • 环境光照(Ambient Lighting):即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
  • 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。
  • 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。

环境光照

下面我们来模拟一下环境光照,环境光照非常简单,把环境光照加入场景很简单,就是一个光照强度常量因子乘以光的颜色,得到的结果再乘以物体的颜色,就是片段的颜色了。
下面我们接着用上一篇文章中的代码来实现为立方体加入环境光照。这里我们在片段着色器中加入一个LightColor 三分量,这里表示颜色的光,我们传入白色的环境光,就是(1.0,1.0,1.0);

#version 300 es
precision mediump float;

uniform vec3 lightColor;

in vec3 fColor;

out vec4 gColor;

void main() {
    //计算环境光照
    float ambientStrength = 0.2;

    vec3 ambient =  ambientStrength  * lightColor;

    //最终颜色等于环境光照乘以物体颜色
    vec3 result = ambient * fColor;

    gColor = vec4(result,1.0);

}

环境光照强度0.2 效果图:


IMG_6683.PNG

环境光照强度0.5效果图:


IMG_6684.PNG

环境光照强度1.0效果图:


IMG_6685.PNG

从上面几种效果可以大致感受到环境光照强度对物体表面最终成色的影响。可以体会到,当环境光照强度越小的情况,物体表面最终成色越趋近于黑色,这个是比较符合现实

漫反射

环境光照本身不能提供最有趣的结果,但是漫反射光照就能开始对物体产生显著的视觉影响了,对于漫反射我们可以这样理解:漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度。为了能够更好的理解漫反射光照,请看下图:


漫反射示意图

图左上方有一个光源,它所发出的光线落在物体的一个片段上。我们需要测量这个光线是以什么角度接触到这个片段的。如果光线垂直于物体表面,这束光对物体的影响会最大化。当θ接近于90度时,这束光对物体的影响最小。我们用法向量和代表光照的向量点乘的结果表示这个影响的值,正正好负责上述的结论:当光纤垂直于物体表面时,对物体的影响最大。
所以我们如果想要计算漫反射光照,需要两个条件:1.垂直于顶点的法向量(单位向量)2.定向的光线:光源的位置与片段的位置之间向量差的方向向量。为了计算这个光线,我们需要光的位置向量和片段的位置向量。
注意: 这里片段的位置还必须是世界坐标中的位置!

下面我们来计算一下漫反射
先看顶点着色器:

#version 300 es

precision mediump float;

in vec4 vPosition;

//垂直于顶点的法向量
in vec3 N;

out vec3 fColor;
out vec3 Normal;
//片段的位置,转化为世界坐标系下的
out vec3 fragPos;

uniform mat4 modelTransform;
uniform mat4 viewTransform;
uniform mat4 projectTransform;

//法线变换矩阵,把法线向量转化到世界空间中,需要运用到法线变换矩阵
uniform mat4 invertTransposeMatrix;

void main(){
    
    mat4 mvpTransform = projectTransform * viewTransform * modelTransform;
    
    gl_Position = mvpTransform * vPosition;
    
    //将顶点位置转换为世界空间坐标
    fragPos = (modelTransform * vPosition).xyz;
    
    //法线向量转换为世界空间坐标
    Normal = (invertTransposeMatrix * vec4(N,0.0)).xyz;
}

这里需要注意的有三点:
1.我们取的顶点位置需要转换到世界空间坐标下。
2.法线向量的转换:这里需要用法线矩阵来乘以法线向量,将法线向量也转换到世界空间坐标下。法线矩阵被定义为「模型矩阵左上角的逆矩阵的转置矩阵」。至于法线矩阵为什么这样取值,感兴趣的可以去百度一下,这里不做深入探讨了。
3.法线向量我们通过属性Normal传递进来,因为我们这里绘制的是立方体,它每个面的法向量的取值非常简单,比如+X轴的面的法线向量就是(1.0, 0.0, 0.0)。
代码中是这样获取逆转置矩阵:

 //获取法线变换矩阵
    BOOL canConvert = YES;
    GLKMatrix4 invertTransposeMatrix = GLKMatrix4InvertAndTranspose(modelMatrix, &canConvert);
    
    //如果能获取到法线矩阵,就加载
    if (canConvert) {
        glUniformMatrix4fv(invertTransposeIndex, 1, GL_FALSE, invertTransposeMatrix.m);
    }

GLKit提供了很多矩阵操作的API,可以提高开发效率。

接下来看一下顶点着色器有哪些改变:

#version 300 es
precision mediump float;

uniform vec3 lightColor;
//光源位置
uniform vec3 lightPos;

in vec3 Normal;
in vec3 fragPos;

out vec4 gColor;

void main() {
    //计算环境光照
    float ambientStrength = 0.2;

    vec3 ambient =  ambientStrength  * lightColor;
    
    //计算漫反射
    //计算光线位置
    vec3 norm = normalize(Normal);
    vec3 lightDirection = normalize(lightPos - fragPos);
    
    float diffu = dot(norm,lightDirection);
    vec3 diffuse = diffu * lightColor;

    //最终颜色(这里指定物体颜色为红色)
    vec3 result = (ambient + diffuse) * vec3(1.0,0.0,0.0);

    gColor = vec4(result,1.0);

}

这里需要注意:
我们传入的光源位置也是基于世界空间坐标系,光源位置减去顶点位置就是光线的方向向量,然后我们对法线向量和光线方向向量取单位向量,因为我们当计算光照时我们通常不关心一个向量的模长或它的位置,我们只关心它们的方向。所以,几乎所有的计算都使用单位向量完成,因为这简化了大部分的计算(比如点乘)。所以当进行光照计算时,确保你总是对相关向量进行标准化,来保证它们是真正地单位向量。忘记对向量进行标准化是一个十分常见的错误。

最后我们看一下加入了环境光照和漫反射的效果:


IMG_6694.PNG

这里是不是看起来更像一个立方体了,不要着急,后面还有一个很关键的因素:镜面反射:

镜面反射

截屏2021-12-07 下午3.50.23.png

在生活中你也许会有过这种经历,窗外的阳光照射到镜子的时候,你挪动位置看向镜子,会发现在一个位置,光线最为刺眼,镜面反射就是模拟这种效果,如上图所示。
光线照在物体表面,反射后形成反射光线(我们计为R),人看向物体表面被照射的点形成一个观察向量,当这两条向量重合的时候(θ = 0°时),人眼看到的光线强度最大,当两条向量夹角θ 越大时,看到的光线强度最小,我们就需要计算这个镜面分量。
下面我们看一下顶点着色器的变化:

version 300 es

precision mediump float;

uniform vec3 lightColor;
//光源位置
uniform vec3 lightPos;

//观察点的位置
uniform vec3 viewPos;

in vec3 Normal;
in vec3 fragPos;

out vec4 gColor;

void main() {
//计算环境光照
float ambientStrength = 0.3;

vec3 ambient =  ambientStrength  * lightColor;

//计算漫反射
//计算光线位置
vec3 norm = normalize(Normal);
vec3 lightDirection = normalize(lightPos - fragPos);

float diffu = dot(norm,lightDirection);
vec3 diffuse = diffu * lightColor;

//计算镜面光照
//定义一个镜面强度
float specularStrength = 0.5;

//观察向量
vec3 viewDirection = normalize(viewPos - fragPos);
//反射向量
vec3 reflectDirection = reflect(-lightDirection,norm);

float spec = pow(max(dot(viewDirection,reflectDirection), 0.0) , 32.0);

vec3 specular = specularStrength * spec * lightColor;

//最终颜色(这里指定物体颜色为红色)
vec3 result = (ambient + diffuse + specular) * vec3(1.0,0.0,0.0);

gColor = vec4(result,1.0);

}

这里我们要注意以下几点:
1.计算反射向量的时候,我们第一个传入的光线向量取值是反的,因为这里lightDirection是顶点到光源的向量,而reflect函数要求第一个传入的参数是光源到顶点的向量,所以要取反。
2.计算镜面分量:float spec = pow(max(dot(viewDirection,reflectDirection), 0.0) , 32.0);
这里我们取观察向量和反射向量的点乘结果,只取正值,这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。改变这个反光度,你会看到不同反光度的视觉效果影响。

在加入镜面光照后,我们改变了光源的位置,这样是为了使光线向量和观察向量
的夹角处于一个合适的范围,以便于我们观察到添加镜面光照后效果。

//设置环境光颜色为白色
    static float light[3] = {1.0f, 1.0f, 1.0f};
    
    //设置光源位置(世界空间坐标系)
    static float lightPos[3] = {0.0,1.5,-1.5};
    
    //这里设置观察点为摄像机的位置(cameraMatrix里eye的位置)
    static float viewPos[3] = {0.0,0.0,2.0};

最终效果如下图:


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

推荐阅读更多精彩内容