欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

老子不信我学不会OpenGL系列!008 坐标变换!

程序员文章站 2022-03-25 17:28:01
...

在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

专业名词翻译的不一定(应该是 一定不)对……,英文列在后面了。下面具体解释一下。

老子不信我学不会OpenGL系列!008 坐标变换!

(下面的有的是我自己说的,可能不对)

  • 局部坐标系:每个物体都有一个自己的坐标系,坐标原点一般是物体的中心(虽然在哪里都可以)。目的是方便对这个物体自身的修改(如建一个人头的模型的时候)
  • 世界坐标系:由局部坐标系,经过 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,这一步是自动完成的。就是将齐次坐标(四个分量的)化为一般的三维坐标。就是将前三个分量都除以第四个分量,然后去掉第四个分量。

老子不信我学不会OpenGL系列!008 坐标变换!

(图片来源:https://www.cnblogs.com/graphics/archive/2012/07/25/2582119.html,这里也更详细的讲了投影变换,可以参考这里的)

如果我们直接把z坐标去掉,那就成了正交投影了,这样不会有透视的效果。而多数时候,我们显然希望有透视效果,所以多数时候要做一些操作(用不同的projection matrix),使得有透视效果。有两种projection matrix:正交投影,透视投影。

老子不信我学不会OpenGL系列!008 坐标变换!

 

正交投影:

老子不信我学不会OpenGL系列!008 坐标变换!

用这个函数来生成所需的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,就越靠近视野的中心。这是符合我们直观的(一条直线,越远就越靠近中心)

老子不信我学不会OpenGL系列!008 坐标变换!

用这个函数来生成透视投影的变换矩阵:

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)的范围了) 

老子不信我学不会OpenGL系列!008 坐标变换!

 

应用:

现在我们终于可以加入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);

这里要注意一下顺序,离着向量越近的变换,就越先进行。

 

代码与结果:

老子不信我学不会OpenGL系列!008 坐标变换!

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);
}

 

相关标签: OpenGL