准备知识

在前面的几节教程中,我们已经提到过几种变换,为物体在3D世界中的移动提供的极大的灵活性。但是我们还有很多东西需要学习(如摄像机控制和透视投影),你可以已经猜到,我们需要將这些变换组合起来。在很多情况下,你可能需要缩放一个物体使它适合3D场景,然后还要旋转使它面向正确的方向,接着把它移动到合适的位置。到现在为止我们都在练习一次只做一个变换,为了执行一系列的变换,我们要用顶点位置乘上第一个变换矩阵,然后用结果乘上第二个变换矩阵,一直乘下去直到所有的变换都应用于该顶点上。有一种简单的做法,我们可以把变换矩阵提供给shader,然后由shader来完成相乘操作。然而这种做法效率比较低,因为所有顶点都要乘上所有的变换矩阵,而这些顶点仅仅是位置不同。幸运的是,线性代数中有一个规则可以使它变得更简单。

给定一组矩阵M0…Mn 和一个向量V,则有:

Mn * Mn-1 * ... * M0 * V = (Mn* Mn-1 * ... * M0) * V

所以你可以先把矩阵的乘积算出来:

N = Mn * Mn-1 * ... * M0

则有:

Mn * Mn-1 * ... * M0 * V = N * V

这意味着我们可以先把矩阵N算出来,然后作为一个uniform类型的变量传递到shader中,在shader中把它和每一个顶点相乘,这些操作將由GPU来完成。

在把变换矩阵相乘获得N矩阵时,这些矩阵的顺序是怎样的呢?你需要记住的第一件事就是最初相乘的矩阵必须在最右边(本例中为M0),然后每个变换矩阵从右倒左依次相乘。在3D图形学中,通常先缩放对象,然后是旋转,最后才是移动。

我们来看看如果先旋转后平移会是什么情况:



接下来看看先平移后旋转的情况:

正如你所看到的,当我们先平移后旋转时,物体在世界坐标系中的位置很难把握,因为你把物体从原始点移动以后,再旋转时物体的位置也会跟着不断改变,这是我们要避免的现象。这就是为什么我们通常先做旋转变换后做平移变换。

在这个案例中我们开始处理同事存在多种变换的情况,我们必须放弃直接在渲染函数中更新变换矩阵的做法。这种方法无法扩展,容易出差,这里使用Pipeline类代替。这个类隐藏了矩阵操作的细节,使用简单的API进行平移、旋转、缩放等操作。设置了所有的参数后,你只需要简单的获取所有变换后的最终矩阵,这个矩阵可以直接用于shader中。

程序代码

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


