![](https://img-blog.csdnimg.cn/img_convert/d55a5e0ae14ace1a6c82989452576021.jpeg)
开始学习!!;
文档地址:https://learnopengl-cn.github.io/01%20Getting%20started/10%20Review/
颜色
本节我们将会更深入地讨论什么是颜色,并且还会为接下来的光照(Lighting)教程创建一个场景。
现实世界中有无数种颜色,每一个物体都有它们自己的颜色。我们需要使用(有限的)数值来模拟真实世界中(无限)的颜色,所以并不是所有现实世界中的颜色都可以用数值来表示的。然而我们仍能通过数值来表现出非常多的颜色,甚至你可能都不会注意到与现实的颜色有任何的差异。颜色可以数字化的由红色(Red)、绿色(Green)和蓝色(Blue)三个分量组成,它们通常被缩写为RGB。仅仅用这三个值就可以组合出任意一种颜色。例如,要获取一个珊瑚红(Coral)色的话,我们可以定义这样的一个颜色向量:
glm::vec3 coral(1.0f, 0.5f, 0.31f);//珊瑚红;
我们在现实生活中看到某一物体的颜色并不是这个物体真正拥有的颜色,而是它所反射的(Reflected)颜色。换句话说,那些不能被物体所吸收(Absorb)的颜色(被拒绝的颜色)就是我们能够感知到的物体的颜色。例如,太阳光能被看见的白光其实是由许多不同的颜色组合而成的(如下图所示)。如果我们将白光照在一个蓝色的玩具上,这个蓝色的玩具会吸收白光中除了蓝色以外的所有子颜色,不被吸收的蓝色光被反射到我们的眼中,让这个玩具看起来是蓝色的。下图显示的是一个珊瑚红的玩具,它以不同强度反射了多个颜色。
![](https://img-blog.csdnimg.cn/img_convert/77b237091153764cb54142b0d3b6966e.png)
你可以看到,白色的阳光实际上是所有可见颜色的集合,物体吸收了其中的大部分颜色。它仅反射了代表物体颜色的部分,被反射颜色的组合就是我们所感知到的颜色(此例中为珊瑚红)。
这些颜色反射的定律被直接地运用在图形领域。当我们在OpenGL中创建一个光源时,我们希望给光源一个颜色。在上一段中我们有一个白色的太阳,所以我们也将光源设置为白色。当我们把光源的颜色与物体的颜色值相乘,所得到的就是这个物体所反射的颜色(也就是我们所感知到的颜色)。让我们再次审视我们的玩具(这一次它还是珊瑚红),看看如何在图形学中计算出它的反射颜色。我们将这两个颜色向量作分量相乘,结果就是最终的颜色向量了:
glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);
我们可以看到玩具的颜色吸收了白色光源中很大一部分的颜色,但它根据自身的颜色值对红、绿、蓝三个分量都做出了一定的反射。这也表现了现实中颜色的工作原理。由此,我们可以定义物体的颜色为物体从一个光源反射各个颜色分量的大小。现在,如果我们使用绿色的光源又会发生什么呢?
glm::vec3 lightColor(0.0f, 1.0f, 0.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.0f, 0.5f, 0.0f);
可以看到,并没有红色和蓝色的光让我们的玩具来吸收或反射。这个玩具吸收了光线中一半的绿色值,但仍然也反射了一半的绿色值。玩具现在看上去是深绿色(Dark-greenish)的。我们可以看到,如果我们用绿色光源来照射玩具,那么只有绿色分量能被反射和感知到,红色和蓝色都不能被我们所感知到。这样做的结果是,一个珊瑚红的玩具突然变成了深绿色物体。现在我们来看另一个例子,使用深橄榄绿色(Dark olive-green)的光源:
glm::vec3 lightColor(0.33f, 0.42f, 0.18f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.33f, 0.21f, 0.06f);
可以看到,我们可以使用不同的光源颜色来让物体显现出意想不到的颜色。有创意地利用颜色其实并不难。
这些颜色的理论已经足够了,下面我们来构造一个实验用的场景吧。
创建一个场景
在接下来的教程中,我们将会广泛地使用颜色来模拟现实世界中的光照效果,创造出一些有趣的视觉效果。由于我们现在将会使用光源了,我们希望将它们显示为可见的物体,并在场景中至少加入一个物体来测试模拟光照的效果。
首先我们需要一个物体来作为被投光(Cast the light)的对象,我们将使用前面教程中的那个著名的立方体箱子。我们还需要一个物体来代表光源在3D场景中的位置。简单起见,我们依然使用一个立方体来代表光源(我们已拥有立方体的顶点数据是吧?)。
填一个顶点缓冲对象(VBO),设定一下顶点属性指针和其它一些乱七八糟的东西现在对你来说应该很容易了,所以我们就不再赘述那些步骤了。如果你仍然觉得这很困难,我建议你复习之前的教程,并且在继续学习之前先把练习过一遍。
我们首先需要一个顶点着色器来绘制箱子。与之前的顶点着色器相比,容器的顶点位置是保持不变的(虽然这一次我们不需要纹理坐标了),因此顶点着色器中没有新的代码。我们将会使用之前教程顶点着色器:
#version 330 core
layout(location = 0) in vec3 aPos;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
void main()
{
gl_Position = projection*view*model*vec4(aPos,1.0);
}
因为我们还要创建一个表示灯(光源)的立方体,所以我们还要为这个灯创建一个专门的VAO。
【这里不建议与其他物体共用一个VAO】;
unsigned int lightVAO;//创建一个灯的VAO顶点数组;
glGenVertexArrays(1,&lightVAO);
glBindVertexArray(lightVAO);
//这里只需要绑定VBO不用再次设置VBO的数据,因为箱子的VBO数据中已经包含了正确的立方体顶点数据
glBindBuffer(GL_ARRAY_BUFFER,VBO);//绑定VBO;
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
这段代码对你来说应该非常直观。现在我们已经创建了表示灯和被照物体箱子,我们只需要再定义一个片段着色器就行了:
#version 330 core
out vec4 FragColor;
uniform vec3 objectColor;
uniform vec3 lightColor;
voidmain(){
FragColor = vec4(lightColor * objectColor, 1.0);
}
这个片段着色器从uniform变量中接受物体的颜色和光源的颜色。正如本节一开始所讨论的那样,我们将光源的颜色和物体(反射的)颜色相乘。这个着色器理解起来应该很容易。我们把物体的颜色设置为之前提到的珊瑚红色,并把光源设置为白色。
// 在此之前不要忘记首先 use 对应的着色器程序(来设定uniform)
lightingShader.use();
lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
要注意的是,当我们修改顶点或者片段着色器后,灯的位置或颜色也会随之改变,这并不是我们想要的效果。我们不希望灯的颜色在接下来的教程中因光照计算的结果而受到影响,而是希望它能够与其它的计算分离。我们希望灯一直保持明亮,不受其它颜色变化的影响(这样它才更像是一个真实的光源)。
为了实现这个目标,我们需要为灯的绘制创建另外的一套着色器,从而能保证它能够在其它光照着色器发生改变的时候不受影响。顶点着色器与我们当前的顶点着色器是一样的,所以你可以直接把现在的顶点着色器用在灯上。灯的片段着色器给灯定义了一个不变的常量白色,保证了灯的颜色一直是亮的:
//这一个属于灯自己的一个片段着色器代码,结果就是输出的时候均为白光;
#version 330 core
out vec4 FragColor;
voidmain(){
FragColor = vec4(1.0); // 将向量的四个分量全部设置为1.0
}
当我们想要绘制我们的物体的时候,我们需要使用刚刚定义的光照着色器来绘制箱子(或者可能是其它的物体)。
当我们想要绘制灯的时候,我们会使用灯的着色器。在之后的教程里我们会逐步更新这个光照着色器,从而能够慢慢地实现更真实的效果。
使用这个灯立方体的主要目的是为了让我们知道光源在场景中的具体位置。我们通常在场景中定义一个光源的位置,但这只是一个位置,它并没有视觉意义。为了显示真正的灯,我们将表示光源的立方体绘制在与光源相同的位置。我们将使用我们为它新建的片段着色器来绘制它,让它一直处于白色的状态,不受场景中的光照影响。
我们声明一个全局vec3变量来表示光源在场景的世界空间坐标中的位置:
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
然后我们把灯位移到这里,然后将它缩小一点,让它不那么明显:
model = glm::mat4();
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f));
绘制灯立方体的代码应该与下面的类似:
lampShader.use();
// 设置模型、视图和投影矩阵uniform
...
// 绘制灯立方体对象
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
下面是我的main.cpp代码:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include<E:\OpenGl\练习1.1\3.3.shader_class\shader s.h>
//以下三行为glm的头文件代码;
#include <E:\OpenGl\glm\glm-master\glm\glm.hpp>
#include <E:\OpenGl\glm\glm-master\glm\gtc\matrix_transform.hpp>
#include <E:\OpenGl\glm\glm-master\glm\gtc\type_ptr.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include <E:/OpenGl/stb_image.h/stb-master/stb_image.h>//这两行代码加入了stb_image库;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_back(GLFWwindow* window, double xoffset, double yoffset);
//三个调整摄像机位置的全局变量;
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
float deltatime = 0.0f;//上一帧与这一帧的时间差;
float lastime = 0.0f;//上一帧的时间;
//用来存储上一帧鼠标的位置!,设置为屏幕中心;
float lastX = 400.0;
float lastY = 300.0;
//仰俯角和偏航角;
float pitch = 0.0f;
float yaw = -90.0f;//从下往上;
float fov = 45.0f;//视域角;
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);//声明一个光源,表示光源在空间中的位置;
int main()
{
//先进行初始化glfw;
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本设置为3;
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本设置为3;
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(800, 600, "MY OPENGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Fail to create a window" << std::endl;
glfwTerminate();//释放资源;
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//创建完告诉将上下文设置为进程上下文;
//以下两步用于摄像机操作中的设置,由于是窗口的操作,因此放在此处!!;
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//告诉GLFW隐藏光标并捕捉他;
glfwSetCursorPosCallback(window, mouse_callback);//此句代码当发生鼠标移动时,就会调用mouse_callback函数改变两个欧拉角,
//进而改变cameraFront方向向量,进而可以实现3D旋转;
//还要对视域角fov做出变化,可以进行放大缩小;
glfwSetScrollCallback(window, scroll_back);//当滚动鼠标滚轮的时候,我们就可以通过调用该函数来改变fov,进而改变透视投影矩阵,
//以此来进一步形成放大和缩小!!;
//对glad进行初始化;
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Fail to initnite glad" << std::endl;
return -1;
}
//引入着色器类,着色器被封装到了class Shader里面;
//这里要创建两个着色器程序,分别用在物体和光源上面;
Shader lightshader("2.1.shader.vs", "2.1.shader.fs");
Shader lightCubeshader("2.1.shader.vs.lightcolor", "2.1.shader.fs.lightcolor");//用于光源的着色器程序;
//位置以及纹理;
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO);//创建VAO;
glGenBuffers(1, &VBO);//创建VBO;
//glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//链接顶点属性;
//位置属性;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);//这里的步长为获得下一个属性值,应该右移六个长度的单位;
glEnableVertexAttribArray(0);
//这里先去除纹理,以后要加入;
//shader.useProgram();
//shader.setInt("Tex1", 0);//采样器1对应纹理单元0;
//shader.setInt("Tex2", 1);
glEnable(GL_DEPTH_TEST);//深度测试;
//下面要再创建一个lightVAO,用来存放光源Cube的顶点指针;
unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);//这里仅需要绑定VBO即可,数据在前面已经传入VBO;
//链接顶点属性;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);//启用顶点属性;
//准备引擎:
while (!glfwWindowShouldClose(window))
{
float currentime = glfwGetTime();
deltatime = currentime - lastime;
lastime = currentime;
//准备输入:
processInput(window);
//每一次更新缓冲颜色,之后继续画下一帧;
glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);//清除颜色缓冲单元;
glClear(GL_DEPTH_BUFFER_BIT);//清除深度缓存冲单元;
绑定两个纹理,要先激活没一个纹理单元;
//glActiveTexture(GL_TEXTURE0);
//glBindTexture(GL_TEXTURE_2D, texture1);//绑定这个纹理到当前激活的纹理单元;
//glActiveTexture(GL_TEXTURE1);//激活纹理单元1;
//glBindTexture(GL_TEXTURE_2D, texture2);
启用Shader对象shader的程序启动函数;
//创建三个变换矩阵;
//先初始化三个单位矩阵;
glm::mat4 projection = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 model = glm::mat4(1.0f);
//创建一个随时间旋转的矩阵:
lightshader.useProgram();//启用着色器程序;
//设置物体着色程序中的片段着色器中的objectcolor以及lightcolor;
glUniform3f(glGetUniformLocation(lightshader.ID, "objectColor"), 1.0f, 0.5f, 0.31f);
glUniform3f(glGetUniformLocation(lightshader.ID, "lightColor"), 1.0f, 1.0f, 1.0f);
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
projection = glm::perspective(glm::radians(fov), (float)800 / (float)600, 0.1f, 100.0f);
//将变换矩阵传入着色器;注意到由于变换随时发生,要每一次迭代都进行,因此要放在里面;
glUniformMatrix4fv(glGetUniformLocation(lightshader.ID, "model"), 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(glGetUniformLocation(lightshader.ID, "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(lightshader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glBindVertexArray(VAO);//绑定VAO;
glDrawArrays(GL_TRIANGLES, 0, 36);
lightCubeshader.useProgram();//启用另外一个着色器程序;画光源;
model = glm::mat4(1.0f);//重新初始化model矩阵;
model = glm::translate(model, lightPos);//物体移动到光源的位置;
model = glm::scale(model, glm::vec3(0.2f));//光源缩小;
//传入着色器;
glUniformMatrix4fv(glGetUniformLocation(lightCubeshader.ID, "model"), 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(glGetUniformLocation(lightCubeshader.ID, "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(lightCubeshader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
// 绘制灯立方体对象
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);//位置0,画36个点;
//更新缓冲;
glfwSwapBuffers(window);
//进行检查;
glfwPollEvents();
}
//结束要删除VAO,VBO,以及删除着色器程序;
glDeleteVertexArrays(1, &VAO);
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &VBO);
//glDeleteProgram(shaderProgram);//删除着色器程序;
//结束清楚资源;
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//如果按下的键为回车键;
glfwSetWindowShouldClose(window, true);
float cameraSpeed = 2.5f * deltatime;//移动速度;
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
cameraPos += cameraUp * cameraSpeed;
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
cameraPos -= cameraUp * cameraSpeed;
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
cameraPos -= cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
cameraPos += cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
}
bool firstMouse = true;
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
//计算鼠标距上一帧的偏移量。
//把偏移量添加到摄像机的俯仰角和偏航角中。
//对偏航角和俯仰角进行最大和最小值的限制。
//计算方向向量。
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;//否则每一次都会进行循环;
}
//1.计算鼠标距上一帧的偏移量。
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;//更新存储的上一帧的值;
float sensitivity = 0.1f;//设置灵敏度;
xoffset *= sensitivity;
yoffset *= sensitivity;
//2.把偏移量添加到摄像机的俯仰角和偏航角中。
pitch = pitch + yoffset;
yaw = yaw + xoffset;
//3.对偏航角和俯仰角进行最大和最小值的限制
if (pitch > 89.0f)
pitch = 89.0f;
if (pitch < -89.0f)
pitch = -89.0f;
//计算方向向量cameraFront;
glm::vec3 direction;
direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
cameraFront = glm::normalize(direction);
}
void scroll_back(GLFWwindow* window, double xoffset, double yoffset)
{
//我们要把fov限制在1.0到45.0之间!!;
if (fov >= 1.0f && fov <= 45.0f)
{
fov -= yoffset;
}
if (fov >= 45.0f)
{
fov = 45.0f;
}
if (fov <= 1.0f)
{
fov = 1.0f;
}
}
![](https://img-blog.csdnimg.cn/img_convert/23f54a34cc3183e65383ba3703a3e33d.png)
上图即为所得结果;
若加入多个物体,
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include<E:\OpenGl\练习1.1\3.3.shader_class\shader s.h>
//以下三行为glm的头文件代码;
#include <E:\OpenGl\glm\glm-master\glm\glm.hpp>
#include <E:\OpenGl\glm\glm-master\glm\gtc\matrix_transform.hpp>
#include <E:\OpenGl\glm\glm-master\glm\gtc\type_ptr.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include <E:/OpenGl/stb_image.h/stb-master/stb_image.h>//这两行代码加入了stb_image库;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_back(GLFWwindow* window, double xoffset, double yoffset);
//三个调整摄像机位置的全局变量;
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
float deltatime = 0.0f;//上一帧与这一帧的时间差;
float lastime = 0.0f;//上一帧的时间;
//用来存储上一帧鼠标的位置!,设置为屏幕中心;
float lastX = 400.0;
float lastY = 300.0;
//仰俯角和偏航角;
float pitch = 0.0f;
float yaw = -90.0f;//从下往上;
float fov = 45.0f;//视域角;
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);//声明一个光源,表示光源在空间中的位置;
int main()
{
//先进行初始化glfw;
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本设置为3;
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本设置为3;
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(800, 600, "MY OPENGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Fail to create a window" << std::endl;
glfwTerminate();//释放资源;
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//创建完告诉将上下文设置为进程上下文;
//以下两步用于摄像机操作中的设置,由于是窗口的操作,因此放在此处!!;
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//告诉GLFW隐藏光标并捕捉他;
glfwSetCursorPosCallback(window, mouse_callback);//此句代码当发生鼠标移动时,就会调用mouse_callback函数改变两个欧拉角,
//进而改变cameraFront方向向量,进而可以实现3D旋转;
//还要对视域角fov做出变化,可以进行放大缩小;
glfwSetScrollCallback(window, scroll_back);//当滚动鼠标滚轮的时候,我们就可以通过调用该函数来改变fov,进而改变透视投影矩阵,
//以此来进一步形成放大和缩小!!;
//对glad进行初始化;
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Fail to initnite glad" << std::endl;
return -1;
}
//引入着色器类,着色器被封装到了class Shader里面;
Shader lightshader("2.1.lightshader.vs", "2.1.lightshader.fs");
Shader lightCubeshader("2.1.lightCube.shader.vs", "2.1.lightCube.shader.fs");
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO);//创建VAO;
glGenBuffers(1, &VBO);//创建VBO;
//glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//链接顶点属性;
//位置属性;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);//这里的步长为获得下一个属性值,应该右移六个长度的单位;
glEnableVertexAttribArray(0);
//纹理属性;
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);//启用纹理属性;
//加入纹理1:
unsigned int texture1;
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
//修改纹理的环绕和过滤方式;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
//加载入纹理:
stbi_set_flip_vertically_on_load(true);//防止上下颠倒;
int width, height, nrchannels;
unsigned char* data = stbi_load("E:/OpenGl/textures/v2-6e8b14becd4699a1e02421670e25ec74_r.jpg", &width, &height, &nrchannels, 0);
//生成纹理;
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Fail to load a image" << std::endl;
}
stbi_image_free(data);
//加入纹理2:
unsigned int texture2;
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
//修改纹理的环绕和过滤方式;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
//加载入纹理:
stbi_set_flip_vertically_on_load(true);//防止上下颠倒;
int width1, height1, nrchannels1;
unsigned char* data2 = stbi_load("E:/OpenGl/textures/2e3c98d3bf204d029be74443398e0c87.jpeg", &width1, &height1, &nrchannels1, 0);
//生成纹理;
if (data2)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width1, height1, 0, GL_RGB, GL_UNSIGNED_BYTE, data2);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Fail to load a image" << std::endl;
}
stbi_image_free(data2);
//还要指定纹理单元对应的采样器;
lightshader.useProgram();
lightshader.setInt("Tex1", 0);//采样器1对应纹理单元0;
lightshader.setInt("Tex2", 1);
glEnable(GL_DEPTH_TEST);
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
/*glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)*/
};
unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
//准备引擎:
while (!glfwWindowShouldClose(window))
{
float currentime = glfwGetTime();
deltatime = currentime - lastime;
lastime = currentime;
//准备输入:
processInput(window);
//每一次更新缓冲颜色,之后继续画下一帧;
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);//清除颜色缓冲单元;
glClear(GL_DEPTH_BUFFER_BIT);
//绑定两个纹理,要先激活没一个纹理单元;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);//绑定这个纹理到当前激活的纹理单元;
glActiveTexture(GL_TEXTURE1);//激活纹理单元1;
glBindTexture(GL_TEXTURE_2D, texture2);
//先初始化三个单位矩阵;
glm::mat4 projection = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 model = glm::mat4(1.0f);
float angle = 20.0f;
//进行4次循环;
for (int i = 0; i < 4; i++)
{
lightshader.useProgram();//启用着色器!!!;
model = glm::translate(model, cubePositions[i]);
model = glm::rotate(model, glm::radians(angle * i), glm::vec3(0.3f, 0.4f, 0.2f));
glUniformMatrix4fv(glGetUniformLocation(lightshader.ID, "model"), 1, GL_FALSE, glm::value_ptr(model));
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
glUniformMatrix4fv(glGetUniformLocation(lightshader.ID, "view"), 1, GL_FALSE, glm::value_ptr(view));
projection = glm::perspective(glm::radians(fov), (float)800 / (float)600, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(lightshader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
//用于计算传到我们眼睛的光照强度;
glUniform3f(glGetUniformLocation(lightshader.ID, "objectColor"), 1.0f, 0.5f, 0.31f);
glUniform3f(glGetUniformLocation(lightshader.ID, "lightColor"), 1.0f, 1.0f, 1.0f);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
lightCubeshader.useProgram();//启用着色器!!!;
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f));//缩小
glUniformMatrix4fv(glGetUniformLocation(lightCubeshader.ID, "model"), 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(glGetUniformLocation(lightCubeshader.ID, "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(lightCubeshader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
//开始绘制;
//glDrawArrays(GL_TRIANGLES, 0, 3);//第一个参数是要进行绘制的类型,第二个参数制定了顶点数组的开始索引,
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//注意:6是指一个要绘制六个顶点;!!
//第三个参数是要进行绘制的顶点的长度;
//更新缓冲;
glfwSwapBuffers(window);
//进行检查;
glfwPollEvents();
}
//结束要删除VAO,VBO,以及删除着色器程序;
glDeleteVertexArrays(1, &VAO);
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &VBO);
//glDeleteProgram(shaderProgram);//删除着色器程序;
//结束清楚资源;
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//如果按下的键为回车键;
glfwSetWindowShouldClose(window, true);
float cameraSpeed = 2.5f * deltatime;//移动速度;
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
cameraPos += cameraUp * cameraSpeed;
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
cameraPos -= cameraUp * cameraSpeed;
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
cameraPos -= cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
cameraPos += cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
}
bool firstMouse = true;
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
//计算鼠标距上一帧的偏移量。
//把偏移量添加到摄像机的俯仰角和偏航角中。
//对偏航角和俯仰角进行最大和最小值的限制。
//计算方向向量。
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;//否则每一次都会进行循环;
}
//1.计算鼠标距上一帧的偏移量。
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;//更新存储的上一帧的值;
float sensitivity = 0.1f;//设置灵敏度;
xoffset *= sensitivity;
yoffset *= sensitivity;
//2.把偏移量添加到摄像机的俯仰角和偏航角中。
pitch = pitch + yoffset;
yaw = yaw + xoffset;
//3.对偏航角和俯仰角进行最大和最小值的限制
if (pitch > 89.0f)
pitch = 89.0f;
if (pitch < -89.0f)
pitch = -89.0f;
//计算方向向量。
glm::vec3 direction;
direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
cameraFront = glm::normalize(direction);
}
void scroll_back(GLFWwindow* window, double xoffset, double yoffset)
{
//我们要把fov限制在1.0到45.0之间!!;
if (fov >= 1.0f && fov <= 45.0f)
{
fov -= yoffset;
}
if (fov >= 45.0f)
{
fov = 45.0f;
}
if (fov <= 1.0f)
{
fov = 1.0f;
}
}
则有以下结果:
![](https://img-blog.csdnimg.cn/img_convert/e7faf55ac919d5de642a597a6492a5ca.png)
![](https://img-blog.csdnimg.cn/img_convert/462f5c21cdfbb6c37e3bf4499a28a763.webp?x-oss-process=image/format,png)
结束.