OGL(教程6)——平移变换

原文地址:http://ogldev.atspace.co.uk/www/tutorial06/tutorial06.html

背景知识
本节我们将会看看3D世界中的一些变换知识。然后使其屏幕的图像有一个深度的效果。变换最通常的是使用矩阵,让他们一个接一个的相乘,然后最终乘以顶点的坐标。每一节我们都会验证一个变换。

这里我们先介绍下线性变换之移动变换。它是将物体沿着指定的方向移动指定的距离。比如下图:
在这里插入图片描述

一种解决方式是提供一个偏移的向量,在这种情况下值为(-1,1),它是shader的统一变量。然后把它加到每个顶点位置上。但是,这种方式是把一组的矩阵乘起来,然后得到一个复杂的变换。除此之外,你会看到用矩阵乘来做变形,然后把坐标加上去做位移,这个太笨了。一个更好的方法是,寻找一个矩阵能够代表所有两个变换,于是想到把两个矩阵乘起来。但是你能能够找到一个矩阵,用它来乘以(0,0),这个点是三角形的左下角,然后的得到(1,1)这个点吗?事实上你不能用2维的矩阵来得到这个点。你也不能用一格三维的矩阵计算得到(0,0,0)。通常来说,我们需要一个矩阵M,还要一个点P(x,y,z),还有一个向量V(v1,v2,v3),公式M*P=P1,也就是(x+v1,y+v2,z+v3)。简单来说矩阵M平移P到P+V。在P1中,我们看到每个分量都是P和V对应分量相加。看起来我们需要从标准矩阵入手,然后找到一点变化,如下图所示:
在这里插入图片描述

我们修改单位矩阵,如下:
在这里插入图片描述

用3x3的矩阵进行变化不能做到上面的结果,所以场下4x4矩阵:
在这里插入图片描述

用4维的向量代替3维的向量,这个向量叫做齐次坐标,这个在3D图形学中非常的流行。第四个分量w。事实上,shader内部的符号gl_Position,我们在之前的章节中看到4-vector的w分量在3D变换到2D中起到很重要的角色。通常来讲,对于点来说w=1,而对于方向来说是w=0。原因是点是可以被平移的,而方向是不能被平移。你可以改变方向的长度,但是向量只有方向,没有长度,它不关心起点。你可简单的把所有的方向的起点设置在原点。把w设置为0,乘以矩阵,得到还是原来的向量。

代码注释:

struct Matrix4f {
    float m[4][4];
};

我们在头文件math_3d.h中加入了4x4的矩阵。这个后面的矩阵变换中经常使用。

GLuint gWorldLocation;

我们使用句柄访问shader中的世界矩阵变量。我们使用world是就意味着是在世界坐标系中移动。

Matrix4f World;
World.m[0][0] = 1.0f; World.m[0][1] = 0.0f; World.m[0][2] = 0.0f; World.m[0][3] = sinf(Scale);
World.m[1][0] = 0.0f; World.m[1][1] = 1.0f; World.m[1][2] = 0.0f; World.m[1][3] = 0.0f;
World.m[2][0] = 0.0f; World.m[2][1] = 0.0f; World.m[2][2] = 1.0f; World.m[2][3] = 0.0f;
World.m[3][0] = 0.0f; World.m[3][1] = 0.0f; World.m[3][2] = 0.0f; World.m[3][3] = 1.0f;

在渲染函数中,我们准备了4x4的矩阵,然后赋值方式如上。我们把World.m[1][3] 和World.m[2][3]置为0,原因是不想对y和z进行平移。而把 World.m[0][3]变为sinus。这就把x坐标在-1到1之间进行变化。现在我们需要加载矩阵到shader。

glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, &World.m[0][0]);

这是使用glUniform*函数加载数据到shader的统一变量。
此函数还有其他的形式如 2x2, 3x3, 3x2, 2x4, 4x2, 3x4 和4x3。第1个参数是统一变量的位置。第2个参数是我们要更新的矩阵的个数。这里是1个,但是我们也可以一次调用更新多个矩阵。 第三个参数是表面矩阵是行优先还是列优先。如果是行优先则矩阵的每行代表一个矩阵,从上往下。如果是列优先,则每个列代表一个向量。C/C++具有默认是行优先。举个例子:

int a[2][3];
a[0][0] = 1;
a[0][1] = 2;
a[0][2] = 3;
a[1][0] = 4;
a[1][1] = 5;
a[1][2] = 6;

那么矩阵则为:

1 2 3
4 5 6

然后内存布局是这样的:

1 2 3 4 5 6 

1的地址是最低的。

所以第3个参数的值为GL_TRUE ,因为我们提供的矩阵是行优先的。我们也可以把此值设置为GL_FALSE,此时我们就要对矩阵进行转置处理了。第4个参数是矩阵的起始地址。

剩下的就是shader的代码了。

uniform mat4 gWorld;

这个是单位4x4的单位矩阵,mat2和mat3也是对应单位矩阵。

gl_Position = gWorld * vec4(Position, 1.0);

