转自:https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/
https://zhuanlan.zhihu.com/p/108670094
本文为学习LearnOpenGL的学习笔记,如有书写和理解错误还请大佬扶正;
教程链接:
坐标系统 - LearnOpenGL CNlearnopengl-cn.github.io
一,基础概念
1,要达到的效果
- OpenGL在每次顶点着色器运行后,所有可见的顶点都应该在标准化设备坐标(Normalized Device Coordinate, NDC)之内。也就是说,每个顶点的x,y,z坐标都应该在-1.0到1.0之间,超出这个坐标范围的顶点都将不可见;
- 通常我们都会自己设定一个坐标的范围,之后再在顶点着色器中将这些坐标变换为标准化设备坐标。然后将这些标准化设备坐标传入光栅器(Rasterizer),将它们变换为屏幕上的二维坐标或像素;
- 顶点从设定范围到传入光栅器经历五个不同的坐标系统;
2,顶点变换的五个不同的坐标系统
- 局部空间(Local Space,或者称为物体空间(Object Space))
- 世界空间(World Space)
- 观察空间(View Space,或者称为视觉空间(Eye Space))
- 裁剪空间(Clip Space)
- 屏幕空间(Screen Space)
变化流程
为了将坐标从一个坐标系变换到另一个坐标系,需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。
3,局部/物体空间
局部空间是指物体所在的坐标空间,即对象最开始所在的地方,在建模软件中的坐标;
4,世界空间
是指顶点相对于(游戏)世界的坐标。物体的坐标从局部变换到世界空间;该变换是由模型矩阵(Model Matrix)实现的;
5,观察空间/摄像机空间
观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。这通常是由一系列的位移和旋转的组合来完成,平移/旋转场景从而使得特定的对象被变换到摄像机的前方。这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里,它被用来将世界坐标变换到观察空间;
6,裁剪空间
在一个顶点着色器运行的最后,OpenGL期望所有的坐标都能落在一个特定的范围内,且任何在这个范围之外的点都应该被裁剪掉(Clipped)。被裁剪掉的坐标就会被忽略,所以剩下的坐标就将变为屏幕上可见的片段。这也就是裁剪空间(Clip Space)名字的由来;
因为将所有可见的坐标都指定在-1.0到1.0的范围内不是很直观,所以我们会指定自己的坐标集(Coordinate Set)并将它变换到标准化设备坐标系;
如果只是图元(Primitive),例如三角形,的一部分超出了裁剪体积(Clipping Volume),则OpenGL会重新构建这个三角形为一个或多个三角形让其能够适合这个裁剪范围。
7,投影矩阵(Projection Matrix)
为了将顶点坐标从观察变换到裁剪空间,我们需要定义一个投影矩阵(Projection Matrix),它指定了一个范围的坐标;比如在每个维度上的-1000到1000。投影矩阵接着会将在这个指定的范围内的坐标变换为标准化设备坐标的范围(-1.0, 1.0)。所有在范围外的坐标不会被映射到在-1.0到1.0的范围之间,所以会被裁剪掉;
将特定范围内的坐标转化到标准化设备坐标系的过程(而且它很容易被映射到2D观察空间坐标)被称之为投影(Projection);
将观察坐标变换为裁剪坐标的投影矩阵有两种不同的形式,每种形式都定义了不同的平截头体。我们可以选择创建一个正射投影矩阵(Orthographic Projection Matrix)或一个透视投影矩阵(Perspective Projection Matrix);
8,正交投影
正射投影矩阵定义了一个类似立方体的平截头箱,它定义了一个裁剪空间,在这空间之外的顶点都会被裁剪掉。创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在使用正射投影矩阵变换至裁剪空间之后处于这个平截头体内的所有坐标将不会被裁剪掉。它的平截头体看起来像一个容器:
它由由宽、高、近(Near)平面和远(Far)平面所指定。任何出现在近平面之前或远平面之后的坐标都会被裁剪掉。正射平截头体直接将平截头体内部的所有坐标映射为标准化设备坐标,因为每个向量的w分量都没有进行改变;(如果w分量等于1.0,透视除法则不会改变这个坐标);
//创建一个正射投影矩阵,GLM的内置函数
//前两个参数指定了平截头体的左右坐标
//第三和第四参数指定了平截头体的底部和顶部
//第五和第六个参数则定义了近平面和远平面的距离
glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
9,透视投影
如果你曾经体验过实际生活给你带来的景象,你就会注意到离你越远的东西看起来更小。这个奇怪的效果称之为透视(Perspective);
透视投影投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-w到w的范围之间(任何大于这个范围的坐标都会被裁剪掉);
顶点坐标的每个分量都会除以它的w分量,距离观察者越远顶点坐标就会越小。这是也是w分量非常重要的另一个原因,它能够帮助我们进行透视投影。最后的结果坐标就是处于标准化设备空间中的;
//创建一个透视投影矩阵
//第一个参数定义了fov的值,它表示的是视野(Field of View),并且设置了观察空间的大小
//第二个参数设置了宽高比,由视口的宽除以高所得。
//第三和第四个参数设置了平截头体的近和远平面。我们通常设置近距离为0.1f
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
10,透视除法
一旦所有顶点被变换到裁剪空间,最终的操作——透视除法(Perspective Division)将会执行,在这个过程中我们将位置向量的x,y,z分量分别除以向量的齐次w分量;透视除法是将4D裁剪空间坐标变换为3D标准化设备坐标的过程。这一步会在每一个顶点着色器运行的最后被自动执行。
11,屏幕空间(Screen Space)
在透视除法这一阶段之后,最终的坐标将会被映射到屏幕空间中(使用glViewport中的设定),并被变换成片段。
12,组合矩阵变换
为上述的每一个步骤都创建了一个变换矩阵:模型矩阵、观察矩阵和投影矩阵。一个顶点坐标将会根据以下过程被变换到裁剪坐标:
矩阵运算的顺序是相反的(需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。
OpenGL然后对裁剪坐标执行透视除法从而将它们变换到标准化设备坐标。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(在我们的例子中是一个800x600的屏幕)。这个过程称为视口变换。
二,绘制立方体理论部分
1,创建模型矩阵,以变换顶点到世界空间
//初始化矩阵
glm::mat4 model = glm::mat4(1.0f);
//绕着x轴旋转
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
2,创建观察矩阵,设置一个定点的摄像机位置
想要在场景里面稍微往后移动,以使得更多的物体变成可见的,由于将摄像机向后移动,和将整个场景向前移动是一样的,所以观察矩阵代码为
glm::mat4 view = glm::mat4(1.0f);
// 将矩阵向我们要进行移动场景的反方向移动
//右手坐标系 指向屏幕的方向为-Z 让物体向前移动3的距离,相当于摄像机向后移动3
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
3,创建透视投影矩阵
glm::mat4 projection = glm::mat4(1.0f);
projection = glm::perspective(glm::radians(45.0f), screenWidth / screenHeight, 0.1f, 100.0f);
4,在着色器中声明变化矩阵,然后从渲染逻辑端传入
#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);
...
}
将矩阵传入着色器(这通常在每次的渲染迭代中进行,因为变换矩阵会经常变动)
int modelLoc = glGetUniformLocation(ourShader.ID, "model"));
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
... // 观察矩阵和投影矩阵与之类似
5,Z缓冲
OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中,也被称为深度缓冲(Depth Buffer)。GLFW会自动为你生成这样一个缓冲(就像它也有一个颜色缓冲来存储输出图像的颜色)。深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。
如果我们想要确定OpenGL真的执行了深度测试,首先我们要告诉OpenGL我们想要启用深度测试;它默认是关闭的。我们可以通过glEnable函数来开启深度测试。glEnable和glDisable函数允许我们启用或禁用某个OpenGL功能。这个功能会一直保持启用/禁用状态,直到另一个调用来禁用/启用它。
//用深度测试
glEnable(GL_DEPTH_TEST);
...
//在每次渲染迭代之前清除深度缓冲(否则前一帧的深度信息仍然保存在缓冲中)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
三,绘制立方体实践部分
实现不同的纹理设置可以用一个纹理单元,然后每次渲染图形时候传入不一样的纹理图像即可
预览效果
修改ShderBase.h文件添加更多的处理uniform变量类型的函数
void setBool(const std::string &name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
// ------------------------------------------------------------------------
void setInt(const std::string &name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void setFloat(const std::string &name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
void setVec2(const std::string &name, const glm::vec2 &value) const
{
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec2(const std::string &name, float x, float y) const
{
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}
void setVec3(const std::string &name, const glm::vec3 &value) const
{
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec3(const std::string &name, float x, float y, float z) const
{
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}
void setVec4(const std::string &name, const glm::vec4 &value) const
{
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec4(const std::string &name, float x, float y, float z, float w)
{
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}
void setMat2(const std::string &name, const glm::mat2 &mat) const
{
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat3(const std::string &name, const glm::mat3 &mat) const
{
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat4(const std::string &name, const glm::mat4 &mat) const
{
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
void setMat4(const std::string &name, glm::mat4 mat)
{
int matLoc = glGetUniformLocation(ID, name.c_str());
glUniformMatrix4fv(matLoc, 1, GL_FALSE, glm::value_ptr(mat));
}
更改渲染逻辑(LearnOpenGL.cpp文件)
//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 "ShaderBase.h" //基础的渲染着色器
//通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h" //图片处理
#include <iostream>
using namespace std;
/********************************************************定义常量********************************************************/
//设置窗口的宽和高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
float mixValue = 0.2f; //图片混合控制参数 KEY UP/DOWN 控制
/********************************************************函数********************************************************/
//响应键盘输入事件
void processInput(GLFWwindow* window)
{
//ESC 退出窗口
//glfwGetKey()用来判断一个键是否按下。第一个参数是GLFW窗口句柄,第二个参数是一个GLFW常量,代表一个键。
//GLFW_KEY_ESCAPE表示Esc键。如果Esc键按下了,glfwGetKey将返回GLFW_PRESS(值为1),否则返回GLFW_RELEASE(值为0)。
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
//glfwSetWindowShouldClose()函数,为窗口设置关闭标志。第一个参数是窗口句柄,第二个参数表示是否关闭
//这里为GLFW_TRUE,表示关闭该窗口。
//注意,这时窗口不会立即被关闭,但是glfwWindowShouldClose()将返回GLFW_TRUE,到了glfwTerminate()就会关闭窗口。
glfwSetWindowShouldClose(window, true);
}
//上键
if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
{
mixValue += 0.01f;
if (mixValue >= 1.0f)
mixValue = 1.0f;
}
//下键
if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
{
mixValue -= 0.01f;
if (mixValue <= 0.0f)
mixValue = 0.0f;
}
}
//当用户改变窗口的大小的时候,视口也应该被调整。
//对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//OpenGL渲染窗口的尺寸大小
//glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)
glViewport(0, 0, width, height);
}
/********************************************************主函数********************************************************/
//主函数
int main()
{
//初始化GLFW
glfwInit();
//声明版本与核心
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//创建窗口并设置其大小,名称,与检测是否创建成功
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
cout << "Failed to create GLFW window" << endl;
glfwTerminate();
return -1;
}
//创建完毕之后,需要让当前窗口的环境在当前线程上成为当前环境,就是接下来的画图都会画在我们刚刚创建的窗口上
glfwMakeContextCurrent(window);
//告诉GLFW我们希望每当窗口调整大小的时候调用这个函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//glad寻找opengl的函数地址,调用opengl的函数前需要初始化glad
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
/********************************************************着色器设置********************************************************/
//启用深度测试,默认是关闭的
//GLFW会自动生成Z-buffer 深度缓冲
glEnable(GL_DEPTH_TEST);
//构建和编译Shader 读取着色器路径
Shader ourShader01("vs01.vs", "fs01.fs");
Shader ourShader02("vs02.vs", "fs02.fs");
/********************************************************顶点数据********************************************************/
//设置顶点数据和顶点属性
//立方体 6个面 每个面2个三角形 一个三角形3个点 6*3*2 36个顶点
float vertices[] = {
// 位置 顶点颜色 UV
-0.5f, -0.5f, -0.5f, 0.0f,0.0f,1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f,0.0f,1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f,0.0f,1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f,0.0f,1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f,0.0f,1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f,0.0f,1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 1.0f,0.0f,0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f,0.0f,0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f,0.0f,0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f,0.0f,0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 1.0f,0.0f,0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 1.0f,0.0f,0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f,1.0f,0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f,1.0f,0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f,1.0f,0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f,1.0f,0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f,1.0f,0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f,1.0f,0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f,1.0f,0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f,1.0f,0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f,1.0f,0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f,1.0f,0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f,1.0f,0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f,1.0f,0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f,1.0f,1.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f,1.0f,1.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f,1.0f,1.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f,1.0f,1.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f,1.0f,1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f,1.0f,1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 1.0f,1.0f,1.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f,1.0f,1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f,1.0f,1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f,1.0f,1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f,1.0f,1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f,1.0f,1.0f, 0.0f, 1.0f
};
//设置立方体的世界空间的位置 通过不同的世界空间矩阵设置
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)
};
/********************************************************VAO/VBO/EBO********************************************************/
//创建 VBO 顶点缓冲对象 VAO顶点数组对象 EBO索引缓冲对象
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
//绑定VAO,VBO与EBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 复制顶点数据到缓冲内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//链接顶点属性,设置顶点属性指针
//顶点位置 0 vec3
属性位置值为0的顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//顶点颜色 1 vec3
//属性位置值为1的顶点属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
//以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。
glEnableVertexAttribArray(1);
//顶点UV坐标 2 vec2
//属性位置值为2的顶点属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
/********************************************************纹理对象********************************************************/
//生成纹理
//创建ID
unsigned int texture1, texture2, texture3;
/********************************************************第一个纹理对象********************************************************/
//创建纹理对象
//glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中
glGenTextures(1, &texture1);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, texture1);
//设置纹理的环绕方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//设置纹理的过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//声明变量 用来储存图片的宽度,高度和颜色通道个数
int width, height, nrChannels;
//因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部
//翻转y轴
stbi_set_flip_vertically_on_load(true);
//stbi_load()函数 载入图片数据
unsigned char *data = stbi_load(("resources/textures/container.jpg"), &width, &height, &nrChannels, 0);
//判断数据是否加载成功
if (data)
{
//利用载入图片数据,生成纹理
//当前绑定的纹理对象就会被附加上纹理图像
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
//为当前绑定的纹理自动生成所有需要的Mipmap(多级渐远纹理)
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
//删除加载的图片数据,释放内存
stbi_image_free(data);
/********************************************************第二个纹理对象********************************************************/
//创建纹理对象
//glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中
glGenTextures(1, &texture2);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, texture2);
//设置纹理的环绕方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//设置纹理的过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//声明变量 用来储存图片的宽度,高度和颜色通道个数
data = stbi_load(("resources/textures/awesomeface.png"), &width, &height, &nrChannels, 0);
//判断数据是否加载成功
if (data)
{
//利用载入图片数据,生成纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
//为当前绑定的纹理自动生成所有需要的Mipmap(多级渐远纹理)
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
//删除加载的图片数据,释放内存
stbi_image_free(data);
/********************************************************第三个纹理对象********************************************************/
//创建纹理对象
//glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中
glGenTextures(1, &texture3);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, texture3);
//设置纹理的环绕方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//设置纹理的过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部
//翻转y轴
stbi_set_flip_vertically_on_load(true);
//stbi_load()函数 载入图片数据
data = stbi_load(("resources/textures/wall.jpg"), &width, &height, &nrChannels, 0);
//判断数据是否加载成功
if (data)
{
//利用载入图片数据,生成纹理
//当前绑定的纹理对象就会被附加上纹理图像
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
//为当前绑定的纹理自动生成所有需要的Mipmap(多级渐远纹理)
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
//删除加载的图片数据,释放内存
stbi_image_free(data);
/********************************************************分配纹理单元********************************************************/
/********************************************************第一个着色器********************************************************/
//设置uniform前,激活着色器
ourShader01.use();
//glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。
//只需要设置一次即可,所以这个会放在渲染循环的前面
//手动设置
//glUniform1i(glGetUniformLocation(ourShader01.ID, "texture1"), 0);
//使用内置自定义函数
ourShader01.setInt("texture1", 0); //0 对应的GL_TEXTURE0
ourShader01.setInt("texture2", 1); //1 对应的GL_TEXTURE1
/********************************************************第二个着色器********************************************************/
ourShader02.use();
ourShader02.setInt("texture3", 2); //2 对应的GL_TEXTURE2
/********************************************************渲染循环********************************************************/
//程序可以一直运行,直到用户关闭窗口。这样我们就需要创建一个循环,叫做游戏循环
//glfwWindowShouldClose()检查窗口是否需要关闭。如果是,游戏循环就结束了,接下来我们将会清理资源,结束程序
while (!glfwWindowShouldClose(window))
{
//响应键盘输入
processInput(window);
//设置清除颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
//清除当前窗口,把颜色设置为清除颜色
//清除深度信息
glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT);
/********************************************************开始绘制********************************************************/
//获取时间
float timeValue = glfwGetTime();
//利用时间获取sin值(-1,1)转换成(0,1)
float sinTime = sin(timeValue) / 2.0f + 0.5f;
//激活链接程序,激活着色器,开始渲染
//在绑定纹理之前先激活纹理单元
glActiveTexture(GL_TEXTURE0);
//glBindTexture()函数调用,会绑定这个纹理到当前激活的纹理单元
//纹理单元GL_TEXTURE0默认总是被激活
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, texture3);
//创建MVP矩阵4X4 局部->世界->投影----裁剪空间
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 projection = glm::mat4(1.0f);
//MVP赋值
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
//绑定VAO
glBindVertexArray(VAO);
//循环绘制十个立方体在不一样的位置
for (unsigned int i = 0; i < 10; i++)
{
// 初始化局部->世界的矩阵
glm::mat4 model = glm::mat4(1.0f);
//赋值位置
model = glm::translate(model, cubePositions[i]);
//定义角度
float angle = 20.0f * i + (float)glfwGetTime() * 10;
//赋值旋转
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
//设置立方体的位置与旋转 根据上诉矩阵
//切换着色器
if (i % 2 == 0)
{
ourShader01.use(); //激活着色器链接程序
ourShader01.setFloat("YOffset", sinTime); //赋值着色器中的变量
ourShader01.setFloat("mixValue", mixValue); //赋值着色器的变量
//为着色器中的矩阵赋值
ourShader01.setMat4("view", view);
ourShader01.setMat4("projection", projection);
ourShader01.setMat4("model", model);
}
else
{
ourShader02.use();
ourShader02.setVec4("outColor", glm::vec4(sinTime));
//为着色器中的矩阵赋值
ourShader02.setMat4("view", view);
ourShader02.setMat4("projection", projection);
ourShader02.setMat4("model", model);
}
//绘制
glDrawArrays(GL_TRIANGLES, 0, 36);
}
//交换颜色缓冲
glfwSwapBuffers(window);
//处理事件
glfwPollEvents();
}
/********************************************************渲染循环结束********************************************************/
//解除绑定
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
//释放前面所申请的内存
glfwTerminate();
return 0;
}
vs01.vs代码为
#version 330 core
layout (location = 0) in vec3 aPos; //顶点位置
layout (location = 1) in vec3 aColor; //顶点颜色
layout (location = 2) in vec2 aTexCoord; //顶点的UV坐标
out vec3 ourColor; //输出颜色
out vec2 TexCoord; //输出顶点UV坐标
//out vec3 ourPosition;
uniform float YOffset; //声明一个float 变量
uniform mat4 model; //世界空间矩阵
uniform mat4 view; //摄像机矩阵
uniform mat4 projection; //投影矩阵
void main()
{
//赋值
gl_Position = projection * view * model * vec4(aPos.x, aPos.y, aPos.z, 1.0); //顶点由局部空间变换到裁剪空间
ourColor = aColor; //顶点颜色赋值
TexCoord = vec2(aTexCoord.x, aTexCoord.y); //顶点的UV坐标
}
fs01.fs代码为
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
//in vec3 ourPosition;
uniform sampler2D texture1; //声明一个贴图
uniform sampler2D texture2; //声明一个贴图
uniform float mixValue; //声明控制混合的变量
void main()
{
//采样贴图
//mix()函数 接受两个值作为参数,并对它们根据第三个参数进行线性插值
FragColor = mix(texture(texture1, TexCoord) * vec4(ourColor, 1.0), texture(texture2, vec2(TexCoord.x, TexCoord.y)), mixValue);
}
vs02.vs代码为
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor; //顶点颜色
layout (location = 2) in vec2 aTexCoord; //顶点的UV坐标
out vec3 ourPosition;
out vec3 ourColor; //输出颜色
out vec2 TexCoord; //输出顶点UV坐标
uniform float YOffset; //声明一个float 变量
uniform mat4 model; //世界空间矩阵
uniform mat4 view; //摄像机矩阵
uniform mat4 projection; //投影矩阵
void main()
{
//赋值
gl_Position = projection * view * model * vec4(aPos.x, aPos.y, aPos.z, 1.0); //顶点由局部空间变换到裁剪空间
ourColor = aColor; //顶点颜色赋值
TexCoord = vec2(aTexCoord.x, aTexCoord.y); //顶点的UV坐标
}
fs02.fs代码为
#version 330 core
out vec4 FragColor;
in vec3 ourPosition;
in vec2 TexCoord;
uniform vec4 outColor;
uniform sampler2D texture3; //声明一个贴图
void main()
{
vec4 tex = texture(texture3, TexCoord) * outColor;
FragColor = tex;
}