openGL之glsl入门6--画三维图魔方、圆柱体

     这一章介绍坐标变换与矩阵相关内容,对应红宝书第5章内容,并通过两个简单的例子展示矩阵变换的效果。

1. 坐标变换

    变换的类型有多种,包括视图、模型、投影、视口变换等,概念可以参照红宝书5.1章节,概念虽不同,但最终作用到顶点坐标的方式是一致的,都是对顶点坐标进行运算(直接加减乘除或者使用矩阵运算),弄清楚这些概念,有利于清晰描述变换的效果,虽然不同的变化都可以得到相同的结果(显示器上输出),但还是应该在合适的场景使用合适的变换达成目的,使程序更易理解。

  下面介绍最基本的坐标变换,这里用的是二维,三维的原理是一致的,只是复杂一些。 

1.1 平移(translate)

    平移很好理解,沿哪个轴平移,对应的坐标直接加对应长度就可以了

    如坐标(xy) 沿x轴和y轴分别平移TxTy(正轴方向为正,负轴方向为负)shader描述如下:

gl_Position = vec4(position.x +Tx, position.y + Ty,position.z,1.0); 

1.2 缩放(scale)

    缩放也很好理解,沿哪个轴方向缩放,乘上对应的值即可

    如坐标(x,y)沿x轴和y轴分别缩放SxSy(缩写<1,放大>1),shader描述如下:

gl_Position = vec4(position.x * Sx,position.y * Sy,position.z ,1.0);

    使用这种方法进行平移与缩放前面的章节都有用到。 

1.3 旋转(rotate)

    旋转稍微复杂一些,2维情况下,绕原点旋转(即3维情况的Z轴),暗含的条件是该点到原点的距离R不变,旋转公式的推导用的是基本的三角公式,推导过程比较简短,这里写一下。

    坐标点( x ,y ) 可以表示为( R*cosα R * sinα ) 其中α为点到x轴的夹角(逆时针方向)。即:

x = R * cosα;
y = R * sinα;

    逆时针 旋转β角度后得到( x',y' ) ,可以用以下公式表示与推导:

x' =R *cos(α+β) = R * (cosαcosβ - sinαsinβ) = xcosβ - ysinβ;
y' = R *sin(α+β) = R * (sinαcosβ + cosαsinβ) = ycosβ + xsinβ;

    所以点( x ,y ) 旋转角度θ后,shader可以写成这样:

gl_Position = vec4(position. x * cosθ - position. y *sinθ ,position. y * cosθ + position. x * sinθ ,position.z ,1.0); 

    以上几种坐标变换时都需要指定坐标轴的(如没有指定,一定要搞清楚函数的默认值,如scale(0.5),要弄清楚是x轴缩小一半还是整体缩小一半),后续可以看到坐标变换需要为每个轴指定变换值,如rotate(x,y,z)则表明绕x轴旋转x度,y轴旋转y度等。

    以上几种坐标变换是可以叠加的,可以同时平移、缩放、旋转,这几个操作,需要注意叠加顺序,先平移后缩放与先缩放后平移得出的坐标肯定不一样了,缩放的同时把平移的坐标也进行缩放了,旋转和平移一样的存在先后问题。 

2. 矩阵坐标变换

    使用上述的方式实现平移和缩放还比较简单,用来旋转就会复杂不少,如果多种操作组合的话,最终的公式就比较复杂了,很容易出错,实际编程过程中,坐标变换都是通过矩阵运算来完成的,而openGL显然对矩阵运算的支持非常好(glsl有很多向量vec*与矩阵mat*的定义及相关运算),前面的例子是供大家比照用的,如果一开始就使用矩阵运算来进行坐标变换,对没有计算机图形学基础的人来说要难上手一些,初步了解矩阵不难,内容也不多,这里介绍一下。

    矩阵基本的计算公式:

    矩阵相乘规则:mn列的矩阵Anp列的矩阵B相乘,会得到mp列的矩阵C,所以矩阵相乘要求矩阵A的列与矩阵B的行一致,得到的结果是,矩阵C的每个值为矩阵A的每一行的元素与矩阵B的每一列元素相乘后的和。上面的矩阵A的第一行位a b c d,矩阵B的第一列为x y z w,相乘的结果是对应位置相乘再相加,即ax+by+cz+dw

    矩阵可以多级相乘,C = A*B

    矩阵相乘不满足交换律(A*B != B*A 如上面的矩阵,交换后A行与B列都不一致了,没法乘),但可以使用结合律, A *( B* C) = (A * B) * C

    可以通过矩阵来实现平移、缩放、旋转等,看下面平移的例子

    可以看到矩阵A 对角线方向为1.0,除最后一列外,其他都是0.0,这个就是平移矩阵,矩阵A最后一列3个值对应x轴、y轴及z轴方向的平移。图中矩阵相乘结果是点(xyz)沿y轴平移0.3 ,公式y' = 0.0*x+1.0*y+0.0*z+0.3*1.0 = y+0.3

    缩放矩阵改动的是对角线的值,旋转矩阵稍微复杂一些,但与平移矩阵的原理是一致的,这些都可以参考红宝书5.2章节。

 

注意:GLSL里的矩阵,实际上都使用一维数组表示,且都是列优先(展开成一维数组时,先列后行,c语言2维数组先行后列不一致),如果自己构建矩阵,像上面的矩阵A,使用一维数组构建时,代码如下(注意0.3的位置)

