接下来的变换是旋转变换,就是说给定一个角度和点,我们将点绕着一个坐标轴旋转。在旋转过程中发生变化的总是x,y,z三个坐标里面的其中两个,而不让第三个坐标值变化。这意味着,旋转路径总在三个坐标轴平面中的一个之中:绕 Z 轴的是 xy 面、绕 X 轴的是 yz 面、绕 Y 轴的是 xy 面。还有许多复杂的旋转变换可以让你绕任意一个向量旋转,但是眼下我们并不需要讨论这些。 让我们概括地定义这个问题这个问题。看看下面的这个图: [

](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022306-300x278.jpg)](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022306.jpg) 我们想沿着圆圈从(x1,y1)移动到(x2,y2)。也就是说我们让向量(x1,y1)转过a2角度。我们假设这个圆的半径是1。这意味着下列成立: x1=cos(a1) y1=sin(a1) x2=cos(a1+a2) y2=sin(a1+a2) 我们用下面的三角恒等式来产生 x2 和 y2 : cos(a+b) = cosacosb - sinasinb sin(a+b) = sinacosb+cosasinb 通过面的式子我们可以推导出: x2=cos(a1+a2) = cosa1cosa2- sina1sina2 = x1cosa2- y1sina2 y2=sin(a1+a2) = sina1cosa2+ cosa1sina2 = y1cosa2+ x1sina2 在上面的图中我们看向 xy 平面,Z 轴指向屏幕内部!如果 X 分量和 Y 分量是四维向量的一部分,那样上面的等式可以写成下面的矩阵形式(不影响 Z 和 W )为:[![](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022302-300x86.jpg)](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022302.jpg)如果我们想创建一个绕着 Y 轴或者绕着 Z 轴的旋转变换矩阵,那么方程基本相同但是矩阵的安排却略有不同!这是绕Y轴旋转的矩阵: [![](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022303-300x81.jpg)](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022303.jpg) 绕X轴的旋转矩阵为: [![](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022304-300x81.jpg)
](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022306-300x278.jpg)](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022306.jpg) 我们想沿着圆圈从(x1,y1)移动到(x2,y2)。也就是说我们让向量(x1,y1)转过a2角度。我们假设这个圆的半径是1。这意味着下列成立: x1=cos(a1) y1=sin(a1) x2=cos(a1+a2) y2=sin(a1+a2) 我们用下面的三角恒等式来产生 x2 和 y2 : cos(a+b) = cosacosb - sinasinb sin(a+b) = sinacosb+cosasinb 通过面的式子我们可以推导出: x2=cos(a1+a2) = cosa1cosa2- sina1sina2 = x1cosa2- y1sina2 y2=sin(a1+a2) = sina1cosa2+ cosa1sina2 = y1cosa2+ x1sina2 在上面的图中我们看向 xy 平面,Z 轴指向屏幕内部!如果 X 分量和 Y 分量是四维向量的一部分,那样上面的等式可以写成下面的矩阵形式(不影响 Z 和 W )为:[![](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022302-300x86.jpg)](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022302.jpg)如果我们想创建一个绕着 Y 轴或者绕着 Z 轴的旋转变换矩阵,那么方程基本相同但是矩阵的安排却略有不同!这是绕Y轴旋转的矩阵: [![](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022303-300x81.jpg)](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022303.jpg) 绕X轴的旋转矩阵为: [![](http://www.wjgbaby.com/wp-content/uploads/2018/02/18022304-300x81.jpg)

源代码详解:

在上个教程的基础上这里代码的变换非常少。我们只是改变代码中变换矩阵的内容,将平移变换矩阵换成了旋转变换矩阵而已:

World.m[0][0]=cosf(Scale);World.m[0][1]=-sinf(Scale);World.m[0][2]=0.0f; World.m[0][3]=0.0f;
World.m[1][0]=sinf(Scale);World.m[1][1]=cosf(Scale);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;

这样会看到图形绕着Z轴旋转了。我们也可以尝试绕其他轴的旋转,但是没有从3d到2d的投影另外两种旋转看上起去会很奇怪,我们会在后面教程中在一个完整的图形变换管线类中来完善它。 源代码:

#include <stdio.h>
#include<string.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include “ogldev_math_3d.h” //用于OpenGL的3d数学库
#include “ogldev_util.h” //用于读取文本文件

GLuint VBO; //全局GLuint引用变量,来操作顶点缓冲器对象
GLuint gWorldLocation; // 平移变换一致变量的句柄引用

// 定义要读取的顶点着色器脚本和片断着色器脚本的文件名,作为文件读取路径
const char* pVSFileName = “shader.vs”;
const char* pFSFileName = “shader.fs”;

static void RenderCallBack()
{
glClear(GL_COLOR_BUFFER_BIT); //清空颜色缓存

static float Scale = 0.0f;   //维护一个不断慢慢增大的静态浮点数
Scale += 0.0001f;   //如果图像变化太快或者太慢,可调节此数值

// 4x4的旋转变换矩阵
Matrix4f World;

World.m\[0\]\[0\] = cosf(Scale); World.m\[0\]\[1\] = -sinf(Scale); World.m\[0\]\[2\] = 0.0f; World.m\[0\]\[3\] = 0.0f;
World.m\[1\]\[0\] = sinf(Scale); World.m\[1\]\[1\] = cosf(Scale); 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;

// 将矩阵数据加载到shader中
glUniformMatrix4fv(gWorldLocation, 1, GL\_TRUE, &World.m\[0\]\[0\]);

glEnableVertexAttribArray(0);   //开启顶点属性
glBindBuffer(GL\_ARRAY\_BUFFER, VBO);   //绑定GL\_ARRAY\_BUFFER缓冲器
glVertexAttribPointer(0, 3, GL\_FLOAT, GL\_FALSE, 0, 0);   //管线解析bufer中的数据

glDrawArrays(GL\_TRIANGLES, 0, 3);   //画三角形,3个顶点
glDisableVertexAttribArray(0);   //禁用顶点数据
glutSwapBuffers();   //交换前后缓存

}

static void InitializeGlutCallbacks()
{
glutDisplayFunc(RenderCallBack);
glutIdleFunc(RenderCallBack); //将渲染回调注册为全局闲置回调
}

static void CreateVertexBuffer()
{
Vector3f Vertices[3]; //创建含有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);   //绑定GL\_ARRAY\_BUFFER缓冲器

glBufferData(GL\_ARRAY\_BUFFER, sizeof(Vertices), Vertices, GL\_STATIC\_DRAW);   //绑定顶点数据

}

//使用shader文本编译shader对象,并绑定shader到着色器程序中
static void AddShader(GLuint ShaderProgram, const char* pShaderText, GLenum ShaderType)
{
// 根据shader类型参数定义两个shader对象
GLuint ShaderObj = glCreateShader(ShaderType);
// 检查是否定义成功
if (ShaderObj == 0)
{
fprintf(stderr, “Error creating shader type %d\n”, ShaderType);
exit(0);
}

// 定义shader的代码源
const GLchar\* p\[1\];
p\[0\] = pShaderText;
GLint Lengths\[1\];
Lengths\[0\] = strlen(pShaderText);
glShaderSource(ShaderObj, 1, p, Lengths);
glCompileShader(ShaderObj);    // 编译shader对象

// 检查和shader相关的错误
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);
}

// 将编译好的shader对象绑定到program object程序对象上
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);

