OpenGL(10)之颜色

704 阅读5分钟

title: OpenGL(10)之颜色

date: 2020-07-17 18:08

category: 图形学

tags: opengl

OpenGL(10)之颜色

项目代码可参见 1.colors

1. 概述

现实世界上中有无数的颜色,每个物体都有自己的颜色,我们需要用有限的数值来模拟真实世界中无限的颜色,并非现实世界中所有的颜色都可以用数值来表示,但是我们仍能通过数值来表现出非常多的颜色。颜色由数字化的红色(Red)、绿色(Green)、蓝色(Blue)三个分量组成,也即RGB三原色。仅仅用这三个值就可以组合出任意一种颜色。

获取一个珊瑚红(Coral)色的话,通过定义一个三维的颜色向量:

glm::vec3 coral(1.0f,0.5f,0.31f);

现实生活中看到的某一个物体的颜色并不是这个物体真正拥有的颜色,而是它所反射的颜色. 即不能被物体所吸收的颜色(被拒绝的颜色)就是我们能够感知到的物体的颜色。

举个例子: 将白光照在一个绿色的玩具上,这个蓝色玩具会吸收白光中除了绿色以外的所有子颜色。不被吸收的蓝光被反射到我们的眼中,让这个玩具看起来是绿色的。

所以说白色光实际上是所有可见颜色的集合,物体吸收了其中大部分的颜色,仅仅反射了代表物体颜色的部分,被反射的颜色组合就是我们所感知的颜色(其中为绿色)。

在OpenGL中,创建一个光源,给定光源为白色,当我们把光源的颜色与物体的颜色值相乘,所得到的就是这个物体所反射的颜色(即是我们肉眼所看到的颜色)。

glm::vec3 lightColor(1.0f,1.0f,1.0f);
glm::vec3 objectColor(0.0f,1.0f,0.0f);
glm::vec3 result = lightColor * objectColor; //=(0.0f,1.0f,0.0f);

可以看到物体完全吸收了红光和蓝光,但是绿光完全被反射出去了。

可能上述例子太过于极端,举一个珊瑚红颜色的例子:

glm::vec3 lightColor(0.0f,1.0f,0.0f);
glm::vec3 objectColor(1.0f,0.5f,0.31f);//珊瑚红
glm::vec3 result = lightColor * objectColor;//=(0.0f,0.5f,0.0f);

只用了绿色光源,没有红色和蓝色分量,该物体还是吸收了光源中一半的绿色值,仍然反射了一半的绿色值。也就是说用绿色光源来照射珊瑚红色的物体,只有绿色分量能够被反射和感知,红色和蓝色不能被感知,该物体变成了一个深绿色的物体。

由此可看出,使用不同的光源颜色能够让物体显示出不同的颜色效果。

2. 创建一个光照场景

首先我们需要一个物体作为被投光的对象(即光源所照射的物体),以及一个物体来代表光源在3D场景中的位置。

同样的我们需要填充一个顶点着色器对象(VBO)将物体的顶点数据进行填充,继而进行物体的绘制,so,编写顶点着色器代码:

#version 330 core
layout (location = 0 )in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main(){
	gl_Position = projection * view *model *vec4(aPos,1.0);
}

创建一个表示光源的立方体,为其创建一个专门的VAO,简单对其model(模型)矩阵进行一些变换即可,在接下来的章节中会频繁的对顶点数据和属性指针作出修改,因此这些修改可能会影响到光源(光源的顶点位置等),因此这也是为什么为其专门创建一个VAO,废话不多说,开工;

unsigned int lightVAO;
glGenVertexArrays(1,&lightVAO);
glBindVertexArray(lightVAO);
//只需要绑定VBO即可不用再次设置VBO数据,即不使用glBufferData函数,因为光源沿用物体的VBO数据。
glBindBuffer(GL_ARRAY_BUFFER,VBO);
//设置光源的顶点属性
glVertexAttribPointer(0,3,GL_FLOAT,GL_FLASE,3*sizeof(float),(void*)0);
//激活顶点属性
glEnableVertexAttribArray(0);

好了 上述工作做完之后,就可以来定义片段着色器,用于着色器操作了;

#version 330 core

out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;

void main(){
	FragColor = vec4(lightColor * objectColor ,1.0);
}

从上述片段着色器可以看出,需要从uniform变量中接受物体的颜色和光源的颜色。那么贴出代码片段如下:

//切记,在设置unifrom之前要使用这个着色器程序
lightShader.use();
lightShader.setVec3("objectColor", 1.0f,0.5f,0.31f);
lightShader.setVec3("lightColor",1.0f,1.0f,1.0f);

要注意的是,当修改顶点或者片段着色器时候,光源的位置或颜色也会随之改变,这显然是不对的,因此我们希望光源一直保持着明亮的白光,不受其他颜色变换影响。

这里采取为光源绘制创建另外一套着色器,从而保证其能够在其他光照着色器发生改变的时候不受影响,顶点着色器与当前的顶点着色器一致,光源的片段着色器定义为一个不变的常量白色;

#version 330 core;

out vec4 FragColor;

void main(){
	FragColor =vec4(1.0); // 向量的4个分量都为1.0
}

为了凸显光源照射效果,使用一个光源立方体来确切的定位光源在场景中的位置,因此在场景中定义一个光源的位置,并将光源立方体绘制在光源相同的位置。使用上述定义的片段着色器,使其一直处于白色的状态,不受场景中的关照影响(具体可以参考OpenGL(9)之基础光照一节中讲的冯氏光照模型);

声明一个全局的vec3变量来表示光源在场景的世界空间坐标的位置;

glm::vec3 ligthPosition(1.2f,1.0f,2.0f);

通过变换将光源进行位移并且进行缩放操作,使得光源形成一个点光源。

model  = glm::mat4();
model  = glm::translate(model,lightPos); // 将局部空间坐标转换到世界空间坐标。
model = glm::scale(mode,glm::vec3(0.2f));//x,y,z分量缩放为0.2

接下来就是绘制光源立方体:

//....
//绘制光源立方体
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES,0,36);

好了,完成上述操作,可以得出以下效果:

参考

LearnOpenGL