float MTA[] = {1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.3.0.0,1.0}; 

3. 矩阵操作库

    自己构建各种矩阵当然是可行的,但我们一般不这么做,一般使用现成的库或者自己编写库来实现各种矩阵的构建,红宝书里用的vmath库,vmath库就一个头文件,你可以在红宝书源码里include目录中找到,即vmath.h,你也可以在http://bartipan.net/vmath/上找到最新版本。为保证源码的完整性,本文最后还是贴一下vmath的源码(为方便阅读,放到最后面,有点长)。

    vmath库的api如下:

/**
@brief: 生成四维旋转 矩阵
@param angle:为旋转的角度,单位为度。
@param x,y,z:为对应xyz轴的布尔值变量,如x为1.0表示沿x轴旋转。
@param return:返回四维旋转矩阵
*/
mat4 rotate(float angle,float x,float y, float z );

    这个函数用法可以参考openGLapi中的glRotatefglRotatef是直接作用到3D模型上,而rotate返回一个四维旋转矩阵,需与顶点坐标相乘达成旋转目的。 

/**
@brief: 生成四维平移矩阵
@param x,y,z:为对应xyz轴平移的坐标,负数为负轴方向。
@param return:返回四维平移 矩阵
*/
mat4  translate(float x,float y, float z );

    可参考openGLAPI中的 glTranslatef函数。 

/**
@brief: 生成四维缩放矩阵
@param x,y,z:为对应xyz轴缩放的比例,>1放大,<1缩小。
@param return:返回四维缩放矩阵
*/
mat4  scale(float x,float y, float z );

    可参考openGLAPI中的 glscalef函数。

注意:原型为scalefloat s)即只有一个参数的时候,是整体缩小模型,而不只是缩小x轴。

 

mat4 frustum(float left, float right, float bottom, float top, float near, float far);
mat4 perspective(float fovy, float aspect, float near , float far );
mat4 lookat(vec3 eye, vec3 center, vec3 up);

    3个函数都是做视图变换的,分别对应openGLglFrustumgluPerspectivegluLookAt函数,这几个函数的参数会比较复杂,网上相关的介绍很多,用法与rotate等函数是一致的。

    多矩阵相乘,最好放到客户端进行(用CPU去算),如放到shader中做,每个点都需算一遍,性能多少有些浪费。多矩阵相乘时,顺序需注意,避免顺序不对导致效果不符合要求的情况。 

4. 画魔方立方体

    魔方是规则的立方体,比较好绘制,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/glut.h>
/* 该头文件请到第6章下载*/
#include "vmath.h"
using namespace vmath;
#define POINT_CNT       6       /* 每条边对应的点数*/
typedef struct
{
    GLfloat x;
    GLfloat y;
    GLfloat z;
} POINT_S;
static const GLchar * vertex_source =
    "#version 330 core\n"
    "uniform mat4 pos_matrix;\n"
    "uniform mat4 face_matrix;\n"
    "uniform vec3 color_vec;\n"
    "layout (location = 0) in vec3 in_position;\n"
    "out vec3 color_frag;\n"
    "void main(void)\n"
    "{\n"
        "       if( abs(in_position.z) == 0.49)\n"
    "           color_frag = vec3(0.0,0.0,0.5);\n"
        "       else\n"
    "           color_frag = color_vec;\n"
        "       gl_Position = pos_matrix * face_matrix * vec4(in_position,1.0);\n"
    "}\n";
static const GLchar * frag_source =
    "#version 330 core\n"
    "in vec3 color_frag;\n"
    "out vec4 color;\n"
    "void main(void)\n"
    "{\n"
    "    color = vec4(color_frag,1.0);\n"
    "}\n";
