五个坐标系统
- 局部空间(物体空间)
- 世界空间
- 观察空间
- 裁剪空间
- 屏幕空间
五个坐标系统的概念 : 坐标系统
为了将坐标, 从一个坐标系转换到另一个坐标系. 我们需要用到的几个矩阵. 最重要的几个分别是 :
- 模型(Model)
- 视图(View)
- 投影(Projection)
顶点坐标 始于 局部空间 ==> 世界坐标 ==> 观察坐标 ==> 裁剪坐标 ==> 屏幕坐标
投射投影
由投影矩阵创建的观察区域. 参照以下的图.
也就是从 NEAR PLANE 到 FAR PLANE之间的这段, 也叫 平截头体
正射投影
一个类似立方体的平截头体. 参照以下的图
也就是NEAR PLANE的面积 跟FAR PLANE面积是一样的,
把它们都组合到一起
为每一个步骤都创建了一个转换矩阵:模型矩阵、观察矩阵和投影矩阵。一个顶点的坐标将会根据以下过程被转换到裁剪坐标:
Vclip = Mprojection ⋅ Mview ⋅ Mmodel ⋅ Vlocal
注意每个矩阵被运算的顺序是相反的(记住我们需要从右往左乘上每个矩阵)。
最后的顶点应该被赋予顶点着色器中的gl_Position且OpenGL将会自动进行透视划分和裁剪。
Important
顶点着色器的输出需要所有的顶点都在裁剪空间内,而这是我们的转换矩阵所做的。OpenGL然后在裁剪空间中执行透视划分从而将它们转换到标准化设备坐标。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(在我们的例子中屏幕是800 *600)。这个过程称为视口转换。
实践
坐标系统先大概知道有这些东西, 这一章的主题可能会比较难理解 , 因为从这几个矩阵里面, 还不清楚该怎么用, 或则怎么理解.
Vclip = Mprojection ⋅ Mview ⋅ Mmodel ⋅ Vlocal
对应 :
gl_Position = projection * view * model * vec4(aPos, 1.0);
注意矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。
最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。
Shader
#define STRINGIZE(x) #x
#define SHADER(shader) STRINGIZE(shader)
/// 着色器程序之间的数据传递
static char *myCoordinateVertexShaderStr = SHADER(
\#version 330 core\n
layout (location = 0) in vec3 position; //顶点数据源输入
layout (location = 1) in vec3 color; //颜色数据源输入
out vec4 vertexColor;//把片元着色器的颜色从这里输出
uniform mat4 myProjection;//投影矩阵
uniform mat4 myView;//观察矩阵
uniform mat4 myModel;//模型矩阵
void main()
{
gl_Position = myProjection * myView * myModel * vec4(position, 1.0f);
vertexColor = vec4(color, 1.0f); //输出给片元着色器
}
);
//片元着色器程序
static char *myCoordinateFragmentShaderSrc = SHADER(
\#version 330 core\n
in vec4 vertexColor;//从顶点着色器中拿color的值
out vec4 color;
void main()
{
color = vertexColor;//获取值
}
);
顶点数据
#include "glad.h"
#include <GLFW/glfw3.h>
//正方形
static GLfloat myCoordinateVertices[] = {
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, //左下 红
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, //右下 绿
0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, //右上 绿
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, //左上 红
};
//索引
static GLint myCoordinateVerticesIndices[] = { // 注意索引从0开始!
0, 1, 2, // 第一个三角形
0, 3, 2 // 第二个三角形
};
程序
#include <iostream>
#include "MyCoordinate.hpp"
#include "MyProgram.hpp"
#include "MyCoordinateShader.hpp"
#include "MyCoordinateVertices.hpp"
#include "glm.hpp"
#include "matrix_transform.hpp"
#include "type_ptr.hpp"
int runMyCoordinate() {
int result = glfwInit();
if (result == GL_FALSE) {
printf("glfwInit 初始化失败");
return -1;
}
//这里的宏不好提示出来, 根据LearnOpenGL的文档提示, 用这三个
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//Mac平台需要加入
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
//创建一个Window
GLFWwindow *window = glfwCreateWindow(600, 400, "My Opengl Window", NULL, NULL);
if(!window) {
printf("window 创建失败");
}
//opengl运行模式 -- 单线程, 理解为跟当前的Window做一次绑定操作.
glfwMakeContextCurrent(window);
//任何的OpenGL接口调用都必须在初始化GLAD库后才可以正常访问。如果成功的话,该接口将返回GL_TRUE,否则就会返回GL_FALSE。
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
//----------------------------------------------------------------------
//先创建我们的Program对象, 加载顶点着色器程序和片元着色器程序
MyProgram myProgram = MyProgram(myCoordinateVertexShaderStr, myCoordinateFragmentShaderSrc);
GLuint VBO , VAO , EBO;
unsigned int squareIndicesCount = 0;
//创建VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(myCoordinateVertices), myCoordinateVertices, GL_STATIC_DRAW);
//创建VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
//创建EBO, 这里的EBO相当于索引的作用
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(myCoordinateVerticesIndices), myCoordinateVerticesIndices, GL_STATIC_DRAW);
//解绑VAO
glBindVertexArray(0);
//计算索引个数
squareIndicesCount = sizeof(myCoordinateVerticesIndices)/sizeof(myCoordinateVerticesIndices[0]);
glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
//进行绘制
while(!glfwWindowShouldClose(window)){
//检查事件
glfwPollEvents();
//渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(myProgram.program);
///变换处理
GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");
GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
//单位矩阵
glm::mat4 trans = glm::mat4(1.0f);
//矩阵赋值
glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(trans));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, squareIndicesCount, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
//交换缓冲
glfwSwapBuffers(window);
}
//程序销毁
glfwTerminate();
return 1;
}
先运行一遍看看效果
没有做任何变换前面有说 :
为每一个步骤都创建了一个转换矩阵:模型矩阵、观察矩阵和投影矩阵。一个顶点的坐标将会根据以下过程被转换到裁剪坐标:
Vclip = Mprojection ⋅ Mview ⋅ Mmodel ⋅ Vlocal
注意由右往左的顺序看这几个矩阵
所以在Shader中定义三个对应的Uniform.
uniform mat4 myProjection;//投影矩阵
uniform mat4 myView;//观察矩阵
uniform mat4 myModel;//模型矩阵
gl_Position = myProjection * myView * myModel * vec4(position, 1.0f);
其实跟这个公式是一样的.
然后我们在程序中.
GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");
GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
//单位矩阵
glm::mat4 trans = glm::mat4(1.0f);
//矩阵赋值
glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(trans));
这里做了一个和单位矩阵转换的操作. 其实这里的意思就是对应的顶点数据没有发生任何的改变.
投影矩阵变换
我们的顶点坐标, 通过模型, 观察和投影矩阵来转换, 最终得到的对象应该是:
- 往后想地板倾斜
- 离我们有点距离
- 透视展示(顶点越远, 变得越小)
整体效果类似于看火车轨道, 越远越窄,越小.
代码
///变换处理
GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");
//单位矩阵
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 projection = glm::mat4(1.0f);
//矩阵变换
model = glm::rotate(model, glm::radians(-45.0f), glm::vec3(1.0f,0.0f,0.0f));//以x轴旋转45度
view = glm::translate(view, glm::vec3(0.0f,0.0f, -3.0f)); // 向Z轴的负方向移动
projection = glm::perspective(glm::radians(60.0f), 1.0f, 0.01f, 100.f);//投影矩阵
//矩阵值处理
glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
效果图
注意不要混淆, 前面的平移, 旋转, 缩放的矩阵变换. 跟这里的效果不一样, 这里是相当于整个对象倾斜了.
PS : 在坐标系统源代码中.直接定义了矩阵, 没有赋值单位矩阵, 有可能会导致你显示不出来.
glm::mat4 model;
glm::mat4 view;
glm::mat4 projection;
这里最好初始化一个矩阵都是单位矩阵. 比如
glm::mat4 model = glm::mat4(1.0f);
3D
自制顶点数据
//立方体 : 8个点连成一个立方体
static GLfloat myCoordinateCubeVertices[] = {
//左侧
-0.5f, -0.5f, -0.5f, 1.0f,0.0f,0.0f, //左下后 红 0
-0.5f, -0.5f, 0.5f, 1.0f,0.0f,0.0f, //左下前 红 1
-0.5f, 0.5f, 0.5f, 1.0f,1.0f,0.0f, //左上前 蓝 2
-0.5f, 0.5f, -0.5f, 1.0f,1.0f,0.0f, //左上后 蓝 3
//右侧
0.5f, -0.5f, -0.5f, 0.0f,0.0f,0.0f, //右下后 白 4
0.5f, -0.5f, 0.5f, 0.0f,0.0f,0.0f, //右下前 白 5
0.5f, 0.5f, 0.5f, 0.0f,1.0f,1.0f, //右上前 蓝 6
0.5f, 0.5f, -0.5f, 0.0f,1.0f,1.0f, //右上后 绿 7
};
//立方体索引
static GLint myCoordinateCubeVerticesIndices[] = { // 注意索引从0开始!
//第1面 : 左 0123
0, 1, 2,
0, 2, 3,
//第2面 : 右 4567
4, 5, 6,
4, 6, 7,
//第3面 : 上 2367
2, 3, 6,
3, 6, 7,
//第4面 : 下 0145
0, 1, 4,
1, 4, 5,
//第5面 : 前 1256
1, 2, 5,
2, 5, 6,
//第6面 : 后 0347
0, 3, 4,
3, 4, 7,
};
加入深度测试 GL_DEPTH_TEST
while(!glfwWindowShouldClose(window)){
//检查事件
glfwPollEvents();
//渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(myProgram.program);
glEnable(GL_DEPTH_TEST);
///变换处理
GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");
//单位矩阵
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 projection = glm::mat4(1.0f);
model = glm::rotate(model,(GLfloat)glfwGetTime() * 1.0f, glm::vec3(1.0f,1.0f,0.0f));//以x,y轴旋转
view = glm::translate(view, glm::vec3(0.0f,0.0f, -3.0f)); // 向Z轴的负方向移动
projection = glm::perspective(glm::radians(60.0f), 1.0f, 0.01f, 100.f);//投影矩阵
glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, squareIndicesCount, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
//交换缓冲
glfwSwapBuffers(window);
}
QQ20200924-020827-HD.gif
纹理Cube
目前以颜色的方式生成一个旋转的立方体. 现在给这个立方体加上纹理贴图.
修改一下Shader
/// 顶点着色器 CubeTexture
static char *myCoordinateCubeTextureVertexShaderStr = SHADER(
\#version 330 core\n
layout (location = 0) in vec3 position; //顶点数据源输入
layout (location = 1) in vec3 color; //颜色数据源输入
layout (location = 2) in vec2 texCoords; //纹理数据源输入(2D)
out vec2 vertexTexCoords;//把片元着色器的纹理从这里输出
uniform mat4 myProjection;//投影
uniform mat4 myView;//观察
uniform mat4 myModel;//模型
void main()
{
gl_Position = myProjection * myView * myModel * vec4(position, 1.0f);
vertexTexCoords = texCoords;
}
);
/// 片元着色器 CubeTexture
static char *myCoordinateCubeTextureFragmentShaderSrc = SHADER(
\#version 330 core\n
in vec2 vertexTexCoords;//从顶点着色器中拿纹理的值
uniform sampler2D myTexture;
out vec4 FragColor;
void main()
{
FragColor = texture(myTexture, vertexTexCoords);
}
);
程序修改
设置纹理坐标
//纹理坐标, 纹理坐标用的三角形坐标一致
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)0);
glEnableVertexAttribArray(2);
生成纹理
unsigned int texture;
unsigned char *data;
int width, height, nrChannels;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
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( "/Users/liliguang/Desktop/LearnOpengl/LearnOpenGl/LearnOpenGl/Demo/Common/ImgSources/dizhuan.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);
glBindTexture(GL_TEXTURE_2D, 0);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
加载纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(glGetUniformLocation(myProgram.program, "myTexture"), 0);
效果
纹理Cube.gif多个自旋转纹理Cube
glm::vec3 cubePositions[10];
for (int i = 0; i<10; i++) {
float xRandomNum = arc4random_uniform(10.0) - 5.0f;
float yRandomNum = arc4random_uniform(10.0) - 5.0f;
cubePositions[i] =glm::vec3( xRandomNum, yRandomNum, 0.0f);
}
//进行绘制
while(!glfwWindowShouldClose(window)){
//检查事件
glfwPollEvents();
//渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(myProgram.program);
//加载纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(glGetUniformLocation(myProgram.program, "myTexture"), 0);
//单位矩阵
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 projection = glm::mat4(1.0f);
///变换处理
GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");
projection = glm::perspective(glm::radians(120.0f), 1.0f, 0.01f, 100.f);//投影矩阵
glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
glBindVertexArray(VAO);
for(unsigned int i = 0; i < 10; I++)
{
//改变模型数据的位置
model = glm::mat4(1.0f);
model = glm::translate(model,glm::vec3(cubePositions[i].x,cubePositions[i].y,0.0f));//x,y平移
glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(model));
view = glm::mat4(1.0f);
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -5.0f)); // 向Z轴的负方向移动
view = glm::rotate(view,(GLfloat)glfwGetTime() * 1.0f, glm::vec3(cubePositions[i].x,cubePositions[i].y,0.0f));//以x,y轴旋转
glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(view));
glDrawElements(GL_TRIANGLES, squareIndicesCount, GL_UNSIGNED_INT, 0);
}
//交换缓冲
glfwSwapBuffers(window);
}
glBindVertexArray(0);
//程序销毁
glfwTerminate();
多个Cube
PS : 如果需要自旋,这里需要注意translate和rotate 对应的关系,
比如translate 矩阵x轴移动2, y轴移动2. 要以xy轴旋转, 那么对应的是rotate的x也是2,y也是2. 等比例对应.
网友评论