#include "stdafx.h"
/* 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 11 - Concatenating transformation2
*/ #include <stdio.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <GL/glew.h>
#include <GL/freeglut.h> #include "ogldev_util.h"
#include "ogldev_pipeline.h" GLuint VBO;
GLuint IBO;
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; Pipeline p;
p.Scale(sinf(Scale * 0.1f), sinf(Scale * 0.1f), sinf(Scale * 0.1f));
p.WorldPos(sinf(Scale), 0.0f, 0.0f);
p.Rotate(sinf(Scale) * 90.0f, sinf(Scale) * 90.0f, sinf(Scale) * 90.0f); glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, (const GLfloat*)p.GetWorldTrans()); glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO); glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0); glDisableVertexAttribArray(0); glutSwapBuffers();
} static void InitializeGlutCallbacks()
{
glutDisplayFunc(RenderSceneCB);
glutIdleFunc(RenderSceneCB);
} static void CreateVertexBuffer()
{
Vector3f Vertices[4];
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(0.0f, -1.0f, 1.0f);
Vertices[2] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[3] = 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 CreateIndexBuffer()
{
unsigned int Indices[] = { 0, 3, 1,
1, 3, 2,
2, 3, 0,
0, 1, 2 }; glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, 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 11"); 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();
CreateIndexBuffer(); CompileShaders(); glutMainLoop();
return 0;
}

代码解读

代码中我们用到一个矩阵类Matrix4f,它提供了矩阵常用的操作 ,该类重载了乘法运算符,实现如下:

inline Matrix4f operator*(const Matrix4f& Right) const
{
Matrix4f Ret;
for (unsigned int i = 0 ; i < 4 ; i++) {
for (unsigned int j = 0 ; j < 4 ; j++) {
Ret.m[i][j] = m[i][0] * Right.m[0][j] +
m[i][1] * Right.m[1][j] +
m[i][2] * Right.m[2][j] +
m[i][3] * Right.m[3][j];
}
} return Ret;
}

我们还用到了管道类Pipeline,声明如下:

class Pipeline
{
public:
Pipeline() { ... }
void Scale(float ScaleX, float ScaleY, float ScaleZ) { ... }
void WorldPos(float x, float y, float z) { ... }
void Rotate(float RotateX, float RotateY, float RotateZ) { ... }
const Matrix4f* GetTrans();
private:
Vector3f m_scale;
Vector3f m_worldPos;
Vector3f m_rotateInfo;
Matrix4f m_transformation;
};

Pipeline::GetTrans函数实现代码如下:

const Matrix4f* Pipeline::GetTrans()
{
Matrix4f ScaleTrans, RotateTrans, TranslationTrans;
InitScaleTransform(ScaleTrans);
InitRotateTransform(RotateTrans);
InitTranslationTransform(TranslationTrans);
m_transformation = TranslationTrans * RotateTrans * ScaleTrans;
return &m_transformation;
}

这个函数中初始化三个独立的变换矩阵,然后返回三个矩阵的乘积作为最终的变换矩阵。相乘的顺序是通过硬编码写死的。需要注意的是最终变换矩阵是该类的成员属性,你可以通过检查一个脏数据标志位来优化该方法,返回一个存储的矩阵以防止函数最后一次调用时配置上没发生任何变化。

主程序中代码:

Pipeline p;
p.Scale(sinf(Scale * 0.1f), sinf(Scale * 0.1f), sinf(Scale * 0.1f));
p.WorldPos(sinf(Scale), 0.0f, 0.0f);
p.Rotate(sinf(Scale) * 90.0f, sinf(Scale) * 90.0f, sinf(Scale) * 90.0f);
glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, (const GLfloat*)p.GetTrans());

这里是主程序中和上一节不同的地方,我们定义一个Pipeline 对象,通过该对象的成员函数来完成缩放、平移、旋转变换。

运行效果

可以看到四面体在屏幕上同时移动、旋转、变大变小。