void loadShader(GLuint program, GLuint type, const GLchar * source)
{
    GLint status = 0;
    const GLchar * shaderSource[] = {source};
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, shaderSource, 0);
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    glAttachShader(program, shader);
}
GLuint vao, vbo, ebo;
mat4 pos_matrix,face_matrix;
GLuint pos_matrix_idx,face_matrix_idx,color_vec_idx;
void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glBindBuffer(GL_ARRAY_BUFFER, vao);
    glUniformMatrix4fv(pos_matrix_idx, 1, GL_FALSE, pos_matrix);
        static float pos[6][6] =
        {
                        {0.0f, 0.0f, 0.0f, 1.0f,1.0f,0.0f}, /* 顶面黄*/
                        {90.0f, 1.0f, 0.0f, 0.0f,0.0f,1.0f}, /* 侧面绿*/
                        {-90.0f, 1.0f, 0.0f, 0.0f,1.0f,0.0f},/* 侧面红*/
                        {90.0f, 0.0f, 1.0f, 1.0f,0.0f,0.0f}, /* 侧面蓝*/
                        {-90.0f, 0.0f, 1.0f, 1.0f,0.0f,1.0f},/* 侧面橙*/
                        {180.0f, 1.0f, 0.0f, 1.0f,1.0f,1.0f}, /* 底面白*/
        };
        for( int i =0;i< 6;i++)
        {
                /* 告知各个面颜色*/
                glUniform3fv(color_vec_idx,1,&pos[i][3]);
                /* 坐标只定义了顶面,其他面通过旋转得到*/
                face_matrix = vmath::rotate(pos[i][0], pos[i][1],pos[i][2],0.0f);
            glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
                /* 使用正方形绘制,每个正方形个顶点*/
                glDrawElements(GL_QUADS, 10*4, GL_UNSIGNED_INT, (GLvoid *)(0));
        }
    glutSwapBuffers();
}
void init(void)
{
    int i = 0, j = 0;
    POINT_S * p = NULL;
        /* 总宽度,一个格子,线宽*/
        float quad[POINT_CNT] = {0.025f,0.325f,0.350f,0.650f,0.675f,0.975f};
        POINT_S groud[4] =
        {
                        {-0.5f,0.5f,0.49f},
                        {0.5f,0.5f,0.49f},
                        {0.5f,-0.5f,0.49f},
                        {-0.5f,-0.5f,0.49f},
        };
    /* 只分配一个面的顶点数据,其他面通过旋转绘制,额外的个点画面的底色*/
    POINT_S vertex_list[POINT_CNT*POINT_CNT + 4];
    for (i = 0; i < POINT_CNT ; i++)
    {
                for(j = 0;j< POINT_CNT;j++)
                {
                p = &vertex_list[i*POINT_CNT + j];
                        p->x = quad[j] - 0.5f; /* quad从开始,挪中间*/
                        p->y = quad[i] - 0.5f;
                        p->z = 0.5;
                }
    }
        memcpy(vertex_list+POINT_CNT*POINT_CNT,groud,sizeof(POINT_S)*4);
    /* 绘制索引,一个面个正方形*/
    GLuint index_list[10][4] =
    {
                {36,37,38,39},  /* 先画底色框,再画上面的格子*/
                {0,1,7,6},{2,3,9,8},{4,5,11,10},
                {12,13,19,18},{14,15,21,20},{16,17,23,22},
                {24,25,31,30},{26,27,33,32},{28,29,35,34},
   };
    GLuint program = glCreateProgram();
    loadShader(program, GL_VERTEX_SHADER, vertex_source);
    loadShader(program, GL_FRAGMENT_SHADER, frag_source);
    glLinkProgram(program);
    glUseProgram(program);
    glGenBuffers(1, &vao);
    glBindBuffer(GL_ARRAY_BUFFER, vao);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_list), vertex_list, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)0);
    glEnableVertexAttribArray(0);
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_list), index_list, GL_STATIC_DRAW);
    /* 定义旋转数组,主要存放当前旋转信息*/
        face_matrix_idx = glGetUniformLocation(program, "face_matrix");
        color_vec_idx = glGetUniformLocation(program, "color_vec");
        pos_matrix_idx = glGetUniformLocation(program, "pos_matrix");
    pos_matrix = vmath::rotate(30.0f, 1.0f, 1.0f,0.0f);
    glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
    glClearDepth(1.0);
    glEnable(GL_DEPTH_TEST);
    glLineWidth(1.0);
}
void keyboard(unsigned char key, int x, int y)
{
    switch (key)
    {
        case '-':
            pos_matrix *= vmath::scale(0.95f);
            break;
        case '=':
        case '+':
            pos_matrix *= vmath::scale(1.05f);
            break;
        default:
            break;
    }
    glutPostRedisplay();
}
void specialKey(GLint key, GLint x, GLint y)
{
    float step = 2.0f;
    switch (key)
    {
        case GLUT_KEY_UP:
            pos_matrix *= vmath::rotate(step, 1.0f, 0.0f, 0.0f);
            break;
        case GLUT_KEY_DOWN:
            pos_matrix *= vmath::rotate(-1.0f * step, 1.0f, 0.0f, 0.0f);
            break;
        case GLUT_KEY_LEFT:
            pos_matrix *= vmath::rotate(step, 0.0f, 1.0f, 0.0f);
            break;
        case GLUT_KEY_RIGHT:
            pos_matrix *= vmath::rotate(-1.0f * step, 0.0f, 1.0f, 0.0f);
            break;
        default:
            break;
    }
    glutPostRedisplay();
}
int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(400, 400);
    glutCreateWindow("MagicCube");
    glewInit();
    init();
    glutDisplayFunc(display);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKey);
    glutMainLoop();
    return 0;
}

效果如下:

 

    魔方的每个面由底色正方形和9个小的正方形组成,程序里只设置了一个面的顶点坐标,其他面都是通过旋转画出来的,画魔方各个面的时候,需先告知每个面的旋转角度和颜色,代码中的角度和颜色通过一个数组给出。相比二维绘图,绘制三维图都用的三维坐标,相关参数(glVertexAttribPointer 数据宽度,顶点着色器in变量的类型等)需匹配,查看时,旋转才能看到效果,其他的与二维绘制差不多。

    有一个细节需注意,客户端代码里并没有给出底色的颜色,而是顶点着色器代码里做判断来设置底色的颜色(配合背景的z坐标一起使用)。

 if( abs(in_position.z) == 0.49)\n"
     color_frag = vec3(0.0,0.0,0.5);\n"
 else
     color_frag = color_vec;

    为什么是0.49,并没有特殊的含义,就只是利用了深度测试特性,把底色与上面的小正方形错开,避免在同一平面绘制两种颜色,同一区域画颜色时(即把底色坐标的z坐标也设置成0.5时),有两种情况:

1. 开启深度测试(glEnable(GL_DEPTH_TEST);),深度一致的情况下,底色和魔方格子画在同一区域时,颜色是随机的,下左图就是这种情况。

2. 不开启深度测试,先绘制的会作为底色,后面绘制的会叠加在上面,下右图就是这种情况,无论如何转动,最后绘的白色总能显示出来。


    实际使用中,如果不是做颜色混合,同一区域(坐标平面与区域一致)不要重复绘制,浪费资源,且结果不好预期。 

5. 画圆柱体

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/glut.h>
#include "vmath.h"
using namespace vmath;
/* 绘制步长,单位为度*/
#define STEP                    6
#define SAMPLE_CNT              (360/STEP)
static const GLchar * vertex_source =
    "#version 330 core\n"
    "uniform mat4 in_matrix;\n"
    "layout (location = 0) in vec3 in_position;\n"
    "layout (location = 1) in vec3 in_color;\n"
    "flat out vec3 frag_color;\n"
    "void main(void)\n"
    "{\n"
    "    gl_Position = in_matrix * vec4(in_position,1.0);\n"
    "    frag_color = in_color;\n"
    "}\n";
static const GLchar * frag_source =
    "#version 330 core\n"
    "flat in vec3 frag_color;\n"
    "out vec4 color;\n"
    "void main(void)\n"
    "{\n"
    "    color = vec4(frag_color,1.0);\n"
    "}\n";
void loadShader(GLuint program, GLuint type, const GLchar * source)
{
    GLint status = 0;
    const GLchar * shaderSource[] = {source};
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, shaderSource, 0);
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    glAttachShader(program, shader);
}
GLuint vao, vbo;
GLuint matrix_idx;
mat4 pos_matrix;
GLuint ebo;
void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glBindBuffer(GL_ARRAY_BUFFER, vao);
    glUniformMatrix4fv(matrix_idx, 1, GL_FALSE, pos_matrix);
        
        /* 画圆柱体侧面*/
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 2 * SAMPLE_CNT);
        /* 画圆柱顶面和底面,底面使用索引偏移量的方式绘制*/
        glDrawElements(GL_TRIANGLE_FAN, SAMPLE_CNT, GL_UNSIGNED_INT, (GLvoid *)(0));
        glDrawElementsBaseVertex(GL_TRIANGLE_FAN, SAMPLE_CNT, GL_UNSIGNED_INT, (GLvoid *)(0),1);
        
    glutSwapBuffers();
}
void init(void)
{
    int i = 0;
    float p = 0.0, r = 0.5;
    GLfloat vertex_list[2 * 360 / STEP][3];
    GLfloat color_list[2 * 360 / STEP][3];
        GLuint index_list[360 / STEP];
        /* 确定顶面和底面的坐标*/
    for (i = 0; i < SAMPLE_CNT * 2; i += 2)
    {
        p = i * STEP * 3.14 / 180;
        vertex_list[i][0] = cos(p) * r;
        vertex_list[i][1] = sin(p) * r;
        vertex_list[i][2] = 0.5f;
        vertex_list[i + 1][0] = cos(p) * r;
        vertex_list[i + 1][1] = sin(p) * r;
        vertex_list[i + 1][2] = -0.5f;
    }
        /* 确定每个点的坐标*/
    for (i = 0; i < SAMPLE_CNT * 2; i++)
    {
        if ((i / 2) % 2 == 0)
        {
            color_list[i][0] = 0.5f;
            color_list[i][1] = 0.0f;
            color_list[i][2] = 1.0f;
        }
        else
        {
            color_list[i][0] = 1.0f;
            color_list[i][1] = 1.0f;
            color_list[i][2] = 0.0f;
        }
    }
        /* 确定顶面的索引*/
    for (i = 0; i < SAMPLE_CNT; i++)
    {
                index_list[i] = i+2;
    }   
    GLuint program = glCreateProgram();
    loadShader(program, GL_VERTEX_SHADER, vertex_source);
    loadShader(program, GL_FRAGMENT_SHADER, frag_source);
    glLinkProgram(program);
    glUseProgram(program);
    glGenBuffers(1, &vao);
    glBindBuffer(GL_ARRAY_BUFFER, vao);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_list) + sizeof(color_list), NULL, GL_STATIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertex_list), vertex_list);
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertex_list), sizeof(color_list), color_list);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(vertex_list)));
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_list), index_list, GL_STATIC_DRAW);
    matrix_idx = glGetUniformLocation(program, "in_matrix");
    pos_matrix = vmath::rotate(30.0f, 1.0f, 1.0f, 0.0f);
        /* 使用非渐变的方式绘制颜色*/
        glProvokingVertex(GL_FIRST_VERTEX_CONVENTION); 
    glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
    glClearDepth(1.0);
    glEnable(GL_DEPTH_TEST);
}
void keyboard(unsigned char key, int x, int y)
{
    switch (key)
    {
        case '-':
            pos_matrix *= vmath::scale(0.9f);
            break;
        case '=':
        case '+':
            pos_matrix *= vmath::scale(1.1f);
            break;
        default:
            break;
    }
    glutPostRedisplay();
}
void specialKey(GLint key, GLint x, GLint y)
{
    float step = 2.0f;
    switch (key)
    {
        case GLUT_KEY_UP:
            pos_matrix *= vmath::rotate(step, 1.0f, 0.0f, 0.0f);
            break;
        case GLUT_KEY_DOWN:
            pos_matrix *= vmath::rotate(-1.0f * step, 1.0f, 0.0f, 0.0f);
            break;
        case GLUT_KEY_LEFT:
            pos_matrix *= vmath::rotate(step, 0.0f, 1.0f, 0.0f);
            break;
        case GLUT_KEY_RIGHT:
            pos_matrix *= vmath::rotate(-1.0f * step, 0.0f, 1.0f, 0.0f);
            break;
        default:
            break;
    }
    glutPostRedisplay();
}
int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(500, 500);
    glutCreateWindow("Cylinder");
    glewInit();
    init();
    glutDisplayFunc(display);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKey);
    glutMainLoop();
    return 0;
}

