光照在现实世界是很复杂的
游戏引擎中一般使用简单的光照模型进行模拟
如图:
Bling-Phong Light Model 光照模型:
1.环境光照(Ambient Lighting)
即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的
我们会使用一个环境光照常量,它永远会给物体一些颜色
2.漫反射光照(Diffuse Lighting)
模拟光源对物体的方向性影响(Directional Impact)
物体的某一部分越是正对着光源,它就会越亮
3.镜面光照(Specular Lighting)
模拟有光泽物体上面出现的亮点
镜面光照的颜色相比于物体的颜色会更倾向于光的颜色
在OpenGL中如何模拟光照?
大体流程:
a)创建光源和受光照物体
b)着色器中使用光照模型计算每个片段的光照颜色
先创建光源和受光照物体,都用Cube模拟
1.计算光照信息需要模型空间坐标+模型法线方向,先填充顶点信息
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f
};
2.创建VBO、VAO
VBO(Vertex Buffer Object)是用于存储顶点数据的对象
VAO(Vertex Array Object)是用于管理这些数据对象的对象
光源和受光照物体适用不同的VAO
因为:会频繁地对顶点数据和属性指针做出修改
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// ========================= Cube
unsigned int cubeVAO;
glGenVertexArrays(1, &cubeVAO);
glBindVertexArray(cubeVAO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// ========================= Lighting
unsigned int lightingVAO;
glGenVertexArrays(1, &lightingVAO);
glBindVertexArray(lightingVAO);
// 只需要绑定VBO不用再次设置VBO的数据,因为箱子的VBO数据中已经包含了正确的立方体顶点数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
3.定义Shader着色器代码
对于光源着色器,只需要输出光的颜色即可
//vertex shader
#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.0f);
}
//fragment shader
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0);
}
对于受光物体的着色器要复杂一些,需要计算根据光照模型计算输出颜色
a)顶点着色器:负责对模型空间的坐标和法线进行世界空间的转换
注意:法线转换过程需要看下缩放
#version 330 core
layout (location=0) in vec3 positionOS;
layout (location=1) in vec3 normalOS;
out vec3 positionWS;
out vec3 normalWS;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(positionOS, 1.0f);
//计算世界空间坐标,乘以模型矩阵
positionWS = vec3(model * vec4(positionOS, 1.0));
//计算法线世界方向
//
//不能直接乘以模型矩阵
//1.法向量只是一个方向向量,不能表达空间中的特定位置
// 对于法向量,没有齐次坐标(顶点位置中的w分量),只希望对它实施缩放和旋转变换。
//2.模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面了
//
//使用一个为法向量专门定制的模型矩阵。这个矩阵称之为法线矩阵(Normal Matrix)
// 法线矩阵:模型矩阵左上角3x3部分的逆矩阵的转置矩阵
//计算比较耗费性能,一般都提前算出来传进去
//mat3 normalMatrix = mat3(transpose(inverse(model)));
//normalWS = normalMatrix * normalOS;
//对于一般模型 没有缩放的话 直接乘就行了
normalWS = mat3(model) * normalOS;
}
b)片断着色器,计算光照颜色
#version 330 core
in vec3 positionWS;
in vec3 normalWS;
out vec4 FragColor;
uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 cameraPos;
void main()
{
//环境光
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
//漫反射
vec3 lightDir = normalize(lightPos - positionWS);
vec3 normalDir = normalize(normalWS);
vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * lightColor;
//高光反射
float specularStrength = 0.5;
vec3 viewDir = normalize(cameraPos - positionWS);
vec3 halfDir = normalize(viewDir + lightDir);
vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), 10.0) * specularStrength * lightColor;
vec3 lightingResult = ambient + diffuse + specular;
FragColor = vec4(lightingResult * objectColor, 1.0);
}
4.渲染循环中传递数据,进行渲染
注意:观察矩阵和投影矩阵应该用同一个
glEnable(GL_DEPTH_TEST);
//灯光位置
glm::vec3 lightPos(0.6f, 0.4f, -1.0f);
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.1f, 0.1f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
float time = (float)glfwGetTime();
deltaTime = time - lastFrame;
lastFrame = time;
glm::mat4 view = camera.GetViewMatrix();
lightPos = glm::vec3(sin(time), 0.0f, cos(time)) * 2.0f;
glm::mat4 projection = glm::mat4(1.0f);
projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 model = glm::mat4(1.0f);
cubeShader.use();
cubeShader.setM4("model", model);
cubeShader.setM4("view", view);
cubeShader.setM4("projection", projection);
cubeShader.setF3("objectColor", 1.0f, 0.5f, 0.31f);
cubeShader.setF3("lightColor", 1.0f, 1.0f, 1.0f);
cubeShader.setF3("lightPos", lightPos);
cubeShader.setF3("cameraPos", camera.Position);
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glm::mat4 lightingModel = glm::mat4(1.0f);
lightingModel = glm::translate(lightingModel, lightPos);
lightingModel = glm::scale(lightingModel, glm::vec3(0.3f));
lightingShader.use();
lightingShader.setM4("model", lightingModel);
lightingShader.setM4("view", view);
lightingShader.setM4("projection", projection);
glBindVertexArray(lightingVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(window);
glfwPollEvents();
}