准备知识

从这一节我们开始接触3D对象各种各样的变换,使其显示在屏幕上看起来有深度的感觉。通常每一种变换都是通过矩阵来实现的,把这些变换矩阵逐个的乘起来,然后用乘积乘以顶点位置。在每个教程中,我们致力于研究一种变换。

这里我们看一下平移变换,它负责把一个对象沿着一个向量移动一定的方向和距离。比如说你想把三角形从左图的位置移动到右图的位置。

一种做法是在shader中提供一个uniform变量类型的偏移向量,本例中为(1,1),把它和每个待处理的顶点位置相加。然而,这中做法破坏了把一组变换矩阵逐个乘起来获得一个综合的变换矩阵的方法。另外,你將会看到平移变换通常不是第一步变换,因此在平移之前需要用一个矩阵乘以顶点位置的形式来表示该变换,然后和顶点位置相加。这样做显得比较麻烦,一种更好的做法是我们可以找到一个矩阵代表平移变换,然后用它乘上其他变换矩阵。但是你能找到一个矩阵,当乘以三角形左下角的点(0,0)使其结果变为(1,1)吗?事实上使用2维矩阵没法做到。一般来说我们需要一个矩阵M对于给定的一个点P(x,y,z)和一个平移向量V(v1,v2,v3)有M*P=P1(x+v1,y+v2,z+v3)。在P1中我们可以看到每一个分量是P中的分量与对应的V中分量的和。单位矩阵具有这样的性质:I * P = P(x,y,z)。我们需要在单位矩阵的基础上进行修改使得每个分量的结果类似于(…+V1, …+V2, …+V3),最终我们找到的矩阵形式如下:

从这个计算结果我们可以得到两个结论。

1.a,b,c,d,e和f必须为0,否则每两个分量都会对第三个分量有影响。

2.当x,y,z都为0时,结果也为0向量,也就是说我们没办法把(0,0,0)点通过这种方式平移到其他点。

我们想要找到一个矩阵使得右边具有以下的计算结果:

我们需要通过一种方法將v1,v3,v3加上去,这样a-f就可以为0了,因此我们需要在矩阵中添加第四列,由于3x4阶矩阵和3x1阶矩阵不能直接相乘,我们需要將平移向量中也添加一个分量,该分量的值最好为1,这样我们用v1,v2,v3和第四个分量相乘的时候值才不会变。我们目前的矩阵仍然不是最好的,我们通常用一个4x4阶矩阵作为变换矩阵。最终的变换矩阵如下图所示:



(注意:原文对矩阵的描述中有一些错误,图也是错的,这里笔者更正了一下)

项目配置

参考前面的文章。

程序代码

清单1.主程序 tutorial06.cpp代码

/*

    Copyright 2010 Etay Meiri
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. Tutorial 06 - translation transform
*/
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include "ogldev_math_3d.h" GLuint VBO;
GLuint gWorldLocation; const char* pVSFileName = "shader.vs";
const char* pFSFileName = "shader.fs"; 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(1);
} 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);
} string vs, fs; if (!ReadFile(pVSFileName, vs)) {
exit(1);
}; if (!ReadFile(pFSFileName, fs)) {
exit(1);
}; AddShader(ShaderProgram, vs.c_str(), GL_VERTEX_SHADER);
AddShader(ShaderProgram, fs.c_str(), 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 _tmain(int argc, _TCHAR* 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;
} printf("GL version: %s\n", glGetString(GL_VERSION)); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); CreateVertexBuffer(); CompileShaders(); glutMainLoop(); return 0;
}

代码解读

我们在上一节代码基础上进行修改,这里只对关键代码进行讲解。

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

该结构体在ogldev_math_3d.h头文件中定义,我们几乎所有的变换矩阵都用它表示。

GLuint gWorldLocation;

我们用这个句柄访问shader中定义的uniform变量。

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;

我们准备一个变换矩阵,將v2,v3值设为0,确保它在y/z方向上的位置不发生变换,將v1设为正弦函数的返回值,所以图形会在x方向做“单摆运动”。接下来我们需要將矩阵加载到shader中。

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

还有一组类似的 glUniform*函数用来將数据加载到shader的uniform变量中。上面这个特定的函数用于加载4x4阶矩阵,还有对应的2x2, 3x3, 3x2, 2x4, 4x2, 3x4 和 4x3版本。第一个参数是uniform变量的位置。第二个参数表明我们要更新矩阵的数量,这里我们只有一个矩阵,所以参数值为1。第三个参数对于新手来说会产生困惑,它表明矩阵是行主序还是列主序。行主序表明矩阵是从上到下一行一行的在内存连续区域存放,列也类似。c/c++语言中默认为行主序。这决定了二维数组元素在内存中的分布情况。

清单2. shader.vs代码

#version 330

layout (location = 0) in vec3 Position;

uniform mat4 gWorld;

void main()
{
gl_Position = gWorld * vec4(Position, 1.0);
}
uniform mat4 gWorld;

这里定义了一个表示4x4阶矩阵的uniform 类型变量。

gl_Position = gWorld * vec4(Position, 1.0);

三角形的顶点位置在顶点缓冲区中为3分量,我们需要第四个分量并将其值设为1。有两个选择:將顶点缓存区中放置四个分量的顶点或者在vertex shader增加一个分量。这里我们采用第二种,这样做效率也会更高。

编译运行

你將会看到三角形左右不停移动。

最新文章

  1. Android中使用ExpandableListView实现微信通讯录界面(完善仿微信APP)
  2. tcpdum使用
  3. 剑指Offer面试题:18.二叉树的镜像
  4. 关于RPC与MQ异同的理解
  5. 【faster-rcnn】训练自己的数据——修改图片格式、类别
  6. LR11启动卡修改
  7. 我所常用的ajax调用格式
  8. 为什么在SQL Server2008在视图中修改表结构无效
  9. linux [Fedora] 下的 &quot;飞秋/飞鸽传书&quot;
  10. Java并发编程-关卡
  11. Linux文件类型介绍
  12. pandas.read_csv参数详解
  13. 排序算法(sorting algorithm)之 插入排序(insertion sort)
  14. 高手养成计划基础篇-Linux第二季
  15. 移植 Qt 至 tiny210 详细过程
  16. 记录一次因代理Controller产生的404问题
  17. CAS 单点登录 移动端获取TGT、ST 已经移动端登录页面不进行跳转的设置
  18. Swoole 结合TP5搭建文字直播平台
  19. 基于react的标准form+table页面的请求流程图
  20. poi excel导入导出

热门文章

  1. node 内存消息队列
  2. UVA - 10689 Yet another Number Sequence 矩阵快速幂
  3. doT.js实现混合布局,判断,数组,函数使用,取模,数组嵌套
  4. m_Orchestrate learning system---十二、为什么thinkphp验证场景里面的多个属性之间是逗号
  5. MYSQL主从复制搭建及切换操作(GTID与传统)
  6. 深入理解 JavaScript 异步——转载
  7. BAT 解密(四):配置中心、服务中心、异步技术细节
  8. 转:EL表达式
  9. 配置 centos apache 的日志文件为每天保存,在home分区
  10. HDU 5234 Happy birthday【DP】