// 链接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);
}

// 设置到管线声明中来使用上面成功建立的shader程序
glUseProgram(ShaderProgram);

// 查询获取一致变量的位置
gWorldLocation = glGetUniformLocation(ShaderProgram, "gWorld");
assert(gWorldLocation != 0xFFFFFFFF);    // 检查错误

}

int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

glutInitWindowSize(400, 400);
glutInitWindowPosition(100, 100);
glutCreateWindow("旋转变换");

glutDisplayFunc(RenderCallBack);  //开始渲染

InitializeGlutCallbacks();

// 检查GLEW是否就绪,必须要在GLUT初始化之后!
GLenum res = glewInit();
if (res != GLEW\_OK)
{
    fprintf(stderr, "Error: '%s'\\n", glewGetErrorString(res));
    return 1;
}

glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //缓存清空后的颜色值
CreateVertexBuffer(); //创建顶点缓冲器

CompileShaders();   // 编译着色器

glutMainLoop();   //通知开始GLUT的内部循环

return 0;

}

片断着色器shader.fs脚本代码:

#version 330 //告诉编译器我们的目标GLSL编译器版本是3.3

out vec4 FragColor; // 片段着色器的输出颜色变量

// 着色器的唯一入口函数
void main()
{
// 定义输出颜色值
FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

顶点着色器shader.vs脚本代码:

#version 330 //告诉编译器我们的目标GLSL编译器版本是3.3

// 绑定定点属性名和属性,方式二缓冲属性和shader属性对应映射
layout (location = 0) in vec3 Position;

// 平移变换聚矩阵一致变量
uniform mat4 gWorld;

void main()
{
// 用平移变换矩阵乘以图形顶点位置对应的4X4矩阵相乘,完成平移变换
gl_Position = gWorld * vec4(Position, 1.0);
}

最终效果(gif图): 参考链接: http://ogldev.atspace.co.uk/www/tutorial07/tutorial07.html https://learnopengl.com/Getting-started/Transformations http://blog.csdn.net/cordova/article/details/52558133