OpenGL编程逐步深入(十一)组合变换的更多相关文章

  1. 用MFC实现OpenGL编程

    一.OpenGL简介 众所周知,OpenGL原先是Silicon Graphics Incorporated(SGI公司)在他们的图形工作站上开发高质量图像的接口.但最近几年它成为一个非常优秀的开放式 ...

  2. OpenGL编程指南(第七版)

    OpenGL编程指南(第七版) 转自:http://blog.csdn.net/w540982016044/article/details/21287645 在接触OpenGL中,配置显得相当麻烦,特 ...

  3. 编译opengl编程指南第八版示例代码通过

    最近在编译opengl编程指南第八版的示例代码,如下 #include <iostream> #include "vgl.h" #include "LoadS ...

  4. C#编程总结(十一)数字证书

    C#编程总结(十一)数字证书 之前已经通过文章介绍了数字证书的基础知识,包括加密和数字签名. 具体可见: 1.C#编程总结(七)数据加密——附源码 2.C#编程总结(八)数字签名 这里来讲述数字证书的 ...

  5. 在 Mac OS X Yosemite 10.10.5 上配置 OpenGL 编程环境

    这个教程主要参考了youtube上的视频 Getting Started in OpenGL with GLFW/GLEW in Xcode 6 ,这个视频有点问题,不能照搬.本人通过自己摸(瞎)索( ...

  6. [转]VS 2012环境下使用MFC进行OpenGL编程

    我就不黏贴复制了,直接给出原文链接:VS 2012环境下使用MFC进行OpenGL编程 其它好文链接: 1.OpenGL系列教程之十二:OpenGL Windows图形界面应用程序

  7. VS15 openGL 编程指南 配置库 triangle例子

    最近去图书馆借了一本书<OpenGL编程指南(原书第八版)>,今天倒腾了一天才把第一个例子运行出来. 所以,给大家分享一下,希望能快速解决配置问题. 一.下载需要的库文件 首先,我们需要去 ...

  8. Win32 OpenGL 编程( 1 ) Win32 下的 OpenGL 编程必须步骤

    http://blog.csdn.net/vagrxie/article/details/4602961 Win32 OpenGL 编程( 1 ) Win32 下的 OpenGL 编程必须步骤 wri ...

  9. OpenGL编程(一)渲染一个指定颜色的背景窗口

    上次已经搭好了OpenGL编程的环境.已经成功运行了第一个程序.可只是照搬书上的代码,并没弄懂其中的原理.这次通过一个小程序来解释使用GLUT库编写OpenGL程序的过程. 程序的入口 与其他程序一样 ...

  10. [转]OpenGL编程指南(第9版)环境搭建--使用VS2017

    1.使用CMake Configure中选择VS2017 Win64 , Finish: 点击Generate. 2.进入build目录 打开GLFW.sln , 生成解决方案. 打开vermilio ...

随机推荐

  1. 葡萄城公布新版ActiveReports 9报表控件和报表server

    2014年11月10日---葡萄城宣布正式公布ActiveReports9,包含了三种报表模型:RDL报表.页面报表.区域报表.对于ActiveReports中的这个最新版本号中,我们专注于提高产品的 ...

  2. Linux以下的两种文件锁

    文件锁是一种文件读写机制.在不论什么特定的时间仅仅同意一个进程訪问一个文件. 利用这样的机制可以使读写单个文件的过程变得更安全. 在这篇文章中.我们将探讨Linux中不同类型的文件锁,并通过演示样例程 ...

  3. Codeforces Round #286 (Div. 1) B. Mr. Kitayuta&#39;s Technology (强连通分量)

    题目地址:http://codeforces.com/contest/506/problem/B 先用强连通判环.然后转化成无向图,找无向图连通块.若一个有n个点的块内有强连通环,那么须要n条边.即正 ...

  4. 蓝桥杯 - 带分数 (DFS)

      历届试题 带分数   时间限制:1.0s   内存限制:256.0MB        问题描写叙述 100 能够表示为带分数的形式:100 = 3 + 69258 / 714. 还能够表示为:10 ...

  5. Educational Codeforces Round 6 C. Pearls in a Row set

    C. Pearls in a Row There are n pearls in a row. Let's enumerate them with integers from 1 to n from ...

  6. ES线程池设置

    每个Elasticsearch节点内部都维护着多个线程池,如index.search.get.bulk等,用户可以修改线程池的类型和大小,线程池默认大小跟CPU逻辑一致 一.查看当前线程组状态 cur ...

  7. Oracle RAC --安装图解

    规划:所用linux系统以虚拟化方式安装在esx上,配置有内网的yum源,各分配有16G存储,下面为简单拓扑图 一,下载软件1,地址:http://www.oracle.com/technology/ ...

  8. ORA-01261: Parameter db_recovery_file_dest destination string cannot be translat

    查看了Oracle的初始化文件. $cat /oracle/oms/102_64/dbs/initSID.ora 如:db_recovery_file_dest='/oracle/oms/flash_ ...

  9. Maven远程仓库:pom依赖以及jar包下载

    Maven远程仓库:pom依赖xml配置以及jar包下载: 地址1: http://mvnrepository.com/ 地址2: http://172.16.163.52:8081/nexus/#w ...

  10. CF 286(div 2) B Mr. Kitayuta's Colorful Graph【传递闭包】

    解题思路:给出n个点,m条边(即题目中所说的两点之间相连的颜色) 询问任意两点之间由多少种不同的颜色连接 最开始想的时候可以用传递闭包或者并查集来做,可是并查集现在还不会做,就说下用传递闭包来做的这种 ...