1. 引言

本文基于C++语言,描述OpenGL的基础光照

前置知识可参考:

笔者这里不过多描述每个名词、函数和细节,更详细的文档可以参考:

2. 概述

冯氏光照模型(Phong Lighting Model):环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照

漫反射光照的强度由法向量与光的方向向量的余弦值决定,角度越小,光照越强

镜面光照的强度由视线向量与光的反射向量的余弦值决定,角度越小,光照越强

3. 环境光照

使用一个很小的常量因子乘以光的颜色,模拟环境光照

在片段着色器GLSL中:

...
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor; vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}

结果如下图:

4. 漫反射光照

使用光线与法向量的余弦值作为漫反射因子,再乘以光的颜色来模拟漫反射光照

在这里,法向量由我们手动输入,光的方向由光的位置向量减去平面的位置向量获取

4.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
};

绑定属性:

//  立方体
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);
// 光源立方体
...
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

在顶点着色器中将法向量变换为世界坐标并向片段着色器传递数据:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
...
out vec3 Normal; void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
Normal = mat3(transpose(inverse(model))) * aNormal;
}

在片段着色器接收数据:

in vec3 Normal;
void main()
{
...
vec3 norm = normalize(Normal);
}

4.2 光的方向向量

在片段着色器中设置光源位置向量:

uniform vec3 lightPos;

传输数据:

lightingShader.setVec3("lightPos", lightPos);

将平面位置变换到世界坐标得到位置向量:

out vec3 FragPos;
out vec3 Normal; void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = aNormal;
}

在片段着色器接收数据并计算光的方向向量:

...
in vec3 FragPos;
void main()
{
...
vec3 lightDir = normalize(lightPos - FragPos);
}

4.3 计算漫反射光照

计算方向向量与光的方向向量之间的余弦值作为漫反射因子(小于零的设为零),再乘以光照颜色得到漫反射光照

在片段着色器中:

float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;

将环境光照与漫反射光照结合:

vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);

结果如下图:

5. 镜面光照

计算视线向量与光的反射向量之间的余弦值作为镜面反射因子(小于零的设为零),再乘以光照颜色得到漫反射光照

在片段着色器中设置观察位置:

uniform vec3 viewPos;

向GPU传输数据:

lightingShader.setVec3("viewPos", cameraPos);

计算视线向量(观察方向)与反射向量:

vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);

定义镜面强度,即反射能力:

float specularStrength = 0.5;

计算镜面光照:

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;

将环境光照、漫反射光照、镜面光照结合:

vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);

结果如下图:

6. 完整代码

主要文件Lighting.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <math.h>
#include "Shader.hpp"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <glm/glm.hpp>
#include <glm/ext/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
#include <glm/gtc/type_ptr.hpp> //全局变量
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 10.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 lightPos(1.2f, 1.0f, 2.0f); // 函数声明
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void process_input(GLFWwindow *window); int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
GLFWwindow *window = glfwCreateWindow(800, 600, "lighting", nullptr, nullptr); if (window == nullptr)
{
std::cout << "Faild to create window" << std::endl;
glfwTerminate();
}
glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Faild to initialize glad" << std::endl;
return -1;
}
glad_glViewport(0, 0, 800, 600);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //配置项
glEnable(GL_DEPTH_TEST); Shader lightCubeShader("../light_cube.vs.glsl", "../light_cube.fs.glsl");
Shader lightingShader("../cube.vs.glsl", "../cube.fs.glsl"); unsigned int cubeVAO;
glGenVertexArrays(1, &cubeVAO);
glBindVertexArray(cubeVAO); 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
};
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 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); unsigned int lightCubeVAO;
glGenVertexArrays(1, &lightCubeVAO);
glBindVertexArray(lightCubeVAO);
// 只需要绑定VBO不用再次设置VBO的数据,因为箱子的VBO数据中已经包含了正确的立方体顶点数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 设置灯立方体的顶点属性(对我们的灯来说仅仅只有位置数据)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); while (!glfwWindowShouldClose(window))
{
process_input(window); glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); lightingShader.use();
lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
lightingShader.setVec3("lightPos", lightPos);
lightingShader.setVec3("viewPos", cameraPos);
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f)); glm::mat4 view = glm::mat4(1.0f);
// view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); glm::mat4 projection = glm::mat4(1.0f);
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f); // 模型矩阵
int modelLoc = glGetUniformLocation(lightingShader.ID, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
// 观察矩阵和投影矩阵与之类似
int viewLoc = glGetUniformLocation(lightingShader.ID, "view");
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
int projectionLoc = glGetUniformLocation(lightingShader.ID, "projection");
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection)); // render the cube
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36); // also draw the lamp object
lightCubeShader.use();
lightCubeShader.setMat4("projection", projection);
lightCubeShader.setMat4("view", view);
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube
lightCubeShader.setMat4("model", model); glBindVertexArray(lightCubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36); glfwSwapBuffers(window);
glfwPollEvents();
} glfwTerminate();
return 0;
} void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
glViewport(0, 0, width, height);
} void process_input(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, true);
}
float cameraSpeed = 0.05f; // adjust accordingly
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
cameraPos += cameraSpeed * cameraFront;
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
cameraPos -= cameraSpeed * cameraFront;
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}

立方体顶点着色器GLSLcue.vs.glsl

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal; out vec3 Normal;
out vec3 FragPos; uniform mat4 model;
uniform mat4 view;
uniform mat4 projection; void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = aNormal;
}

立方体片段着色器GLSLcube.fs.glsl

#version 330 core
in vec3 Normal;
in vec3 FragPos; out vec4 FragColor; uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos; void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor; vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor; vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float specularStrength = 0.8;
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor; vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}

着色器Shader.hpp、光源顶点着色器GLSLlight_cube.vs.glsl、光源片段着色器GLSLlight_cube.fs.glsl见:

7. 参考资料

[1]基础光照 - LearnOpenGL CN (learnopengl-cn.github.io)

最新文章

  1. Spark基本工作流程及YARN cluster模式原理(读书笔记)
  2. 【Android】学习记录&lt;1&gt; -- 初识ffmpeg
  3. 一起入门python7之函数参数
  4. 主机无法访问虚拟机上的elasticsearch服务器
  5. PLSQL_Oracle外部表的概念和使用(案例)
  6. Android PullToRefresh下拉刷新控件的简单使用
  7. session cookie 相结合实现
  8. [转]Spring Boot——2分钟构建spring web mvc REST风格HelloWorld
  9. C函数数组元素初始化
  10. MQTT协议详解一
  11. Windows Phone 8初学者开发—第20部分:录制Wav音频文件
  12. 深入 CSocket 编程之阻塞和非阻塞模式
  13. erlang套接字
  14. java 1.7新特性
  15. 微信JS SDK接入的几点注意事项
  16. [iOS]改变UIAlertController的标题、内容的字体和颜色
  17. JS事件(三)部分常用事件
  18. webpack中resolve用法
  19. 统计分页一些sql
  20. 潭州课堂25班:Ph201805201 爬虫基础 第十二课 点触验证码二 (课堂笔记)

热门文章

  1. 单一JVM同步锁实现
  2. 【开源库推荐】#4 Poi-办公文档处理库
  3. 零基础入门 Java 后端开发,有哪些值得看的视频?
  4. uniapp微信小程序内部跳转其他微信小程序
  5. Linux基础第五章 进程控制
  6. 学习.NET MAUI Blazor(二)、MAUI是个啥
  7. $_GET方法踩坑
  8. 【译】2022 年回顾:Web 性能有哪些新变化?
  9. Mybatis-plus实现数据库的增删改查操作
  10. 关系数据库——MySQL