老子不信我学不会OpenGL系列!008 坐标变换!
在OpenGL中,如果 x,y,z 的坐标超出 (-1,1) 的范围,就不可见。一般的手段是:我们定义一个自己的坐标范围,然后在vertex shader里 将这个坐标系变换到标准范围的坐标系(NDC)。
在从原始坐标系变换到NDC再到屏幕坐标系的过程中,引入一系中间坐标系当做媒介,要比一步变换到最终的坐标系要容易很多。一般分为下面几个坐标系:
- 本地(局部)空间 Local space (or Object space)
- 世界空间 World space
- 观察空间 View space (or Eye space)
- 裁剪空间 Clip space
- 屏幕空间 Screen space
专业名词翻译的不一定(应该是 一定不)对……,英文列在后面了。下面具体解释一下。
(下面的有的是我自己说的,可能不对)
- 局部坐标系:每个物体都有一个自己的坐标系,坐标原点一般是物体的中心(虽然在哪里都可以)。目的是方便对这个物体自身的修改(如建一个人头的模型的时候)
- 世界坐标系:由局部坐标系,经过 model matrix 变换得到。在这个坐标系里,所有物体的坐标都统一了。地面面一般与xOz平面平行。为了方便我们安排物体与物体之间的位置关系。
- 观察坐标系:由世界坐标系,经过 view matrix 变换得到。以摄像机(眼睛)为原点建立的坐标系。摄像机运动的时候就相当于这个坐标系在改变。
- Clip space:由观察坐标系,经过 projection matrix 变换得到。裁剪坐标系将坐标映射到(-1,1)之间,并且决定了哪些点最终会在画在屏幕上。
- 屏幕坐标系:最终输出用于生成片元的坐标系。
Clip space:
通过 projection matrix 把观察坐标系变换到一个坐标系。在这个坐标系里,视锥体(frustum)刚好被映射成(-1,1)*(-1,1)*(-1,1)的正方体。视锥体包含了所有我们感兴趣的点。而且,这个坐标系很容易变换到2维坐标系(似乎就是把z坐标直接去掉?我不确定)。这个坐标系就是裁剪坐标系。
当做完 projection matrix 的变换后,我们还需要进行perspective division,这一步是自动完成的。就是将齐次坐标(四个分量的)化为一般的三维坐标。就是将前三个分量都除以第四个分量,然后去掉第四个分量。
(图片来源:https://www.cnblogs.com/graphics/archive/2012/07/25/2582119.html,这里也更详细的讲了投影变换,可以参考这里的)
如果我们直接把z坐标去掉,那就成了正交投影了,这样不会有透视的效果。而多数时候,我们显然希望有透视效果,所以多数时候要做一些操作(用不同的projection matrix),使得有透视效果。有两种projection matrix:正交投影,透视投影。
正交投影:
用这个函数来生成所需的projection matrix
glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
ortho( 左,右,下,上,前,后 ) 。左,就是视锥体左平面在观察坐标系的x值。前,就是前平面的z值。以此类推。即,平面:x=左,是视锥体的左平面。
透视投影:
projection matrix不止将x,y,z映射到clip space。还将w(第四个分量)设置成了一个代表这个点到观测平面距离的值(这个点越远,w值越大)。最后经过 perspective division ,一个点距离观测平面越远,他的x,y,z的绝对值就越小,越接近0,就越靠近视野的中心。这是符合我们直观的(一条直线,越远就越靠近中心)
用这个函数来生成透视投影的变换矩阵:
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
perspective( fov弧度, 横纵比, *面, 远平面 )。如下图。比near plane 还近的物体,或比 far plane 还远的物体将不会被渲染(超出(-1,1)的范围了)
应用:
现在我们终于可以加入3D的东西了。按照上面所说,需要建立三个变换矩阵,传给vertex shader。然后在vertex shader中,将坐标变换到 Clip 坐标系下,就可以了。不过在做之前,还有一些事情要说。
首先,OpenGL是右手系。
其次,在做 从世界坐标系到观测坐标系变换 的时候,其具体实现是这样的。我们的眼睛往左移动了1个单位,相当于所有物体都向反方向,向右移动了1个单位。旋转也类似。所以在给view矩阵赋值的时候(model,view,projection这些矩阵全都是作用在物体上面的),要注意,他的值与我们的移动、旋转、缩放是相反的。
4件事情:1、建立变换矩阵 2、设置每个矩阵的值 3、将矩阵传入到 VertexShader 中 4、在Vertex Shader 中做变换。
1、建立矩阵 2、设置值
mat4 model(1), view(1), proj(1);//初始化为单位矩阵
model = rotate(model, radians(-90.0f), vec3(1.0f, 0.0f, 0));
view = translate(view, vec3(0, -1.0f, -3.0f));
view = rotate(view, radians(-30.0f), vec3(-1.0f, 0.0f, 0));
proj = perspective(radians(50.0f), 600.0f / 800.0f, 0.1f, 100.0f);
函数在上一章的末尾说了。
3、传入VertexShader
GLuint modelLoc = glGetUniformLocation(myShaderProg.ID, "model");
GLuint viewLoc = glGetUniformLocation(myShaderProg.ID, "view");
GLuint projLoc = glGetUniformLocation(myShaderProg.ID, "proj");
// 6.Render Loop:
while (!glfwWindowShouldClose(window))
{
// ... ...
// 这下面就可以写绘图的代码了
glUseProgram(myShaderProg.ID);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, value_ptr(proj));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// 这上面是绘图的代码
// ... ...
}
同样是上一章的末尾。
4、在vtx.vs中变换
gl_Position = proj * view * model * vec4(inPos,1);
这里要注意一下顺序,离着向量越近的变换,就越先进行。
代码与结果:
main.cpp
// 1.头文件:
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include "Shader.h"
// 5.OpenGL与窗口(main之前):
void CBK_framebuffer_size(GLFWwindow* window, int w, int h)
{
glViewport(0, 0, w, h);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
int main()
{
// 2.在创建窗口之前……:
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 3.创建窗口:
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// 4.在OpenGL之前……:
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 5.OpenGL与窗口:
glViewport(0, 0, 800, 600);
glfwSetFramebufferSizeCallback(window, CBK_framebuffer_size);
// OpenGL的配置:
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//设置背景色
// 绘图需要用的数据:
float vertices[] = {
// positions // colors // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left
};
GLuint indeces[] = {
1,2,3,
3,0,1
};
int imageW, imageH, imageCH, imageW2, imageH2, imageCH2;
stbi_set_flip_vertically_on_load(true);
unsigned char *imageData = stbi_load("..\\my_OtherFiles\\container.jpg", &imageW, &imageH, &imageCH, 0);
unsigned char *imageData2 = stbi_load("..\\my_OtherFiles\\a.png", &imageW2, &imageH2, &imageCH2, 0);
// Texture 贴图
GLuint tex[2];
glGenTextures(2, tex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imageW, imageH, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
glGenerateMipmap(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tex[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imageW2, imageH2, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData2);
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(imageData);
stbi_image_free(imageData2);
// 顶点相关的配置
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indeces), indeces, GL_STATIC_DRAW);
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
// ShaderProgram
Shader myShaderProg("vtx.vs", "frag.fs");
glUseProgram(myShaderProg.ID);
glUniform1i(glGetUniformLocation(myShaderProg.ID, "texData"), 0);
glUniform1i(glGetUniformLocation(myShaderProg.ID, "texData2"), 1);
GLuint modelLoc = glGetUniformLocation(myShaderProg.ID, "model");
GLuint viewLoc = glGetUniformLocation(myShaderProg.ID, "view");
GLuint projLoc = glGetUniformLocation(myShaderProg.ID, "proj");
glUseProgram(0);
// 坐标变换
using namespace glm;
mat4 model(1), view(1), proj(1);
model = rotate(model, radians(-90.0f), vec3(1.0f, 0.0f, 0));
view = translate(view, vec3(0, -1.0f, -3.0f));
view = rotate(view, radians(-30.0f), vec3(-1.0f, 0.0f, 0));
proj = perspective(radians(50.0f), 800.0f / 600.0f, 0.1f, 100.0f);
// 6.Render Loop:
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClear(GL_COLOR_BUFFER_BIT);//绘制背景色
// 这下面就可以写绘图的代码了
glUseProgram(myShaderProg.ID);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, value_ptr(proj));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// 这上面是绘图的代码
glfwSwapBuffers(window);
glfwPollEvents();
}
// 7.程序结束之前:
glfwTerminate();
return 0;
}
vtx.vs:
#version 330 core
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inCol;
layout (location = 2) in vec2 inTexCoor;
out vec3 col;
out vec2 texCoor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
void main()
{
gl_Position = proj * view * model * vec4(inPos,1);
col = inCol;
texCoor = inTexCoor;
}
frag.fs(没变化)
#version 330 core
in vec2 texCoor;
in vec3 col;
out vec4 fragCol;
uniform sampler2D texData;
uniform sampler2D texData2;
void main()
{
fragCol = mix(texture(texData,texCoor),texture(texData2,texCoor),0.5) * vec4(col,1);
}