仿射变换(Affine Transformation)
Affine Transformation是一种二维坐标到二维坐标之间的线性变换,保持二维图形的“平直性”(译注:straightness,即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(译注:parallelness,其实是指保二维图形间的相对位置关系不变,平行线还是平行线,相交直线的交角不变)。
OpenGL基础仿射变化原理
仿射变换可以通过一系列的原子变换的复合来实现,包括:平移(Translation)、缩放(Scale)、翻转(Flip)、旋转(Rotation)和剪切(Shear)。
基础仿射变换
几种典型的仿射变换如下:
平移变换 Translation
image平移变换是一种“刚体变换”,rigid-body transformation,就是不会产生形变的理想物体。
效果:
缩放变换(Scale)
将每一点的横坐标放大(缩小)至sx倍,纵坐标放大(缩小)至sy倍,变换矩阵为:
image变换效果如下:
image剪切变换(Shear)
变换矩阵为:
image相当于一个横向剪切与一个纵向剪切的复合
image效果:
image旋转变换(Rotation)
目标图形围绕原点顺时针旋转theta弧度,变换矩阵为:
image效果:
image组合
旋转变换,目标图形以(x, y)为轴心顺时针旋转theta弧度,变换矩阵为:
image相当于两次平移变换与一次原点旋转变换的复合:
image先移动到中心节点,然后旋转,然后再移动回去。
仿射变化原理:
物体变化从最终显示效果的实现基本有两种实现方式:
- 改变物体
- 反向改变观察者
结合我们之前提到的
物体从对象坐标系到屏幕坐标系的计算流程
我们就可以理解物体仿射变换的具体过程.
1. 视觉坐标
视觉可以理解为屏幕坐标。是一个虚拟的固定的坐标系。
2. 视图变换
视图变化是当观察者移动位置时视图相应的发生角度的变换,默认观察者是以z轴负方向观察,当观察沿x轴负方向观察时,实际观察模型的侧面。
一般都是先进行视觉变化,已确定观察的视角,然后进行其它变化。这样更容易理解变化的过程。
3. 模型变换
是用于操纵模型或某一对象,通过变换改变了其位置,大小或者角度。
4. 模型视图的二次元
模型视图二次元是模型视图两种变换在线性变换管线中的进行组合,成为一个单独的变化矩阵。即模型视图矩阵。
5. 投影变换
将模型视图变换之后应用到顶点上,这种变换后实际上是确认了视景体和剪裁平面。
对于平行投影来说,视景体是一个四边平行于投影方向,长度无限的四棱柱;
对于透视投影来说,则是以投影中心为顶点的四棱锥。
6. 视口变换
当以上变换结束后,我们得到了一个场景的二维投影,这时候我们需要将其映射到窗口的某片区域进行显示,还包括颜色缓冲区与窗口像素间的转换。
7. 模型视图矩阵
模型视图矩阵是一个4x4的矩阵,所表示一个变换后的坐标系,模型视图变换就是通过将要变换的顶点和模型变换矩阵相乘得到一个变换后相对于视觉坐标系新的坐标系。
下面是一个模型视图变换的例子
GLBatch squareBatch;
GLShaderManager shaderManager;
GLfloat blockSize = 0.1;
GLfloat vVerts[] = { -blockSize, -blockSize, 0.0f,//一个3x4矩阵忽略了w缩放值
blockSize, -blockSize, 0.0f,
blockSize, blockSize, 0.0f,
-blockSize, blockSize, 0.0f};
GLfloat xPos = 0.0;
GLfloat yPos = 0.0;
void SetupRC()
{
//设置背景色
glClearColor(0.0f, 0.0f, 1.0f, 1.0f );
//初始化存储着色器
shaderManager.InitializeStockShaders();
//设置着色器的图片类型和定点数
squareBatch.Begin(GL_TRIANGLE_FAN, 4);
//存储管理顶点着色器的坐标
squareBatch.CopyVertexData3f(vVerts);
//当前管理容器结束标识
squareBatch.End();
}
// 通过键盘的上下左右改变 xPos,yPos的值
void SpecialKeys(int key, int x, int y)
{
GLfloat stepSize = 0.025;
if (key == GLUT_KEY_UP){
yPos += stepSize;
}
if (key == GLUT_KEY_DOWN){
yPos -= stepSize;
}
if (key == GLUT_KEY_LEFT){
xPos -= stepSize;
}
if(key == GLUT_KEY_RIGHT){
xPos += stepSize;
}
if(xPos < (-1.0f + blockSize)){
xPos = -1.0f + blockSize;
}
if(xPos > (1.0f - blockSize)){
xPos = 1.0f - blockSize;
}
if(yPos < (-1.0f + blockSize)){
yPos = -1.0f + blockSize;
}
if(yPos > (1.0f - blockSize)) {
yPos = 1.0f - blockSize;
}
glutPostRedisplay();
}
void RenderScene(void)
{
//清理缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//颜色矩阵
GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
//4x4矩阵
M3DMatrix44f mFinalTransform, mTranslationMatrix, mRotationMatrix;
//平移矩阵,平移后后相对于视觉系新的坐标集合会存储在mTranslationMatrix中
//原理是坐标系的每个顶点坐标与平移矩阵进行相乘会得到一个相对与视觉系的新的顶点,所有坐标相乘后就会得到一个相对于视觉的新的坐标系。
//例如将顶点(1,1,1)沿着x正方向移动到(2,1,1)乘以 1,0,0,1即可
// 0,1,0,0
// 0,0,1,0
// 0,0,0,1
m3dTranslationMatrix44(mTranslationMatrix, xPos, yPos, 0.0f);
static float yRot = 0.0f;
yRot += 5.0f;
// 旋转矩阵 转换后相对于视觉系新的坐标集合会存储在mRotationMatrix中
// 其中yRot为旋转的角度,0.0f, 0.0f, 1.0f为旋转的方向;
// 这里是沿着z轴旋转乘以 cos角,-sin角,0,1即可
// sin角, cos角,0,0
// 0, 0, 1,0
// 0, 0, 0,1
m3dRotationMatrix44(mRotationMatrix, m3dDegToRad(yRot), 0.0f, 0.0f, 1.0f);
// 最终会把平移和旋转的结果应用到mFinalTransform中
m3dMatrixMultiply44(mFinalTransform, mTranslationMatrix, mRotationMatrix);
// 通过平面着色器将3d坐标转换成2d坐标
shaderManager.UseStockShader(GLT_SHADER_FLAT, mFinalTransform, vRed);
// 绘制
squareBatch.Draw();
// 后台缓冲区和前台缓冲区切换函数
glutSwapBuffers();
}
void ChangeSize(int w, int h)
{
// 确认视口大小
glViewport(0, 0, w, h);
}
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);
glutInitWindowSize(600, 600);
glutCreateWindow("MOVE");
GLenum err = glewInit();
if (GLEW_OK != err)
{
fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
return 1;
}
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
glutSpecialFunc(SpecialKeys);//监听键盘按键的回调
SetupRC();
glutMainLoop();
return 0;
}
8. 模型视图投影矩阵
因为计算机只识别单元立方的坐标系,有时候我们需要跳出坐标系进行变换,比如说当我们采用投影变换后得到的非单元立方的坐标系,这时候就要通过模型视图矩阵转型成单位立方矩阵.
下面是一个通过透视投影然后经过模型视图投影矩阵变换的例子
GLFrustum viewFrustum;
GLShaderManager shaderManager;
GLTriangleBatch torusBatch;
void ChangeSize(int w, int h)
{
if(h == 0)
h = 1;
glViewport(0, 0, w, h);
//进行透视投影得到视景图
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 1000.0f);
}
void RenderScene(void)
{
static CStopWatch rotTimer;
//时间监听,得到当前时间
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
M3DMatrix44f mTranslate, mRotate, mModelview, mModelViewProjection;
//平移变换矩阵, 向z轴平移-2.5f
m3dTranslationMatrix44(mTranslate, 0.0f, 0.0f, -2.5f);
//旋转变换矩阵, 沿y轴逆时针旋转
m3dRotationMatrix44(mRotate, m3dDegToRad(yRot), 0.0f, 1.0f, 0.0f);
//得到视图模型变换后矩阵
m3dMatrixMultiply44(mModelview, mTranslate, mRotate);
//其实视图变换矩阵就可以绘制显示了,但是计算机只识别单元立方体坐标系,而经过透视投影之后为非单元立方体坐标系,所以需要模型视图投影矩阵进行转换成单元立方坐标系,这里首先先从透镜矩阵实例viewFrustum,取到投影矩阵坐标,然后和模型视图矩阵通过m3dMatrixMultiply44方法转换换成单元立方坐标系放在mModelViewProjection中.
m3dMatrixMultiply44(mModelViewProjection, viewFrustum.GetProjectionMatrix(),mModelview);
GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };
//通过平面存储着色器转换成二维的顶点坐标
shaderManager.UseStockShader(GLT_SHADER_FLAT, mModelViewProjection, vBlack);
//绘制
torusBatch.Draw();
glutSwapBuffers();
glutPostRedisplay();
}
void SetupRC()
{
glClearColor(0.8f, 0.8f, 0.8f, 1.0f );
//开启深度测试,防止图元重叠
glEnable(GL_DEPTH_TEST);
shaderManager.InitializeStockShaders();
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
//多边形模式,多边形图元以线段的形式前后都显示
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(800, 600);
glutCreateWindow("ModelViewProjection");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
网友评论