【一步步学OpenGL 11】 -《复合变换》

标签: opengl C++ GLSL
3308人阅读 评论(15) 收藏 举报
分类:

教程11

复合变换

原文: http://ogldev.atspace.co.uk/www/tutorial11/tutorial11.html

CSDN完整版专栏: http://blog.csdn.net/column/details/13062.html


注:这个教程要添加更多的头文件了,基本上全了,其中用到了AntTweakBar这个库,安装很简单,和之前其他插件一样,去官网下载资源包,里面lib文件夹内已经有了编译好的dll和lib库,也可以运行vs项目自己重新编译。
将lib文件夹里面的dll文件复制到Windows/System32目录下,lib文件复制到比如我的C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\lib目录下,另外将include文件夹里面的AntTweakBar.h头文件复制到比如我的C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include目录下就可以了。

背景

在前面的几个教程中我们建立了几种基本图形变换是我们能灵活的将物体移动到3d世界的任意地方。我们还有很多要学(相机控制和透视投影等等),但你可能也已经想到,复合的图形变换也是必的。多数情况下,你会想缩放物体来和3d世界相适应,旋转物体到合适的方向,移动物体到某个地方等等。到现在我们已经实践了每一次的单一图形变换。为了实现上面一系列的变换,我们需要对顶点位置左乘第一个变换矩阵,然后得到的结果再左乘下一个变换矩阵等等,将所有变换矩阵都左乘顶点位置之后实现多个变换。一种笨办法就是在shader中应用每一个变换矩阵实现所有的变换,但这样很低效,因为对于所有顶点这些矩阵都是一样的,只有顶点的位置发生变化,这样要不断重复对每个顶点位置进行这一系列的矩阵相乘操作。幸运的是线性代数中有一些规则可以说让我们的生活更加简易,他告我们对于给定的一组矩阵M0…Mn和一个向量V有下面的等式换算:

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

所以如果你令:

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

那么:

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

这意味着我们可以一次性计算N,然后把它作为一致变量传给shader和每一个顶点位置相乘完成所有的变换,这个只需要GPU对每个顶点进行一次矩阵/向量相乘操作。

当计算N的时候怎样安排每个变换矩阵的先后顺序呢?你首先要记住向量最开始先是被最右边的矩阵左乘的(上面的例子就是M0)。然后向量被从右边到左边所有的变换矩阵所变换。在3d图形中你通常希望先缩放物体,然后旋转它,然后平移,之后再进行camera转换最后投影到2d屏幕。让我们先看先旋转后平移会怎样:

这里写图片描述

然后再看先平移后旋转会怎样:

这里写图片描述

可以看出,如果先平移物体的话会很难来设置物体的最终位置,因为当你将物体远离坐标原点后,再旋转物体会同时造成物体的平移效果(是绕原点旋转,而不是绕自身旋转了),这是我们希望能避免的。通过先旋转后移动可以避免这两个操作的相互依赖性,这也是尽量围绕原点对称建模的原因,那样当你缩放或者旋转物体不会产生副作用,缩放和旋转后物体依然保持和之前一样对称。

源代码详解

(1)
#define ToRadian(x) ((x) * M_PI / 180.0f)

#define ToDegree(x) ((x) * 180.0f / M_PI)

在这个教程中我们开始要使用具体的角度值了。标准C语言库中三角函数使用弧度值作为参数。上面的宏定义可以实现角度和弧度之间的转换

(2)

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

这里是定义了矩阵相乘的操作符。可以看到结果矩阵的值依次是左边矩阵每一行和右边矩阵对应每一列的点乘。这个矩阵相乘的操作对实现管线类十分重要。

(3)


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

管线类抽象出了一个物体的所有变换的数据信息。现在有3个私有vector成员变量分别来存储物体在世界空间中的缩放比例,位置和每个像素的旋转角度。另外有接口函数来设置他们的值,以及可以获取表示所有变换最终的复合变换矩阵。

(4)

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

这个函数初始化了那三个独立的变换矩阵并把它们一个一个的相乘然后返回最终的积。注意相乘的顺序是和上面描述的一样固定死的。如果你想更灵活一点你可以设置一个定义变换顺序的位掩码。还要注意它总是将最终的复合变换矩阵作为一个陈成员变量。你可以通过检查标记来做优化,当上一次的变换函数没有任何变化时可以返回上一次存储的变换矩阵,避免重复计算。

这个函数按照之前教程的内容讲的使用私有方法来产生不同的变换。在下一个教程中这个类还将被扩展,来进行相机控制和透视变换。

(5)

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管线类对象,初始化配置好之后传递给shader。通过调整参数来看最终图像的效果。

示例Demo

用到了matrix4x4头文件中的扩展工具函数;


#include <stdio.h>
#include <string.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管线类对象,初始化配置好之后传递给shader
    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 main(int argc, char** 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;
}