效果如下:

  

在绘制的过程中,顶面、底面和侧面的坐标的坐标是复用的,底面的坐标使用了glDrawElementsBaseVertex函数,采用相对索引方式绘制。


附vmath.h源码

(红宝书里用的vmath库,vmath库就一个头文件,你可以在红宝书源码里include目录中找到,即vmath.h,你也可以在http://bartipan.net/vmath/上找到最新版本。)

#ifndef __VMATH_H__
#define __VMATH_H__


#define _USE_MATH_DEFINES  1 // Include constants defined in math.h
#include <math.h>

namespace vmath
{

template <typename T> 
inline T radians(T angleInRadians)
{
	return angleInRadians * static_cast<T>(180.0/M_PI);
}

template <const bool cond>
class ensure
{
public:
    inline ensure() { switch (false) { case false: case cond: break; } }
};

template <typename T, const int len> class vecN;

template <typename T, const int len>
class vecN
{
public:
    typedef class vecN<T,len> my_type;

    // Default constructor does nothing, just like built-in types
    inline vecN()
    {
        // Uninitialized variable
    }

    // Copy constructor
    inline vecN(const vecN& that)
    {
        assign(that);
    }

    // Construction from scalar
    inline vecN(T s)
    {
        int n;
        for (n = 0; n < len; n++)
        {
            data[n] = s;
        }
    }

    // Assignment operator
    inline vecN& operator=(const vecN& that)
    {
        assign(that);
        return *this;
    }

    inline vecN operator+(const vecN& that) const
    {
        my_type result;
        int n;
        for (n = 0; n < len; n++)
            result.data[n] = data[n] + that.data[n];
        return result;
    }

    inline vecN& operator+=(const vecN& that)
    {
        return (*this = *this + that);
    }

    inline vecN operator-() const
    {
        my_type result;
        int n;
        for (n = 0; n < len; n++)
            result.data[n] = -data[n];
        return result;
    }

    inline vecN operator-(const vecN& that) const
    {
        my_type result;
        int n;
        for (n = 0; n < len; n++)
            result.data[n] = data[n] - that.data[n];
        return result;
    }

    inline vecN& operator-=(const vecN& that)
    {
        return (*this = *this - that);
    }

    inline vecN operator*(const vecN& that) const
    {
        my_type result;
        int n;
        for (n = 0; n < len; n++)
            result.data[n] = data[n] * that.data[n];
        return result;
    }

    inline vecN& operator*=(const vecN& that)
    {
        return (*this = *this * that);
    }

    inline vecN operator*(const T& that) const
    {
        my_type result;
        int n;
        for (n = 0; n < len; n++)
            result.data[n] = data[n] * that;
        return result;
    }

    inline vecN& operator*=(const T& that)
    {
        assign(*this * that);

        return *this;
    }

    inline vecN operator/(const vecN& that) const
    {
        my_type result;
        int n;
        for (n = 0; n < len; n++)
            result.data[n] = data[n] * that.data[n];
        return result;
    }

    inline vecN& operator/=(const vecN& that)
    {
        assign(*this * that);

        return *this;
    }

    inline vecN operator/(const T& that) const
    {
        my_type result;
        int n;
        for (n = 0; n < len; n++)
            result.data[n] = data[n] / that;
        return result;
    }

    inline vecN& operator/(const T& that)
    {
        assign(*this / that);
    }

    inline T& operator[](int n) { return data[n]; }
    inline const T& operator[](int n) const { return data[n]; }

    inline static int size(void) { return len; }

    inline operator const T* () const { return &data[0]; }

protected:
    T data[len];

    inline void assign(const vecN& that)
    {
        int n;
        for (n = 0; n < len; n++)
            data[n] = that.data[n];
    }
};

template <typename T>
class Tvec2 : public vecN<T,2>
{
public:
    typedef vecN<T,2> base;

    // Uninitialized variable
    inline Tvec2() {}
    // Copy constructor
    inline Tvec2(const base& v) : base(v) {}

    // vec2(x, y);
    inline Tvec2(T x, T y)
    {
        base::data[0] = x;
        base::data[1] = y;
    }
};

template <typename T>
class Tvec3 : public vecN<T,3>
{
public:
    typedef vecN<T,3> base;

    // Uninitialized variable
    inline Tvec3() {}

    // Copy constructor
    inline Tvec3(const base& v) : base(v) {}

    // vec3(x, y, z);
    inline Tvec3(T x, T y, T z)
    {
        base::data[0] = x;
        base::data[1] = y;
        base::data[2] = z;
    }

    // vec3(v, z);
    inline Tvec3(const Tvec2<T>& v, T z)
    {
        base::data[0] = v[0];
        base::data[1] = v[1];
        base::data[2] = z;
    }

    // vec3(x, v)
    inline Tvec3(T x, const Tvec2<T>& v)
    {
        base::data[0] = x;
        base::data[1] = v[0];
        base::data[2] = v[1];
    }
};

template <typename T>
class Tvec4 : public vecN<T,4>
{
public:
    typedef vecN<T,4> base;

    // Uninitialized variable
    inline Tvec4() {}

    // Copy constructor
    inline Tvec4(const base& v) : base(v) {}

    // vec4(x, y, z, w);
    inline Tvec4(T x, T y, T z, T w)
    {
        base::data[0] = x;
        base::data[1] = y;
        base::data[2] = z;
        base::data[3] = w;
    }

    // vec4(v, z, w);
    inline Tvec4(const Tvec2<T>& v, T z, T w)
    {
        base::data[0] = v[0];
        base::data[1] = v[1];
        base::data[2] = z;
        base::data[3] = w;
    }

    // vec4(x, v, w);
    inline Tvec4(T x, const Tvec2<T>& v, T w)
    {
        base::data[0] = x;
        base::data[1] = v[0];
        base::data[2] = v[1];
        base::data[3] = w;
    }

    // vec4(x, y, v);
    inline Tvec4(T x, T y, const Tvec2<T>& v)
    {
        base::data[0] = x;
        base::data[1] = y;
        base::data[2] = v[0];
        base::data[3] = v[1];
    }

    // vec4(v1, v2);
    inline Tvec4(const Tvec2<T>& u, const Tvec2<T>& v)
    {
        base::data[0] = u[0];
        base::data[1] = u[1];
        base::data[2] = v[0];
        base::data[3] = v[1];
    }

    // vec4(v, w);
    inline Tvec4(const Tvec3<T>& v, T w)
    {
        base::data[0] = v[0];
        base::data[1] = v[1];
        base::data[2] = v[2];
        base::data[3] = w;
    }

    // vec4(x, v);
    inline Tvec4(T x, const Tvec3<T>& v)
    {
        base::data[0] = x;
        base::data[1] = v[0];
        base::data[2] = v[1];
        base::data[3] = v[2];
    }
};

typedef Tvec2<float> vec2;
typedef Tvec2<int> ivec2;
typedef Tvec2<unsigned int> uvec2;
typedef Tvec2<double> dvec2;

typedef Tvec3<float> vec3;
typedef Tvec3<int> ivec3;
typedef Tvec3<unsigned int> uvec3;
typedef Tvec3<double> dvec3;

typedef Tvec4<float> vec4;
typedef Tvec4<int> ivec4;
typedef Tvec4<unsigned int> uvec4;
typedef Tvec4<double> dvec4;

template <typename T, int n>
static inline const vecN<T,n> operator * (T x, const vecN<T,n>& v)
{
    return v * x;
}

template <typename T>
static inline const Tvec2<T> operator / (T x, const Tvec2<T>& v)
{
    return Tvec2<T>(x / v[0], x / v[1]);
}

template <typename T>
static inline const Tvec3<T> operator / (T x, const Tvec3<T>& v)
{
    return Tvec3<T>(x / v[0], x / v[1], x / v[2]);
}

template <typename T>
static inline const Tvec4<T> operator / (T x, const Tvec4<T>& v)
{
    return Tvec4<T>(x / v[0], x / v[1], x / v[2], x / v[3]);
}

template <typename T, int len>
static inline T dot(const vecN<T,len>& a, const vecN<T,len>& b)
{
    int n;
    T total = T(0);
    for (n = 0; n < len; n++)
    {
        total += a[n] * b[n];
    }
    return total;
}

template <typename T>
static inline vecN<T,3> cross(const vecN<T,3>& a, const vecN<T,3>& b)
{
    return Tvec3<T>(a[1] * b[2] - b[1] * a[2],
                    a[2] * b[0] - b[2] * a[0],
                    a[0] * b[1] - b[0] * a[1]);
}

template <typename T, int len>
static inline T length(const vecN<T,len>& v)
{
    T result(0);

    for (int i = 0; i < v.size(); ++i)
    {
        result += v[i] * v[i];
    }

    return (T)sqrt(result);
}

template <typename T, int len>
static inline vecN<T,len> normalize(const vecN<T,len>& v)
{
    return v / length(v);
}

template <typename T, int len>
static inline T distance(const vecN<T,len>& a, const vecN<T,len>& b)
{
    return length(b - a);
}

template <typename T, const int w, const int h>
class matNM
{
public:
    typedef class matNM<T,w,h> my_type;
    typedef class vecN<T,h> vector_type;

    // Default constructor does nothing, just like built-in types
    inline matNM()
    {
        // Uninitialized variable
    }

    // Copy constructor
    inline matNM(const matNM& that)
    {
        assign(that);
    }

    // Construction from element type
    // explicit to prevent assignment from T
    explicit inline matNM(T f)
    {
        for (int n = 0; n < w; n++)
        {
            data[n] = f;
        }
    }

    // Construction from vector
    inline matNM(const vector_type& v)
    {
        for (int n = 0; n < w; n++)
        {
            data[n] = v;
        }
    }

    // Assignment operator
    inline matNM& operator=(const my_type& that)
    {
        assign(that);
        return *this;
    }

    inline matNM operator+(const my_type& that) const
    {
        my_type result;
        int n;
        for (n = 0; n < w; n++)
            result.data[n] = data[n] + that.data[n];
        return result;
    }

    inline my_type& operator+=(const my_type& that)
    {
        return (*this = *this + that);
    }

    inline my_type operator-(const my_type& that) const
    {
        my_type result;
        int n;
        for (n = 0; n < w; n++)
            result.data[n] = data[n] - that.data[n];
        return result;
    }

    inline my_type& operator-=(const my_type& that)
    {
        return (*this = *this - that);
    }

    // Matrix multiply.
    // TODO: This only works for square matrices. Need more template skill to make a non-square version.
    inline my_type operator*(const my_type& that) const
    {
        ensure<w == h>();

        my_type result(0);

        for (int j = 0; j < w; j++)
        {
            for (int i = 0; i < h; i++)
            {
                T sum(0);

                for (int n = 0; n < w; n++)
                {
                    sum += data[n][i] * that[j][n];
                }

                result[j][i] = sum;
            }
        }

        return result;
    }

    inline my_type& operator*=(const my_type& that)
    {
        return (*this = *this * that);
    }

    inline vector_type& operator[](int n) { return data[n]; }
    inline const vector_type& operator[](int n) const { return data[n]; }
    inline operator T*() { return &data[0][0]; }
    inline operator const T*() const { return &data[0][0]; }

    inline matNM<T,h,w> transpose(void) const
    {
        matNM<T,h,w> result;
        int x, y;

        for (y = 0; y < w; y++)
        {
            for (x = 0; x < h; x++)
            {
                result[x][y] = data[y][x];
            }
        }

        return result;
    }

    static inline my_type identity()
    {
        ensure<w == h>();

        my_type result(0);

        for (int i = 0; i < w; i++)
        {
            result[i][i] = 1;
        }

        return result;
    }

    static inline int width(void) { return w; }
    static inline int height(void) { return h; }

protected:
    // Column primary data (essentially, array of vectors)
    vecN<T,h> data[w];

    // Assignment function - called from assignment operator and copy constructor.
    inline void assign(const matNM& that)
    {
        int n;
        for (n = 0; n < w; n++)
            data[n] = that.data[n];
    }
};

/*
template <typename T, const int N>
class TmatN : public matNM<T,N,N>
{
public:
    typedef matNM<T,N,N> base;
    typedef TmatN<T,N> my_type;

    inline TmatN() {}
    inline TmatN(const my_type& that) : base(that) {}
    inline TmatN(float f) : base(f) {}
    inline TmatN(const vecN<T,4>& v) : base(v) {}

    inline my_type transpose(void)
    {
        my_type result;
        int x, y;

        for (y = 0; y < h; y++)
        {
            for (x = 0; x < h; x++)
            {
                result[x][y] = data[y][x];
            }
        }

        return result;
    }
};
*/

template <typename T>
class Tmat4 : public matNM<T,4,4>
{
public:
    typedef matNM<T,4,4> base;
    typedef Tmat4<T> my_type;

    inline Tmat4() {}
    inline Tmat4(const my_type& that) : base(that) {}
    inline Tmat4(const base& that) : base(that) {}
    inline Tmat4(const vecN<T,4>& v) : base(v) {}
    inline Tmat4(const vecN<T,4>& v0,
                 const vecN<T,4>& v1,
                 const vecN<T,4>& v2,
                 const vecN<T,4>& v3)
    {
        base::data[0] = v0;
        base::data[1] = v1;
        base::data[2] = v2;
        base::data[3] = v3;
    }
};

typedef Tmat4<float> mat4;
typedef Tmat4<int> imat4;
typedef Tmat4<unsigned int> umat4;
typedef Tmat4<double> dmat4;

static inline mat4 frustum(float left, float right, float bottom, float top, float n, float f)
{
    mat4 result(mat4::identity());

    if ((right == left) ||
        (top == bottom) ||
        (n == f) ||
        (n < 0.0) ||
        (f < 0.0))
       return result;

    result[0][0] = (2.0f * n) / (right - left);
    result[1][1] = (2.0f * n) / (top - bottom);

    result[2][0] = (right + left) / (right - left);
    result[2][1] = (top + bottom) / (top - bottom);
    result[2][2] = -(f + n) / (f - n);
    result[2][3]= -1.0f;

    result[3][2] = -(2.0f * f * n) / (f - n);
    result[3][3] =  0.0f;

    return result;
}

static inline mat4 perspective(float fovy /* in degrees */, float aspect, float n, float f)
{
	float  top = n * tan(radians(0.5f*fovy)); // bottom = -top
	float  right = top * aspect; // left = -right
	return frustum(-right, right, -top, top, n, f);
}

template <typename T>
static inline Tmat4<T> lookat(vecN<T,3> eye, vecN<T,3> center, vecN<T,3> up)
{
    const Tvec3<T> f = normalize(center - eye);
    const Tvec3<T> upN = normalize(up);
    const Tvec3<T> s = cross(f, upN);
    const Tvec3<T> u = cross(s, f);
    const Tmat4<T> M = Tmat4<T>(Tvec4<T>(s[0], u[0], -f[0], T(0)),
                                Tvec4<T>(s[1], u[1], -f[1], T(0)),
                                Tvec4<T>(s[2], u[2], -f[2], T(0)),
                                Tvec4<T>(T(0), T(0), T(0), T(1)));

    return M * translate<T>(-eye);
}

template <typename T>
static inline Tmat4<T> translate(T x, T y, T z)
{
    return Tmat4<T>(Tvec4<T>(1.0f, 0.0f, 0.0f, 0.0f),
                    Tvec4<T>(0.0f, 1.0f, 0.0f, 0.0f),
                    Tvec4<T>(0.0f, 0.0f, 1.0f, 0.0f),
                    Tvec4<T>(x, y, z, 1.0f));
}

template <typename T>
static inline Tmat4<T> translate(const vecN<T,3>& v)
{
    return translate(v[0], v[1], v[2]);
}

template <typename T>
static inline Tmat4<T> scale(T x, T y, T z)
{
    return Tmat4<T>(Tvec4<T>(x, 0.0f, 0.0f, 0.0f),
                    Tvec4<T>(0.0f, y, 0.0f, 0.0f),
                    Tvec4<T>(0.0f, 0.0f, z, 0.0f),
                    Tvec4<T>(0.0f, 0.0f, 0.0f, 1.0f));
}

template <typename T>
static inline Tmat4<T> scale(const Tvec4<T>& v)
{
    return scale(v[0], v[1], v[2]);
}

template <typename T>
static inline Tmat4<T> scale(T x)
{
    return Tmat4<T>(Tvec4<T>(x, 0.0f, 0.0f, 0.0f),
                    Tvec4<T>(0.0f, x, 0.0f, 0.0f),
                    Tvec4<T>(0.0f, 0.0f, x, 0.0f),
                    Tvec4<T>(0.0f, 0.0f, 0.0f, 1.0f));
}

template <typename T>
static inline Tmat4<T> rotate(T angle, T x, T y, T z)
{
    Tmat4<T> result;

    const T x2 = x * x;
    const T y2 = y * y;
    const T z2 = z * z;
    float rads = float(angle) * 0.0174532925f;
    const float c = cosf(rads);
    const float s = sinf(rads);
    const float omc = 1.0f - c;

    result[0] = Tvec4<T>(T(x2 * omc + c), T(y * x * omc + z * s), T(x * z * omc - y * s), T(0));
    result[1] = Tvec4<T>(T(x * y * omc - z * s), T(y2 * omc + c), T(y * z * omc + x * s), T(0));
    result[2] = Tvec4<T>(T(x * z * omc + y * s), T(y * z * omc - x * s), T(z2 * omc + c), T(0));
    result[3] = Tvec4<T>(T(0), T(0), T(0), T(1));

    return result;
}

template <typename T>
static inline Tmat4<T> rotate(T angle, const vecN<T,3>& v)
{
    return rotate<T>(angle, v[0], v[1], v[2]);
}

#ifdef min
#undef min
#endif

template <typename T>
static inline T min(T a, T b)
{
    return a < b ? a : b;
}

#ifdef max
#undef max
#endif

template <typename T>
static inline T max(T a, T b)
{
    return a >= b ? a : b;
}

template <typename T, const int N>
static inline vecN<T,N> min(const vecN<T,N>& x, const vecN<T,N>& y)
{
    vecN<T,N> t;
    int n;

    for (n = 0; n < N; n++)
    {
        t[n] = min(x[n], y[n]);
    }

    return t;
}

template <typename T, const int N>
static inline vecN<T,N> max(const vecN<T,N>& x, const vecN<T,N>& y)
{
    vecN<T,N> t;
    int n;

    for (n = 0; n < N; n++)
    {
        t[n] = max<T>(x[n], y[n]);
    }

    return t;
}

template <typename T, const int N>
static inline vecN<T,N> clamp(const vecN<T,N>& x, const vecN<T,N>& minVal, const vecN<T,N>& maxVal)
{
    return min<T>(max<T>(x, minVal), maxVal);
}

template <typename T, const int N>
static inline vecN<T,N> smoothstep(const vecN<T,N>& edge0, const vecN<T,N>& edge1, const vecN<T,N>& x)
{
    vecN<T,N> t;
    t = clamp((x - edge0) / (edge1 - edge0), vecN<T,N>(T(0)), vecN<T,N>(T(1)));
    return t * t * (vecN<T,N>(T(3)) - vecN<T,N>(T(2)) * t);
}

template <typename T, const int N, const int M>
static inline matNM<T,N,M> matrixCompMult(const matNM<T,N,M>& x, const matNM<T,N,M>& y)
{
    matNM<T,N,M> result;
    int i, j;

    for (j = 0; j < M; ++j)
    {
        for (i = 0; i < N; ++i)
        {
            result[i][j] = x[i][j] * y[i][j];
        }
    }

    return result;
}

template <typename T, const int N, const int M>
static inline vecN<T,N> operator*(const vecN<T,M>& vec, const matNM<T,N,M>& mat)
{
    int n, m;
    vecN<T,N> result(T(0));

    for (m = 0; m < M; m++)
    {
        for (n = 0; n < N; n++)
        {
            result[n] += vec[m] * mat[n][m];
        }
    }

    return result;
}

};

#endif /* __VMATH_H__ */
 

  • 6
    点赞
  • 92
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值