OpenGL编程逐步深入(十一)组合变换
准备知识
在前面的几节教程中,我们已经提到过几种变换,为物体在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编程逐步深入(十一)组合变换的更多相关文章
- 用MFC实现OpenGL编程
一.OpenGL简介 众所周知,OpenGL原先是Silicon Graphics Incorporated(SGI公司)在他们的图形工作站上开发高质量图像的接口.但最近几年它成为一个非常优秀的开放式 ...
- OpenGL编程指南(第七版)
OpenGL编程指南(第七版) 转自:http://blog.csdn.net/w540982016044/article/details/21287645 在接触OpenGL中,配置显得相当麻烦,特 ...
- 编译opengl编程指南第八版示例代码通过
最近在编译opengl编程指南第八版的示例代码,如下 #include <iostream> #include "vgl.h" #include "LoadS ...
- C#编程总结(十一)数字证书
C#编程总结(十一)数字证书 之前已经通过文章介绍了数字证书的基础知识,包括加密和数字签名. 具体可见: 1.C#编程总结(七)数据加密——附源码 2.C#编程总结(八)数字签名 这里来讲述数字证书的 ...
- 在 Mac OS X Yosemite 10.10.5 上配置 OpenGL 编程环境
这个教程主要参考了youtube上的视频 Getting Started in OpenGL with GLFW/GLEW in Xcode 6 ,这个视频有点问题,不能照搬.本人通过自己摸(瞎)索( ...
- [转]VS 2012环境下使用MFC进行OpenGL编程
我就不黏贴复制了,直接给出原文链接:VS 2012环境下使用MFC进行OpenGL编程 其它好文链接: 1.OpenGL系列教程之十二:OpenGL Windows图形界面应用程序
- VS15 openGL 编程指南 配置库 triangle例子
最近去图书馆借了一本书<OpenGL编程指南(原书第八版)>,今天倒腾了一天才把第一个例子运行出来. 所以,给大家分享一下,希望能快速解决配置问题. 一.下载需要的库文件 首先,我们需要去 ...
- Win32 OpenGL 编程( 1 ) Win32 下的 OpenGL 编程必须步骤
http://blog.csdn.net/vagrxie/article/details/4602961 Win32 OpenGL 编程( 1 ) Win32 下的 OpenGL 编程必须步骤 wri ...
- OpenGL编程(一)渲染一个指定颜色的背景窗口
上次已经搭好了OpenGL编程的环境.已经成功运行了第一个程序.可只是照搬书上的代码,并没弄懂其中的原理.这次通过一个小程序来解释使用GLUT库编写OpenGL程序的过程. 程序的入口 与其他程序一样 ...
- [转]OpenGL编程指南(第9版)环境搭建--使用VS2017
1.使用CMake Configure中选择VS2017 Win64 , Finish: 点击Generate. 2.进入build目录 打开GLFW.sln , 生成解决方案. 打开vermilio ...
随机推荐
- 【跟我一步一步学Struts2】——拦截器
前言 前面提到过拦截器.而且说拦截器仅仅能拦截Action.这里拦截器的调用体现了责任链模式.为什么说体现了责任链模式呢? 以下的一段话说的非常明确: Struts2将整个运行划分成若干同样类型的元素 ...
- python 3.x 学习笔记11 (静态、类、属性、特殊成员方法)
1.静态方法通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法.静态方法是不可以访问实例变量或类变量的即没有self,一个不能访问实例变量和类变量的方法,其实相当于跟类本身已经没什 ...
- 问题集锦 ~ PS
#画正圆 按住鼠标左键 + shift (+alt 从中心扩散) #透明背景 选中选区,图层转换为智能对象,栅格化,按 delete #抠图 魔术棒,套索工具 #填充选区颜色 Ctrl+Del #填充 ...
- Eclipse中將Java项目转变为Java Web项目
1.在项目上点击右键=>properties,在Project Facets配置项中,勾选Dynamic Web Module.Java.JavaScript选项. 2.用记事本打开项目目录下的 ...
- hiho 1572 - set.upper_bound,排序树
链接 小Hi家的阳台上摆着一排N个空花盆,编号1~N.从第一天开始,小Hi每天会选择其中一个还空着花盆,栽种一株月季花,直到N个花盆都栽种满月季. 我们知道小Hi每天选择的花盆的编号依次是A1, A2 ...
- Dapper基础知识二
在下刚毕业工作,之前实习有用到Dapper?这几天新项目想用上Dapper,在下比较菜鸟,这块只是个人对Dapper的一种总结. 2,如何使用Dapper? 首先Dapper是支持多种数据库的 ...
- 洛谷 P1462 通往奥格瑞玛的道路 二分 最短路
#include<cstdio> #include<queue> #include<cstring> #include<algorithm> using ...
- oracle查询优化之子查询条件优化
环境:oracle 11g 现有a表与b表通过a01字段关联,要查询出a表的数据在b表没有数据的数据:sql如下 ) ) 因为flag是虚拟字段没有走不了索引导致这条sql执行起来特别慢 310W条数 ...
- How Javascript works (Javascript工作原理) (五) 深入理解 WebSockets 和带有 SSE 机制的HTTP/2 以及正确的使用姿势
个人总结: 1.长连接机制——分清Websocket,http2,SSE: 1)HTTP/2 引进了 Server Push 技术用来让服务器主动向客户端缓存发送数据.然而,它并不允许直接向客户端程序 ...
- [agc016d]xor replace
题意: 题解: 棒棒的神仙题...这题只是D题???(myh:看题五分钟,讨论两小时) 首先这个异或和是假的,比如我现在有$a=(a_1,a_2,a_3,a_4)$,操作一下$a_2$,就变成了$a= ...