shader.vs和shader.fs脚本不变

shader.vs


#version 330

layout (location = 0) in vec3 Position;

uniform mat4 gWorld;

out vec4 Color;

void main()
{
    gl_Position = gWorld * vec4(Position, 1.0);
    Color = vec4(clamp(Position, 0.0, 1.0), 1.0);
}

shader.fs


#version 330

in vec4 Color;

out vec4 FragColor;

void main()
{
    FragColor = Color;
}

运行效果

缩放,旋转和平移的复合变换效果,可自行设计调整,可以看到彩色四面体在空间中自由的灰来灰去打转。

这里写图片描述

查看评论

用VC++编制FTP客户端应用程序

用VC++编制FTP客户端应用程序  FTP协议将使用两条单独的TCP连接,一条专用于发送FTP命令,另一条则专用于传递数据。初始建立连接时,服务器在21号端口上接收来自客户端的命令连接。当需要传送数...
  • ghj1976
  • ghj1976
  • 2001-08-20 11:36:00
  • 1509

OpenGL学习脚印: 模型变换(model transformation)

写在前面 前面为本节内容准备了向量和矩阵、线性变换等内容,本节开始学习OpenGL中的坐标处理。OpenGL中的坐标处理过程包括模型变换、视变换、投影变换、视口变换等内容,这个主题的内容有些...
  • ziyuanxiazai123
  • ziyuanxiazai123
  • 2016-05-29 17:12:55
  • 10462

OpenGL(三)图形变换之几何变换

通常,为了把一组图形融合为一个场景,必须把他们按照批次之间的关系和与观察这的关系排列起来,这就要用到变换。变换使得能够把3D坐标投影到2D场景成为可能,变换包括旋转对象,移动对象,甚至拉伸、压缩和玩去...
  • qq_33850438
  • qq_33850438
  • 2016-07-08 11:18:14
  • 2764

OPENGL + GLUT + Anttweakbar + FLTK

先说glut,虽说glut有点过时,作者很久没更新了,但确实很轻量级很可靠,glut最大的问题在于点右上角的叉,程序就直接退出,单窗口可能觉得没什么,但是要是多窗口的情况,关一个子窗口导致整个程序退出...
  • iscassucess
  • iscassucess
  • 2012-12-16 11:46:01
  • 1618

AntTweakBar with OpenGL

1,下载AntTweakbar,http://sourceforge.net/projects/anttweakbar/files/latest/download?source=dlp 2,用VS2...
  • seamanj
  • seamanj
  • 2015-10-06 21:35:13
  • 1733

(16)二维图形复合变换

有些变换仅用一种基本变换是不能实现的,必须由两种或多种基本变换组合才能实现。这种由多种基本变换组合而成的变换称之为复合变换,相应的变换矩阵称作为复合变换矩阵。 比如:已知三角形各顶点坐标为(10, ...
  • zl908760230
  • zl908760230
  • 2016-12-31 22:30:31
  • 463

计算机图形学(四)几何变换_4_二维复合变换_4_二维刚体变换

如果一个变换矩阵仅包含平移和旋转参数,则它是一个刚体变换矩阵(rigid-body transforma-tion matrix)。二维刚体变换矩阵的一般形式为矩阵1:其中,4个元素rjk是多重旋转项...
  • heyuchang666
  • heyuchang666
  • 2017-04-26 14:54:11
  • 1149

ogldev 教程环境配置

1 .右键工程→"Properties"→"C/C++"下,"General"→"Additional Include Directories" 填入"X:\ImageMagick-6.8.5";"...
  • yanziguilai
  • yanziguilai
  • 2013-08-03 13:40:36
  • 1955

OpenGL 按照三角形仿射变换并贴图渲染(基于shader)(二)

OpenGL 按照三角形仿射变换并贴图渲染(正常渲染或离屏渲染以及异步优化)(二)标签(空格分隔):CG opengl前面我们详细介绍过OpenGL 按照三角形仿射变换并贴图渲染的过程(一),是基于O...
  • hust_sheng
  • hust_sheng
  • 2017-08-28 19:48:25
  • 596

ogldev_tutorial01-04

  • 2015年04月09日 23:00
  • 6.37MB
  • 下载
    个人资料
    专栏达人 持之以恒
    等级:
    访问量: 53万+
    积分: 5635
    排名: 5832
    关于我
    人生苦短,道阻且艰;修行不易,且行且努力。

    【专业兴趣】:游戏开发,图形学,图像处理与计算机视觉,iOS平台

    【专业技能】:iOS,游戏开发

    【个人主页】:信厚的独立博客
    【个人项目】:个人项目
    【GitHub】:jiangxh1992
    【邮箱】:jiangxinhou艾特outlook点com
    博客专栏