世界空间:世界坐标(0,0,0),模型矩阵转换
观察空间:观察矩阵
剪裁空间:投影矩阵,
平截头体Frustum,由投影矩阵创建的观察区域,
被转化到剪裁空间后,执行透视划分:
在这个过程中我们将位置向量的x,y,z分量分别除以向量的齐次w分量;
透视划分是将4维裁剪空间坐标转换为3维标准化设备坐标。
这一步会在每一个顶点着色器运行的最后被自动执行。
投影矩阵将观察坐标转换为裁剪坐标过程有两种不同方式。
正射投影和透视投影。
平截头体定义了由宽、高、近平面和远平面决定的可视的坐标系:
glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
左右 底上 近远 定义参数。 没有透视perspective
透视投影:
通过调节w分量
创建一个透视投影矩阵:
glm::mat4 proj = glm::perspective(45.0f, (float)width/(float)height, 0.1f, 100.0f);
定义了可视空间大的平截头体,
第一参数是视野field of view,一般45 或者更大
然后是宽高比和近远平面。
我们经常设置近距离为0.1而远距离设为100.0。所有在近平面和远平面的顶点且处于平截头体内的顶点都会被渲染。
当你把透视矩阵的near值设置太大时(如10.0),OpenGL会将靠近摄像机的坐标都裁剪掉(在0.0和10.0之间),
这会导致一个你很熟悉的视觉效果:在太过靠近一个物体的时候视线会直接穿过去。
每一个步骤都创建了一个转换矩阵:模型矩阵、观察矩阵和投影矩阵。一个顶点的坐标将会根据以下过程被转换到裁剪坐标:
注意每个矩阵被运算的顺序是相反的(记住我们需要从右往左乘上每个矩阵)。最后的顶点应该被赋予顶点着色器中的gl_Position且OpenGL将会自动进行透视划分和裁剪。
通过矩阵转换,把顶点放置到剪裁空间内,
然后OpenGL执行行透视分化,转换到标准设备坐标。
OpenGL使用glViewPort内部参数将标准化设备坐标映射到屏幕坐标。
每个坐标关联屏幕上的点,称为视口转换。
进入3D:
先创建模型矩阵,用该矩阵转换到全局世界空间,
通过将顶点坐标乘以这个模型矩阵我们将该顶点坐标转换到世界坐标。
glm::mat4 model;
model = glm::rotate(model, -55.0f, glm::vec3(1.0f, 0.0f, 0.0f));
然后创建观察矩阵,:
OpenGL是一个右手坐标系(Right-handed System)
为了理解为什么被称为右手坐标系,按如下的步骤做:
张开你的右手使正y轴沿着你的手往上。
使你的大拇指往右。
使你的食指往上。
向下90度弯曲你的中指。
进入3D:
//坐标系统
glm::mat4 model;
model = glm::rotate(model, -55.0f, glm::vec3(1.0f, 0.0f, 0.0f));
//我们将矩阵向我们要进行移动场景的反向移动。,可以把矩阵看摄像机
glm::mat4 view;
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
//然后是透视投影矩阵。
glm::mat4 projection;
projection = glm::perspective(45.0f, screenWidth / screenHeight, 0.1f, 100.f);
所以,模型矩阵、观察矩阵、投影矩阵都弄好后 。
将他们传入着色器
:
#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
layout(location = 2) in vec2 texCoord;
out vec3 ourColor;
out vec2 TexCoord;
uniform mat4 transform;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
// 注意从右向左读
gl_Position = projection * view * model * vec4(position, 1.0f);
ourColor = color;
TexCoord = vec2(1.0f-texCoord.x, 1.0f-texCoord.y);
}
整体需要添加的代码:
//坐标系统
glm::mat4 model;
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
//我们将矩阵向我们要进行移动场景的反向移动。,可以把矩阵看摄像机
glm::mat4 view;
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
//然后是透视投影矩阵。
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.f);
GLint modelLoc = glGetUniformLocation(ourShader.Program,"model");
GLint viewLoc = glGetUniformLocation(ourShader.Program, "view");
GLint projLoc = glGetUniformLocation(ourShader.Program, "projection");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
写在循环里还是循环外应该是一样的。
再往后面是立方体:
画出了这个:
这有问题,不知道后面写的为什么没了。??
那简单写下:
最后这个旋转 4个正方体旋转。
代码:
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
#include <iostream>
#include "Shader.h"
#include <SOIL.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
void key_callback(GLFWwindow* windows, int key, int scancode, int action, int mode);
//顶点着色器源码:
const GLuint WIDTH = 800, HEIGHT = 600;
float Offset = 0.1;
int main()
{
/*
//此部分写变换
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << std::endl;
//然后是旋转和缩放箱子
glm::mat4 trans;
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
//因为我们把这个矩阵传递给了GLM的每个函数,GLM会自动将矩阵相乘
//返回的结果是一个包括了多个变换的变换矩阵。
//转换弧度glm::radians(90.0f)将角度转换为弧度。
*/
std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;
//这都是初始化GLFW,后面的要看文档
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
//创建窗口,
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);//通知GLFW将我们窗口的上下文设置为当前线程的主上下文
//不知道回调函数为什么要加在这里??
//glfwSetKeyCallback(window, key_callback2);回调函数只有最下面那个有作用
glfwSetKeyCallback(window, key_callback);
//初始化GLEW(管理OpenGL的函数指针)
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
glViewport(0, 0, WIDTH, HEIGHT);//前两个控制左下角的位置,后面是窗口宽度高度(像素)
//开启深度测试
glEnable(GL_DEPTH_TEST);
Shader ourShader("shader.vs", "shader.frag");
//这里省略了颜色值,省的就是位置坐标和纹理坐标
GLfloat 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
};
GLuint indices[] = { // Note that we start from 0!
0, 1, 3, // First Triangle
1, 2, 3 // Second Triangle
};
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)
};
GLuint VBO, VAO,EBO;
//使用glGenBuffers函数和一个缓冲ID生成一个VBO对象??
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &EBO);
//绑定VAO
glBindVertexArray(VAO);
//复制顶点数组到缓冲中,glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数
glBindBuffer(GL_ARRAY_BUFFER, VBO);//glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
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(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
//因为现在是两个属性,需要向右移动步长,6个
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
//由于我们添加了额外的顶点属性,我们要告诉OPenGL新的顶点格式。
//生成纹理
GLuint texture1,texture2;
glGenTextures(1, &texture1);//??
//glGenTextures(1, &texture2);
//glGenTextures函数首先需要输入生成纹理的数量,然后储存在GLuint数组中,让之前任何纹理指令都可以配置当前绑定的纹理。
//下面这一句是绑定纹理。
//glActiveTexture(GL_TEXTURE0);//多个位置时,使用前需要先激活
glBindTexture(GL_TEXTURE_2D, texture1);
//glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);
//同理设置第二个纹理位置
//glActiveTexture(GL_TEXTURE1);//多个位置时,使用前需要先激活
//glBindTexture(GL_TEXTURE_2D, texture2);
//glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);
//设置到两个轴上
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); // Set texture wrapping to GL_REPEAT (usually basic wrapping method)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
//上面两句是对S和T轴设置环绕方式,环绕方式为重复纹理图像。
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
//下面生成纹理。2D纹理,因此纹理目标是GL_TEXTURE_2D
//下面两句设置方法和缩小的纹理过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//使用SOIL加载图片,自此到循环前都是加载并生成纹理。
int width, height;
std::cout << SOIL_last_result() << std::endl;
unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB);
std::cout << SOIL_last_result() << std::endl;
if (image)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
//第一个参数纹理目标Target,会生成与当前绑定纹理对象同样纹理
//设置纹理指定多级渐远纹理级别,0,基本级别
//第三个参数,纹理储存为何种格式,RGB
//四五是设置最终的纹理的宽度和高度,就用之前的变量。
//0
//七八位源图的格式和数据类型,使用RGB加载并储存为char(byte)数组
//最后参数为图像的数据
}
else
{
std::cout << "Failed to load texture." << std::endl;
}
//当前绑定的纹理对象会被附加上纹理图像,然而只有0级别的纹理图像被加载 了,多级的话需要手动设置
//或者可以,在生成纹理之后调用glGenerateMipmap,这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);
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);
// Set texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
image = SOIL_load_image("awesomeface.png", &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);
//防止退出的循环
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//glClearColor是一个状态设置函数,glClear是一个状态应用函数
//绘图
ourShader.Use();
glUniform1f(glGetUniformLocation(ourShader.Program, "para"), Offset);
//坐标系统
//我们将矩阵向我们要进行移动场景的反向移动。,可以把矩阵看摄像机
glActiveTexture(GL_TEXTURE0);//这句是激活。
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);
//这里因为要使用第二个纹理,改变一点渲染流程,先绑定两个纹理到对应的纹理单元,然后定义哪个uniform采样器应对哪个纹理单元。
glm::mat4 view;
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
//然后是透视投影矩阵。
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), (GLfloat)WIDTH/ (GLfloat)HEIGHT, 0.1f, 100.f);
GLint modelLoc = glGetUniformLocation(ourShader.Program, "model");
GLint viewLoc = glGetUniformLocation(ourShader.Program, "view");
GLint projLoc = glGetUniformLocation(ourShader.Program, "projection");
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
glBindVertexArray(VAO);
for (GLuint i = 0; i < 10; i++)
{
// Calculate the model matrix for each object and pass it to shader before drawing
glm::mat4 model;
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
if (i % 3 == 0) // every 3rd iteration (including the first) we set the angle using GLFW's time function.
angle = glfwGetTime() * 25.0f;
model = glm::rotate(model, angle, glm::vec3(1.0f, 0.3f, 0.5f));
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 36);
}
glBindVertexArray(0);
glfwSwapBuffers(window);
}
//回调函数
//释放:
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate(); //释放/删除之前的分配的所有资源
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
std::cout << key << std::endl;
//用户按下ESC键,我们设置window窗口WindowShouldClose属性为true
//关闭应用程序
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key == GLFW_KEY_UP&&action == GLFW_PRESS)
{
Offset += 0.1;
if (Offset >= 1.0f) Offset = 1.0f;
}
if (key == GLFW_KEY_DOWN&&action == GLFW_PRESS)
{
Offset -= 0.1;
if (Offset <= 0.0f) Offset = 0.0f;
}
}