在现实世界里,每个物体会对光产生不同的反应。
比如,钢制物体看起来通常会比陶土花瓶更闪闪发光,一个木头箱子也不会与一个钢制箱子反射同样程度的光。
有些物体反射光的时候不会有太多的散射(Scatter),因而产生较小的高光点,而有些物体则会散射很多,产生一个有着更大半径的高光点。
如果我们想要在OpenGL中模拟多种类型的物体,我们必须针对每种表面定义不同的材质(Material)属性。
在跟着LearnOpenGL学习10–基础光照这一篇中,我们定义了一个物体和光的颜色,并结合环境光与镜面强度分量,来决定物体的视觉输出。
当描述一个表面时,我们可以分别为三个光照分量定义一个材质颜色(Material Color):
通过为每个分量指定一个颜色,我们就能够对表面的颜色输出有细粒度的控制了。
现在,我们再添加一个反光度(Shininess)分量,结合上述的三个颜色,我们就有了全部所需的材质属性了:
片段着色器
struct Material { //材质描述结构体
vec3 ambient; //环境光照
vec3 diffuse; //漫反射光照
vec3 specular; //镜面反射光照
float shininess; //反光度
};
uniform Material material; //材质
在片段着色器中,我们创建一个结构体(Struct)来储存物体的材质属性。
我们也可以把它们储存为独立的uniform值,但是作为一个结构体来储存会更有条理一些。
我们首先定义结构体的布局(Layout),然后简单地以刚创建的结构体作为类型声明一个uniform变量。
如你所见,我们为冯氏光照模型的每个分量都定义一个颜色向量:
ambient
材质向量:定义了在环境光照下这个表面反射的是什么颜色,通常与表面的颜色相同。diffuse
材质向量:定义了在漫反射光照下表面的颜色。漫反射颜色(和环境光照一样)也被设置为我们期望的物体颜色。specular
材质向量:设置的是表面上镜面高光的颜色(或者甚至可能反映一个特定表面的颜色)。shininess
材质向量:影响镜面高光的散射/半径。有这4个元素定义一个物体的材质,我们能够模拟很多现实世界中的材质。如下表格展示了一系列材质属性,它们模拟了现实世界中的真实材质。下图展示了几组现实世界的材质参数值对我们的立方体的影响:
名字 | 环境光照 | 漫反射光照 | 镜面反射光照 | 反射强度 |
---|---|---|---|---|
emerald(翡翠) | 0.0215, 0.1745, 0.0215 | 0.07568, 0.61424, 0.07568 | 0.633, 0.727811, 0.633 | 0.6 |
jade(玉) | 0.135, 0.2225, 0.1575 | 0.54, 0.89, 0.63 | 0.316228, 0.316228, 0.316228 | 0.1 |
obsidian(黑曜石) | 0.05375, 0.05, 0.06625 | 0.18275, 0.17, 0.22525 | 0.332741, 0.328634, 0.346435 | 0.3 |
pearl(珍珠) | 0.25, 0.20725, 0.20725 | 1, 0.829, 0.829 | 0.296648, 0.296648, 0.296648 | 0.088 |
ruby(红宝石) | 0.1745, 0.01175, 0.01175 | 0.61424, 0.04136, 0.04136 | 0.727811, 0.626959, 0.626959 | 0.6 |
turquoise()绿松石 | 0.1, 0.18725, 0.1745 | 0.396, 0.74151, 0.69102 | 0.297254, 0.30829, 0.306678 | 0.1 |
brass(黄铜) | 0.329412, 0.223529, 0.027451 | 0.780392, 0.568627, 0.113725 | 0.992157, 0.941176, 0.807843 | 0.21794872 |
bronze(青铜) | 0.2125, 0.1275, 0.054 | 0.714, 0.4284, 0.18144 | 0.393548, 0.271906, 0.166721 | 0.2 |
chrome(铬) | 0.25, 0.25, 0.25 | 0.4, 0.4, 0.4 | 0.774597, 0.774597, 0.774597 | 0.6 |
copper(铜) | 0.19125, 0.0735, 0.0225 | 0.7038, 0.27048, 0.0828 | 0.256777, 0.137622, 0.086014 | 0.1 |
gold(黄金) | 0.24725, 0.1995, 0.0745 | 0.75164, 0.60648,0.22648 | 0.628281, 0.555802, 0.366065 | 0.4 |
silver(银) | 0.19225, 0.19225, 0.19225 | 0.50754, 0.50754, 0.50754 | 0.508273, 0.508273, 0.508273 | 0.4 |
black plastic(黑色塑料) | 0.0, 0.0, 0.0 | 0.01, 0.01, 0.01 | 0.50, 0.50, 0.50 | 0.25 |
cyan plastic(青色塑料) | 0.0, 0.1, 0.06 | 0.0, 0.50980392, 0.50980392 | 0.50196078, 0.50196078, 0.50196078 | 0.25 |
green plastic(绿色塑料) | 0.0, 0.0, 0.0 | 0.1, 0.35, 0.1 | 0.45, 0.55, 0.45 | 0.25 |
red plastic(红色塑料) | 0.0, 0.0, 0.0 | 0.5, 0.0, 0.0 | 0.7, 0.6, 0.6 | 0.25 |
white plastic(白色塑料) | 0.0, 0.0, 0.0 | 0.55, 0.55, 0.55 | 0.70, 0.70, 0.70 | 0.25 |
yellow plastic(黄色塑料) | 0.0, 0.0, 0.0 | 0.5, 0.5, 0.0 | 0.60, 0.60, 0.50 | 0.25 |
black rubber(黑色橡胶) | 0.02, 0.02, 0.02 | 0.01, 0.01, 0.01 | 0.4, 0.4, 0.4 | 0.78125 |
cyan rubber(青色橡胶) | 0.0, 0.05, 0.05 | 0.4, 0.5, 0.5 | 0.04, 0.7, 0.7 | 0.78125 |
green rubber(绿色橡胶) | 0.0, 0.05, 0.0 | 0.4, 0.5, 0.4 | 0.04, 0.7, 0.04 | 0.78125 |
red rubber(红色橡胶) | 0.05, 0.0, 0.0 | 0.5, 0.4, 0.4 | 0.7, 0.04, 0.04 | 0.78125 |
white rubber(白色橡胶) | 0.05, 0.05, 0.05 | 0.5, 0.5, 0.5 | 0.7, 0.7, 0.7 | 0.78125 |
yellow rubber(黄色橡胶) | 0.05, 0.05, 0.0 | 0.5, 0.5, 0.4 | 0.7, 0.7, 0.04 | 0.78125 |
可以看到,通过正确地指定一个物体的材质属性,我们对这个物体的感知也就不同了。效果非常明显,但是要想获得更真实的效果,我们需要以更复杂的形状替换这个立方体。
搞清楚一个物体正确的材质设定是个困难的工程,这主要需要实验和丰富的经验。用了不合适的材质而毁了物体的视觉质量是件经常发生的事。
让我们试着在着色器中实现这样的一个材质系统。
我们在片段着色器中创建了一个材质结构体的uniform
,所以下面我们希望修改一下光照的计算来遵从新的材质属性。由于所有材质变量都储存在一个结构体中,我们可以从uniform
变量material
中访问它们:
片段着色器
#version 330 core
in vec3 FragPos;
in vec3 Normal;
out vec4 FragColor;
uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos; //观察者坐标
struct Material { //材质描述结构体
vec3 ambient; //环境光照
vec3 diffuse; //漫反射光照
vec3 specular; //镜面反射光照
float shininess; //反光度
};
uniform Material material; //材质
void main()
{
//环境光照
vec3 ambient = material.ambient * lightColor;
//漫反射光照
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightColor - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = (diff *material.diffuse) * lightColor;
//镜面反射光照
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = (material.specular * spec) * lightColor;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
可以看到,我们现在在需要的地方访问了材质结构体中的所有属性,并且这次是根据材质的颜色来计算最终的输出颜色的。物体的每个材质属性都乘上了它们各自对应的光照分量。
我们现在可以通过设置适当的uniform来设置应用中物体的材质了。GLSL中一个结构体在设置uniform时并无任何区别,结构体只是充当uniform变量们的一个命名空间。所以如果想填充这个结构体的话,我们必须设置每个单独的uniform,但要以结构体名为前缀:
lightingShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
lightingShader.setFloat("material.shininess", 32.0f);
我们将环境光和漫反射分量设置成我们想要让物体所拥有的颜色,而将镜面分量设置为一个中等亮度的颜色,我们不希望镜面分量过于强烈。我们仍将反光度保持为32。
现在我们能够轻松地在应用中影响物体的材质了。运行程序,你会得到像这样的结果:
全部代码
main.cpp
#include "mainwindow.h"
#include <QApplication>
//在包含GLFW的头文件之前包含了GLAD的头文件;
//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h);
//所以需要在其它依赖于OpenGL的头文件之前包含GLAD;
#include <glad/glad.h>
#include <GLFW/glfw3.h>
//GLM
//#include <glm/glm.hpp>
//#include <glm/gtc/matrix_transform.hpp>
//#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <QDebug>
#include "shader.h"
#include "stb_image.h"
#include "camera.h"
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
// settings
const unsigned int SCR_WIDTH = 1920;
const unsigned int SCR_HEIGHT = 1080;
//Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = SCR_WIDTH / 2.0;
float lastY = SCR_HEIGHT / 2.0;
bool firstMouse = true;
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
//Light
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
using namespace std;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//MainWindow w;
//w.show();
//初始化GLFW
//--------------------
glfwInit();
//配置GLFW
//--------------------
//告诉GLFW使用的OpenGL本是3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
//告诉GLFW使用的是核心模式(Core-profile)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//创建一个新的OpenGL环境和窗口
//-----------------------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate(); //glfw销毁窗口和OpenGL环境,并释放资源
return -1;
}
//设置参数window中的窗口所关联的OpenGL环境为当前环境
//-----------------------------------
glfwMakeContextCurrent(window);
//设置窗口尺寸改变大小时的回调函数(窗口尺寸发送改变时会自动调用)
//-----------------------------------
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//设置鼠标事件的回调函数(鼠标移动时会自动调用)
//-----------------------------------
glfwSetCursorPosCallback(window, mouse_callback);
//设置鼠标滚轮事件的回调函数(鼠标滚轮移动时会自动调用)
//-----------------------------------
glfwSetScrollCallback(window, scroll_callback);
//告诉GLFW捕捉鼠标
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//glad加载系统相关的OpenGL函数指针
//---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
//开启深度测试
glEnable(GL_DEPTH_TEST);
Shader objectShader("C:/Qt_Pro/OpenGL_GLFW/shader/shader.vs","C:/Qt_Pro/OpenGL_GLFW/shader/shader.fs");
Shader lightShader("C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.vs","C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.fs");
//顶点数据
//---------------------------------------------------------------------
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, objectVAO;
glGenVertexArrays(1, &objectVAO); //创建顶点数组对象
glGenBuffers(1, &VBO); //创建顶点缓冲对象
glBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //将顶点数据复制到GL_ARRAY_BUFFER缓冲区,之后可通过VBO进行操作
glBindVertexArray(objectVAO); //绑定VAO
//设定顶点属性指针
//位置属性
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);
//光源(VBO用上面的)
//----------------------------------------------------------------
unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO); //创建顶点数组对象
glBindVertexArray(lightVAO); //绑定VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//渲染循环
//我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口;
//我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入;
//因此,我们需要在程序中添加一个while循环,它能在我们让GLFW退出前一直保持运行;
//------------------------------------------------------------------------------
while (!glfwWindowShouldClose(window)) //如果用户准备关闭参数window所指定的窗口,那么此接口将会返回GL_TRUE,否则将会返回GL_FALSE
{
//更新时间差
float currentFrame = static_cast<float>(glfwGetTime());
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
//用户输入
//------------------------------------------------------------------------------
processInput(window); //检测是否有输入
//渲染指令
//------------------------------------------------------------------------------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//被投光物体
//============================================================
//激活着色器程序对象
objectShader.use();
objectShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
objectShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
objectShader.setVec3("lightPos", lightPos);
objectShader.setVec3("viewPos", camera.Position);
objectShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
objectShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f);
objectShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
objectShader.setFloat("material.shininess", 32.0f);
//创建变换矩阵
glm::mat4 model = glm::mat4(1.0f);
objectShader.setMat4("model", model);
glm::mat4 view = camera.GetViewMatrix();
objectShader.setMat4("view", view);
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
objectShader.setMat4("projection", projection);
//绘制三角形
glBindVertexArray(objectVAO); //绑定VAO
glDrawArrays(GL_TRIANGLES, 0, 36);
//============================================================
//光源
//============================================================
//激活着色器程序对象
lightShader.use();
lightShader.setMat4("view", view);
lightShader.setMat4("projection", projection);
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f));
lightShader.setMat4("model", model);
//绘制三角形
glBindVertexArray(lightVAO); //绑定VAO
glDrawArrays(GL_TRIANGLES, 0, 36);
//============================================================
//告诉GLFW检查所有等待处理的事件和消息,包括操作系统和窗口系统中应当处理的消息。如果有消息正在等待,它会先处理这些消息再返回;否则该函数会立即返回
//---------------------------------------------------------------------------------------------------------------------------------
glfwPollEvents();
//请求窗口系统将参数window关联的后缓存画面呈现给用户(双缓冲绘图)
//------------------------------------------------------------------------------
glfwSwapBuffers(window);
}
//释放资源
glDeleteVertexArrays(1, &objectVAO);
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &VBO);
//glDeleteProgram(objectShader);
//glDeleteProgram(lightShader);
//glfw销毁窗口和OpenGL环境,并释放资源(之后必须再次调用glfwInit()才能使用大多数GLFW函数)
//------------------------------------------------------------------
glfwTerminate();
return a.exec();
}
//检测是否有输入
//---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) //ESC键,退出
glfwSetWindowShouldClose(window, true);
float cameraSpeed = static_cast<float>(2.5 * deltaTime);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(RIGHT, deltaTime);
}
//给glfw窗口注册的尺寸改变回调函数
//---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//确保视口匹配新的窗口尺寸,请注意:宽度和高度将比视网膜显示器上指定的大得多
glViewport(0, 0, width, height);
}
// 鼠标移动时的回调函数
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
float xpos = static_cast<float>(xposIn);
float ypos = static_cast<float>(yposIn);
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = lastX - xpos;
float yoffset = ypos - lastY; //翻转,因为Y轴是从下到上越来越大
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
//鼠标滚轮的回调函数
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(static_cast<float>(yoffset));
}
不过看起来真的不太对劲?这个物体太亮了。
物体过亮的原因是环境光、漫反射和镜面光这三个颜色对任何一个光源都全力反射。
光源对环境光、漫反射和镜面光分量也分别具有不同的强度。前面的章节中,我们通过使用一个强度值改变环境光和镜面光强度的方式解决了这个问题。我们想做类似的事情,但是这次是要为每个光照分量分别指定一个强度向量。
如果我们假设lightColor
是vec3(1.0),代码会看起来像这样:
vec3 ambient = vec3(1.0) * material.ambient;
vec3 diffuse = vec3(1.0) * (diff * material.diffuse);
vec3 specular = vec3(1.0) * (spec * material.specular);
所以物体的每个材质属性对每一个光照分量都返回了最大的强度。
对单个光源来说,这些vec3(1.0)值同样可以对每种光源分别改变,而这通常就是我们想要的。
现在,物体的环境光分量完全地影响了立方体的颜色,可是环境光分量实际上不应该对最终的颜色有这么大的影响,所以我们会将光源的环境光强度设置为一个小一点的值,从而限制环境光颜色:
vec3 ambient = vec3(0.1) * material.ambient;
我们可以用同样的方式影响光源的漫反射和镜面光强度。这和我们在上一节中所做的极为相似,你可以认为我们已经创建了一些光照属性来影响各个光照分量。我们希望为光照属性创建类似材质结构体的东西:
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
ambient
、diffuse
和specular
光照分量有着不同的强度。和材质uniform一样,我们需要更新片段着色器:
片段着色器
#version 330 core
in vec3 FragPos;
in vec3 Normal;
out vec4 FragColor;
uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos; //观察者坐标
struct Material { //材质描述结构体
vec3 ambient; //环境光照
vec3 diffuse; //漫反射光照
vec3 specular; //镜面反射光照
float shininess; //反光度
};
uniform Material material; //材质
struct Light { //光照强度
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
void main()
{
//环境光照
vec3 ambient = material.ambient * light.ambient;
//漫反射光照
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightColor - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = (diff *material.diffuse) * light.diffuse;
//镜面反射光照
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = (material.specular * spec) * light.specular;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
我们接下来在应用中设置光照强度:
objectShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
objectShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); // 将光照调暗了一些以搭配场景
objectShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
现在我们已经调整了光照对物体材质的影响,我们得到了一个与上一节很相似的视觉效果。但这次我们有了对光照和物体材质的完全掌控:
到目前为止,我们都只对光源设置了从白到灰到黑范围内的颜色,这样只会改变物体各个分量的强度,而不是它的真正颜色。
由于现在能够非常容易地访问光照的属性了,我们可以随着时间改变它们的颜色,从而获得一些非常有意思的效果。
由于所有的东西都在片段着色器中配置好了,修改光源的颜色非常简单,并立刻创造一些很有趣的效果:
//光照强度
glm::vec3 lightColor;
lightColor.x = static_cast<float>(sin(glfwGetTime() * 2.0));
lightColor.y = static_cast<float>(sin(glfwGetTime() * 0.7));
lightColor.z = static_cast<float>(sin(glfwGetTime() * 1.3));
glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f); // decrease the influence
glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // low influence
objectShader.setVec3("light.ambient", ambientColor);
objectShader.setVec3("light.diffuse", diffuseColor);
objectShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
你可以看到,不同的光照颜色能够极大地影响物体的最终颜色输出。由于光照颜色能够直接影响物体能够反射的颜色,这对视觉输出有着显著的影响。
全部代码
#include "mainwindow.h"
#include <QApplication>
//在包含GLFW的头文件之前包含了GLAD的头文件;
//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h);
//所以需要在其它依赖于OpenGL的头文件之前包含GLAD;
#include <glad/glad.h>
#include <GLFW/glfw3.h>
//GLM
//#include <glm/glm.hpp>
//#include <glm/gtc/matrix_transform.hpp>
//#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <QDebug>
#include "shader.h"
#include "stb_image.h"
#include "camera.h"
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
// settings
const unsigned int SCR_WIDTH = 1920;
const unsigned int SCR_HEIGHT = 1080;
//Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = SCR_WIDTH / 2.0;
float lastY = SCR_HEIGHT / 2.0;
bool firstMouse = true;
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
//Light
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
using namespace std;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//MainWindow w;
//w.show();
//初始化GLFW
//--------------------
glfwInit();
//配置GLFW
//--------------------
//告诉GLFW使用的OpenGL本是3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
//告诉GLFW使用的是核心模式(Core-profile)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//创建一个新的OpenGL环境和窗口
//-----------------------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate(); //glfw销毁窗口和OpenGL环境,并释放资源
return -1;
}
//设置参数window中的窗口所关联的OpenGL环境为当前环境
//-----------------------------------
glfwMakeContextCurrent(window);
//设置窗口尺寸改变大小时的回调函数(窗口尺寸发送改变时会自动调用)
//-----------------------------------
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//设置鼠标事件的回调函数(鼠标移动时会自动调用)
//-----------------------------------
glfwSetCursorPosCallback(window, mouse_callback);
//设置鼠标滚轮事件的回调函数(鼠标滚轮移动时会自动调用)
//-----------------------------------
glfwSetScrollCallback(window, scroll_callback);
//告诉GLFW捕捉鼠标
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//glad加载系统相关的OpenGL函数指针
//---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
//开启深度测试
glEnable(GL_DEPTH_TEST);
Shader objectShader("C:/Qt_Pro/OpenGL_GLFW/shader/shader.vs","C:/Qt_Pro/OpenGL_GLFW/shader/shader.fs");
Shader lightShader("C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.vs","C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.fs");
//顶点数据
//---------------------------------------------------------------------
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, objectVAO;
glGenVertexArrays(1, &objectVAO); //创建顶点数组对象
glGenBuffers(1, &VBO); //创建顶点缓冲对象
glBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //将顶点数据复制到GL_ARRAY_BUFFER缓冲区,之后可通过VBO进行操作
glBindVertexArray(objectVAO); //绑定VAO
//设定顶点属性指针
//位置属性
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);
//光源(VBO用上面的)
//----------------------------------------------------------------
unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO); //创建顶点数组对象
glBindVertexArray(lightVAO); //绑定VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//渲染循环
//我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口;
//我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入;
//因此,我们需要在程序中添加一个while循环,它能在我们让GLFW退出前一直保持运行;
//------------------------------------------------------------------------------
while (!glfwWindowShouldClose(window)) //如果用户准备关闭参数window所指定的窗口,那么此接口将会返回GL_TRUE,否则将会返回GL_FALSE
{
//更新时间差
float currentFrame = static_cast<float>(glfwGetTime());
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
//用户输入
//------------------------------------------------------------------------------
processInput(window); //检测是否有输入
//渲染指令
//------------------------------------------------------------------------------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//被投光物体
//============================================================
//激活着色器程序对象
objectShader.use();
objectShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
objectShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
objectShader.setVec3("lightPos", lightPos);
objectShader.setVec3("viewPos", camera.Position);
//光照强度
glm::vec3 lightColor;
lightColor.x = static_cast<float>(sin(glfwGetTime() * 2.0));
lightColor.y = static_cast<float>(sin(glfwGetTime() * 0.7));
lightColor.z = static_cast<float>(sin(glfwGetTime() * 1.3));
glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f); // decrease the influence
glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // low influence
objectShader.setVec3("light.ambient", ambientColor);
objectShader.setVec3("light.diffuse", diffuseColor);
objectShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
//材质
objectShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
objectShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f);
objectShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
objectShader.setFloat("material.shininess", 32.0f);
//创建变换矩阵
glm::mat4 model = glm::mat4(1.0f);
objectShader.setMat4("model", model);
glm::mat4 view = camera.GetViewMatrix();
objectShader.setMat4("view", view);
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
objectShader.setMat4("projection", projection);
//绘制三角形
glBindVertexArray(objectVAO); //绑定VAO
glDrawArrays(GL_TRIANGLES, 0, 36);
//============================================================
//光源
//============================================================
//激活着色器程序对象
lightShader.use();
lightShader.setMat4("view", view);
lightShader.setMat4("projection", projection);
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f));
lightShader.setMat4("model", model);
//绘制三角形
glBindVertexArray(lightVAO); //绑定VAO
glDrawArrays(GL_TRIANGLES, 0, 36);
//============================================================
//告诉GLFW检查所有等待处理的事件和消息,包括操作系统和窗口系统中应当处理的消息。如果有消息正在等待,它会先处理这些消息再返回;否则该函数会立即返回
//---------------------------------------------------------------------------------------------------------------------------------
glfwPollEvents();
//请求窗口系统将参数window关联的后缓存画面呈现给用户(双缓冲绘图)
//------------------------------------------------------------------------------
glfwSwapBuffers(window);
}
//释放资源
glDeleteVertexArrays(1, &objectVAO);
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &VBO);
//glDeleteProgram(objectShader);
//glDeleteProgram(lightShader);
//glfw销毁窗口和OpenGL环境,并释放资源(之后必须再次调用glfwInit()才能使用大多数GLFW函数)
//------------------------------------------------------------------
glfwTerminate();
return a.exec();
}
//检测是否有输入
//---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) //ESC键,退出
glfwSetWindowShouldClose(window, true);
float cameraSpeed = static_cast<float>(2.5 * deltaTime);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(RIGHT, deltaTime);
}
//给glfw窗口注册的尺寸改变回调函数
//---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//确保视口匹配新的窗口尺寸,请注意:宽度和高度将比视网膜显示器上指定的大得多
glViewport(0, 0, width, height);
}
// 鼠标移动时的回调函数
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
float xpos = static_cast<float>(xposIn);
float ypos = static_cast<float>(yposIn);
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = lastX - xpos;
float yoffset = ypos - lastY; //翻转,因为Y轴是从下到上越来越大
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
//鼠标滚轮的回调函数
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(static_cast<float>(yoffset));
}