从顶点缓冲读取处理的顶点数据是3维的。但是我们需要一个四维的向量,又由于是点,所以第四维度设置为1。这里有两个选择:顶点存储的时候就存储为4维的后者在使用的时候,手动添加1维。第一种方式不好,占用内存,这里使用第二种方式。因为对于顶点来说第四维都是1。在GLSL中的使用vec4(Position,1.0)。我们把这个向量乘以矩阵得到的及时gl_Position了。总结,每帧中我们产生一个平移矩阵,将坐标x在-1到1之间做变换。shader把每个顶点的位置都乘以这个矩阵,此时物体就会左右移动。在很多情况下,三角形的一条边会超出边界。此时我们只会看到规格化盒子里面的区域。

代码:

// GLUT.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <glew.h>
#include <freeglut.h>
#include <assert.h>
#include <math.h>
#include "math_3d.h"

#pragma comment( lib, "glew32d.lib" )

GLuint VBO;
GLuint gWorldLocation;
static const char* pVS = " \n\
#version 330 \n\
\n\
layout (location = 0) in vec3 Position; \n\
\n\
uniform mat4 gWorld; \n\
\n\
void main() \n\
{ \n\
gl_Position = gWorld * vec4(Position, 1.0); \n\
}";
static const char* pFS = " \n\
#version 330 \n\
\n\
out vec4 FragColor; \n\
\n\
void main() \n\
{ \n\
FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n\
}";
static void RenderSceneCB()
{
	glClear(GL_COLOR_BUFFER_BIT);
	static float Scale = 0.0f;
	Scale += 0.001f;
	Matrix4f World;
	World.m[0][0] = 1.0f; World.m[0][1] = 0.0f; World.m[0][2] = 0.0f; World.m[0][3] = sinf(Scale);
	World.m[1][0] = 0.0f; World.m[1][1] = 1.0f; World.m[1][2] = 0.0f; World.m[1][3] = 0.0f;
	World.m[2][0] = 0.0f; World.m[2][1] = 0.0f; World.m[2][2] = 1.0f; World.m[2][3] = 0.0f;
	World.m[3][0] = 0.0f; World.m[3][1] = 0.0f; World.m[3][2] = 0.0f; World.m[3][3] = 1.0f;
	glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, &World.m[0][0]);
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glDrawArrays(GL_TRIANGLES, 0, 3);
	glDisableVertexAttribArray(0);
	glutSwapBuffers();
}
static void InitializeGlutCallbacks()
{
	glutDisplayFunc(RenderSceneCB);
	glutIdleFunc(RenderSceneCB);
}
static void CreateVertexBuffer()
{
	Vector3f Vertices[3];
	Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
	Vertices[1] = Vector3f(1.0f, -1.0f, 0.0f);
	Vertices[2] = Vector3f(0.0f, 1.0f, 0.0f);
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
}
static void AddShader(GLuint ShaderProgram, const char* pShaderText, GLenum ShaderType)
{
	GLuint ShaderObj = glCreateShader(ShaderType);
	if (ShaderObj == 0) {
		fprintf(stderr, "Error creating shader type %d\n", ShaderType);
		exit(0);
	}
	const GLchar* p[1];
	p[0] = pShaderText;
	GLint Lengths[1];
	Lengths[0] = strlen(pShaderText);
	glShaderSource(ShaderObj, 1, p, Lengths);
	glCompileShader(ShaderObj);
	GLint success;
	glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
	if (!success) {
		GLchar InfoLog[1024];
		glGetShaderInfoLog(ShaderObj, 1024, NULL, InfoLog);
		fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
		exit(1);
	}
	glAttachShader(ShaderProgram, ShaderObj);
}
static void CompileShaders()
{
	GLuint ShaderProgram = glCreateProgram();
	if (ShaderProgram == 0) {
		fprintf(stderr, "Error creating shader program\n");
		exit(1);
	}
	AddShader(ShaderProgram, pVS, GL_VERTEX_SHADER);
	AddShader(ShaderProgram, pFS, GL_FRAGMENT_SHADER);
	GLint Success = 0;
	GLchar ErrorLog[1024] = { 0 };
	glLinkProgram(ShaderProgram);
	glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
	if (Success == 0) {
		glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
		fprintf(stderr, "Error linking shader program: '%s'\n", ErrorLog);
		exit(1);
	}
	glValidateProgram(ShaderProgram);
	glGetProgramiv(ShaderProgram, GL_VALIDATE_STATUS, &Success);
	if (!Success) {
		glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
		fprintf(stderr, "Invalid shader program: '%s'\n", ErrorLog);
		exit(1);
	}
	glUseProgram(ShaderProgram);
	gWorldLocation = glGetUniformLocation(ShaderProgram, "gWorld");
	assert(gWorldLocation != 0xFFFFFFFF);
}
int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowSize(1024, 768);
	glutInitWindowPosition(100, 100);
	glutCreateWindow("Tutorial 06");
	InitializeGlutCallbacks();
	// Must be done after glut is initialized!
	GLenum res = glewInit();
	if (res != GLEW_OK) {
		fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
		return 1;
	}
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	CreateVertexBuffer();
	CompileShaders();
	glutMainLoop();
	return 0;
}

展示效果略,就是三角形左右移动。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值