Transformation,Animation and Viewing
4 Transformation,Animation and Viewing
声明:该代码来自:Computer Graphics Through OpenGL From Theory to Experiments,仅用作学习参考
4.1 Modeling Transformations
平移、缩放和旋转,即 OpenGL 的建模转换,应用于物体以更改其位置和形状。
Modeling Transformations控制世界坐标系中物体的位置、大小、旋转
4.1.1 Translation
glFrustum(left, right, bottom, top, near, far)
请注意 OpenGL 的一个小怪癖,即 near 和 far 值在符号上颠倒。
给函数glFrustum输入near,far为正数,实际上为
z
=
−
near
,
z
=
−
far
\text{z}=-\text{near}, \text{z}=-\text{far}
z=−near,z=−far
4.1.2 Scaling
the scaling command glScalef(u, v, w) maps each point (x,y,z) of an object to the point (ux vy wz). This has the e ect of stretching objects by a factor of u in the x-direction, v in the y-direction, and w in the z-direction
一个或多个缩放因子为零的缩放转换称为退化转换。虽然并不常见,但偶尔会有一些应用程序会派上用场。
如果w为0那么,将3维物体退化为2维
4.1.3 Rotation
4.2 Composing Modeling Transformations
任务:想要对正方形绕它自己的中心逆时针旋转45°,如 Figure 4.20(a)所示
如果直接使用glRotatef(45.0, 0.0, 0.0, 1.0)则会得到4.20(b)中的效果,即绕原点逆时针45°,而不是绕物体本身的中心旋转45°
正确步骤应该是:
(3)glTranslatef(7.5, 7.5, 0.0); // Translate back
(2)glRotatef(45.0, 0.0, 0.0, 1.0); // Rotate about origin.
(1)glTranslatef(-7.5,-7.5, 0.0); // Translate to origin.
在 OpenGL 中,变换矩阵的操作是以堆栈的方式进行的,最新的变换操作会首先应用。也就是说,变换操作的执行顺序是从最后一个变换开始,依次向前应用。
4.3 Placing Multiple Objects
在 OpenGL 中,所有的矩阵变换操作会累积在当前的模型视图矩阵上,并且会影响后续的绘制操作。因此,在你的 drawScene 函数中,所有的变换操作会同时作用于立方体和球体。这些变换会作用于立方体和球体。为了确保球体的变换不受立方体的变换影响,你可以在绘制球体之前重置模型视图矩阵例如使用glLoadIdentity(); 或者使用 glPushMatrix 和 glPopMatrix 来保存和恢复矩阵状态。
在创建时,Object1 的局部坐标系与世界坐标系重合。
(1)如果变换 tn 是由函数 glTranslatef(p, q, r) 指定的 translation 变换
则物体所有顶点 V 相对于 object1 的局部坐标系的位置
(
a
,
b
,
c
)
(a,b,c)
(a,b,c) 不会改变。但V在世界坐标系中的位置变为了
(
a
+
p
,
b
+
q
,
c
+
r
)
(a+p,b+q,c+r)
(a+p,b+q,c+r)
(2) 如果变换 tn 是由函数 glRotatef(A, p, q, r) 指定的 rotation 变换
则物体所有顶点 V 相对于 object1 的局部坐标系的位置
(
a
,
b
,
c
)
(a,b,c)
(a,b,c) 不会改变
(3)如果变换 tn 是由函数 glScalef(u, v, w) 指定的 scaling 变换
V 在世界坐标中的位置通过缩放更改为
(
u
a
,
v
b
,
w
c
)
(ua,vb,wc)
(ua,vb,wc)。但是,由于相同的缩放适用于 object1 的局部坐标系的轴,因此 V 相对于该系统的位置
(
a
,
b
,
c
)
(a,b,c)
(a,b,c) 不会再次改变。
例子:
浅蓝色为世界坐标系、红人和蓝人有各自的局部坐标系(图中只画出了红人的局部坐标系),对两个人进行变换,可以看出两人在世界坐标系中的坐标发生了变换,但是在他们各自的局部坐标系中均为发生变换
///
// relativePlacement.cpp
//
// This program shows composition of transformations and
// the relative placement of one object w.r.t another.
//
// Interaction:
// Press the up/down arrow keys to process code statements.
//
// Sumanta Guha.
///
#define _USE_MATH_DEFINES
#include <cmath>
#include <iostream>
#include <GL/glew.h>
#include <GL/freeglut.h>
// Globals.
static unsigned int base; // Display lists base index.
static int numVal = 0; // Step index.
static long font = (long)GLUT_BITMAP_8_BY_13; // Font selection.
// Routine to draw a bitmap character string.
void writeBitmapString(void *font, char *string)
{
char *c;
for (c = string; *c != '\0'; c++) glutBitmapCharacter(font, *c);
}
// Draw stick figure with a local co-ordinate system.
void drawMan(void)
{
float angle;
int i;
glLineWidth(2.0);
glBegin(GL_LINE_LOOP);
for (i = 0; i < 20; ++i)
{
angle = 2 * M_PI * i / 20;
glVertex2f(0.0 + cos(angle) * 3.0, 7 + sin(angle) * 3.0);
}
glEnd();
glBegin(GL_LINES);
glVertex2f(0.0, 4.0);
glVertex2f(0.0, -4.0);
glVertex2f(0.0, -4.0);
glVertex2f(6.0, -10.0);
glVertex2f(0.0, -4.0);
glVertex2f(-6.0, -10.0);
glVertex2f(-6.0, 0.0);
glVertex2f(6.0, 0.0);
glEnd();
glLineWidth(1.0);
glRasterPos3f(0.0, 0.0, 0.0);
writeBitmapString((void*)font, "O");
glRasterPos3f(7.0, 0.0, 0.0);
writeBitmapString((void*)font, "x");
glRasterPos3f(-1.0, 6.0, 0.0);
writeBitmapString((void*)font, "y");
}
// Draw local co-ordinates grid.
void drawGrid(void)
{
int i;
glEnable(GL_LINE_STIPPLE); // Enable line stippling.
glLineStipple(1, 0x00FF);
glBegin(GL_LINES);
for (i = -5; i < 6; ++i)
{
glVertex2f(5 * i, 25.0);
glVertex2f(5 * i, -25.0);
}
for (i = -5; i < 6; ++i)
{
glVertex2f(25.0, 5 * i);
glVertex2f(-25.0, 5 * i);
}
glEnd();
glDisable(GL_LINE_STIPPLE); // Disable line stippling.
}
// Draw and label world co-ordinate axes.
void drawWorldAxes(void)
{
glColor3f(0.0, 1.0, 1.0);
glBegin(GL_LINES);
glVertex2f(-50.0, 0.0);
glVertex2f(50.0, 0.0);
glVertex2f(0.0, -50.0);
glVertex2f(0.0, 50.0);
glEnd();
glRasterPos3f(48.0, -2.0, 0.0);
writeBitmapString((void*)font, "x");
glRasterPos3f(1.0, 48.0, 0.0);
writeBitmapString((void*)font, "y");
}
// Write fixed messages.
void writeFixedMessages(void)
{
glColor3f(0.0, 0.0, 0.0);
glRasterPos3f(-15.0, 43.0, 0.0);
writeBitmapString((void*)font, "Press the up/down arrow keys!");
glColor3f(0.8, 0.8, 0.8);
glRasterPos3f(-44.0, -17.0, 0.0);
writeBitmapString((void*)font, "glScalef(1.5, 0.75, 1.0);");
glRasterPos3f(-44.0, -20.0, 0.0);
writeBitmapString((void*)font, "glRotatef(30.0, 0.0, 0.0, 1.0);");
glRasterPos3f(-44.0, -23.0, 0.0);
writeBitmapString((void*)font, "glTranslatef(10.0, 0.0, 0.0);");
glRasterPos3f(-44.0, -26.0, 0.0);
writeBitmapString((void*)font, "drawRedMan; // Also draw grid in his local co-ordinate system.");
glRasterPos3f(-44.0, -29.0, 0.0);
writeBitmapString((void*)font, "glRotatef(45.0, 0.0, 0.0, 1.0);");
glRasterPos3f(-44.0, -32.0, 0.0);
writeBitmapString((void*)font, "glTranslatef(20.0, 0.0, 0.0);");
glRasterPos3f(-44.0, -35.0, 0.0);
writeBitmapString((void*)font, "drawBlueMan;");
}
// Drawing routine.
void drawScene(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
writeFixedMessages();
drawWorldAxes();
glColor3f(0.0, 0.0, 0.0);
switch (numVal)
{
case 0:
goto step0;
break;
case 1:
goto step1;
break;
case 2:
goto step2;
break;
case 3:
goto step3;
break;
case 4:
goto step4;
break;
case 5:
goto step5;
break;
case 6:
goto step6;
break;
case 7:
goto step7;
break;
default:
break;
}
// Transformation steps.
// Text drawing statements are enclosed within push/pop pairs
// so that the raster position is w.r.t the identity transform.
step7:
// Scale.
glPushMatrix();
glLoadIdentity();
glRasterPos3f(-44.0, -17.0, 0.0);
writeBitmapString((void*)font, "glScalef(1.5, 0.75, 1.0);");
glPopMatrix();
glScalef(1.5, 0.75, 1.0);
step6:
// Rotate.
glPushMatrix();
glLoadIdentity();
glRasterPos3f(-44.0, -20.0, 0.0);
writeBitmapString((void*)font, "glRotatef(30.0, 0.0, 0.0, 1.0);");
glPopMatrix();
glRotatef(30, 0.0, 0.0, 1.0);
step5:
// Translate.
glPushMatrix();
glLoadIdentity();
glRasterPos3f(-44.0, -23.0, 0.0);
writeBitmapString((void*)font, "glTranslatef(10.0, 0.0, 0.0);");
glPopMatrix();
glTranslatef(10.0, 0.0, 0.0);
step4:
// Draw red man.
glPushMatrix();
glLoadIdentity();
glRasterPos3f(-44.0, -26.0, 0.0);
writeBitmapString((void*)font, "drawRedMan; // Also draw grid in his local co-ordinate system.");
glPopMatrix();
glColor3f(1.0, 0.0, 0.0);
glCallList(base);
glCallList(base + 1);
glColor3f(0.0, 0.0, 0.0);
step3:
// Rotate.
glPushMatrix();
glLoadIdentity();
glRasterPos3f(-44.0, -29.0, 0.0);
writeBitmapString((void*)font, "glRotatef(45.0, 0.0, 0.0, 1.0);");
glPopMatrix();
glRotatef(45, 0.0, 0.0, 1.0);
step2:
// Translate.
glPushMatrix();
glLoadIdentity();
glRasterPos3f(-44.0, -32.0, 0.0);
writeBitmapString((void*)font, "glTranslatef(20.0, 0.0, 0.0);");
glPopMatrix();
glTranslatef(20.0, 0.0, 0.0);
step1:
// Draw blue man.
glPushMatrix();
glLoadIdentity();
glRasterPos3f(-44.0, -35.0, 0.0);
writeBitmapString((void*)font, "drawBlueMan;");
glPopMatrix();
glColor3f(0.0, 0.0, 1.0);
glCallList(base);
step0:
glFlush();
}
// Initialization routine.
void setup(void)
{
base = glGenLists(2);
glNewList(base, GL_COMPILE);
drawMan();
glEndList();
glNewList(base + 1, GL_COMPILE);
drawGrid();
glEndList();
glClearColor(1.0, 1.0, 1.0, 0.0);
}
// OpenGL window reshape routine.
void resize(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// Keyboard input processing routine.
void keyInput(unsigned char key, int x, int y)
{
switch (key)
{
case 27:
exit(0);
break;
break;
default:
break;
}
}
// Callback routine for non-ASCII key entry.
void specialKeyInput(int key, int x, int y)
{
if (key == GLUT_KEY_UP)
{
if (numVal < 7) numVal++; else numVal = 0;
}
if (key == GLUT_KEY_DOWN)
{
if (numVal > 0) numVal--; else numVal = 7;
}
glutPostRedisplay();
}
// Routine to output interaction instructions to the C++ window.
void printInteraction(void)
{
std::cout << "Interaction:" << std::endl;
std::cout << "Press the up/down arrow keys to process code statements." << std::endl;
}
// Main routine.
int main(int argc, char **argv)
{
printInteraction();
glutInit(&argc, argv);
glutInitContextVersion(4, 3);
glutInitContextProfile(GLUT_COMPATIBILITY_PROFILE);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("relativePlacement.cpp");
glutDisplayFunc(drawScene);
glutReshapeFunc(resize);
glutKeyboardFunc(keyInput);
glutSpecialFunc(specialKeyInput);
glewExperimental = GL_TRUE;
glewInit();
setup();
glutMainLoop();
}
4.4 Modelview Matrix Stack and Isolating Transformations
modelview 矩阵堆栈有助于将转换应用于多个对象
我们描述的 modelview 矩阵是通过右侧的乘法建模转换来修改的,实际上是 modelview 矩阵堆栈中最上面的一个。这个特定的矩阵称为当前 modelview 矩阵。事实上,OpenGL 维护了三个不同的矩阵堆栈:modelview、projection 和 texture。glMatrixMode(mode) 命令(其中 mode 为 GL_MODELVIEW、GL_PROJECTION 或 GL_TEXTURE)确定当前处于活动状态的堆栈。
glPushMatrix() 命令的作用是在 modelview 矩阵堆栈中复制当前(即当前最顶层)矩阵,并将其放置在堆栈顶部;因此,在执行 glPushMatrix() 时,堆栈的两个顶部矩阵立即相同。另一方面,glPopMatrix() 语句会删除 modelview 矩阵堆栈的最顶层矩阵,以便下面的矩阵成为当前矩阵。
4.5 Animation
4.5.1 Animation Technicals
Controlling Animation
在 OpenGL 中,有三种简单的方法可以控制动画:
- 通过键盘或鼠标输入以交互方式,借助其回调例程(callback routine)来调用转换
- 用glutIdelFunc(idle_function)语句调用idle函数,idle 函数在没有 OpenGL 事件时处于待处理状态时调用。
- 通过指定一个常规定时器函数(称为定时器函数),并调用 glutTimerFunc(period, 定时器函数, value) 即可实现半自动操作。定时器函数会在执行 glutTimerFunc() 语句后的 period 毫秒被调用,并且会将传递给它的参数值作为参数。
Double Buffer
颜色缓冲区是一个空间,通常位于 GPU 内存中,用于存储光栅像素的 RGBA 值,通常每个 R、G、B 和 A 分别占用 8 位,总计每个像素 32 位。因此,颜色缓冲区保存着单帧的数据,并在该帧被绘制到显示器时进行读取。
在双缓冲系统中,会提供两个颜色缓冲区的空间,其中一个缓冲区(可查看缓冲区)保存当前显示在显示器上的帧,而下一帧则在第二个缓冲区(可绘制缓冲区)中绘制。当在可绘制缓冲区中绘制下一帧完成时,缓冲区会进行交换,这样下一帧就变为可查看的,同时下一帧的绘制也开始。这个绘制和交换的循环在动画过程中不断重复。
可查看缓冲区(Viewable)通常被称为前缓冲区或主缓冲区,而可绘制缓冲区(Drawable)则被称为后缓冲区或交换缓冲区。任一缓冲区也被称为刷新缓冲区。
双缓冲极大地提高了动画的质量,因为它能隐藏连续帧之间的过渡。而单缓冲则不然,观众会看到下一个帧在包含当前帧的同一个缓冲区中被绘制出来。其结果可能是令人不快的重影,之所以这样称呼,是因为在绘制下一个图像时,之前的图像仍会残留。
运动的实现离不开物理,这一块内容暂时不进行详细了解
4.6 Viewing Transformation
管理相机位姿(相机在世界坐标系中的位置和朝向)
gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz) ,注意这些坐标都是由世界坐标系描述的
模拟 OpenGL 相机第一次移动到位置 eye = (eyex, eyey, eyez),然后指向 center = (centerx, centery, centerz),并且围绕其视线(line of sight,los)旋转 连接eye到center的线,以便其向上方向是从 up = (upx, upy, upz)
将glTranslatef(0.0, 0.0, -15.0)替换为 gluLookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
为什么glTranslatef(0.0, 0.0, -15.0)和 gluLookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0) 渲染后效果是一样的?
原因:使用函数glTranslatef(0.0, 0.0, -15.0)时的情况,相机和物体都在世界坐标系原点,将物体通过函数平移到了相机的视锥体内
使用函数 gluLookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0) 时的情况,物体在世界坐标系原点,将相机平移到了(0,0,15)的位置,物体落到了平移后的相机视锥体内
仅修改相机的center,了解该参数的含义(相机镜头指向的点)
仅修改相机的up,了解该参数的含义(摄像机围绕其视线(z 轴)旋转,因此其向上方向每次都指向沿向上矢量 (upx, upy, upz))
upx=1,相机up方向朝向x轴正方形
相机从原来朝向基础上向右旋转90°
upy为-1,相机up方向朝y轴负方向
相机从原来朝向基础上向左/右旋转180°
4.6.1 Understanding the Viewing Transformation
基础知识铺垫
点积
点积的一个特别有用的应用是:当想要将给定的向量 v 拆分为 v = v1 +v2 时,其中分量 v1 和 v2 分别平行和垂直于另一个给定的非零向量 u。
将上述点积应用到计算相机的up direction上
将up向量分解为
up
1
\text{up}_1
up1、
up
2
\text{up}_2
up2、其中
up
1
\text{up}_1
up1与视线los共线,
up
2
\text{up}_2
up2在平面p上,为相机的up direction
相机位置
eye
=
(eyex,eyey,eyez)
\text{eye}=\text{(eyex,eyey,eyez)}
eye=(eyex,eyey,eyez)
相机“看向”
center
=
(centerx,centery,centerz)
\text{center}=\text{(centerx,centery,centerz)}
center=(centerx,centery,centerz)
相机up direction:
up
2
=
(upx,upy,upz)
\text{up}_2=\text{(upx,upy,upz)}
up2=(upx,upy,upz)
例子:计算每个veiwing transformation的相机up direction
gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz)
gluLookAt(0.0, 0.0, 5.0, 5.0, 0.0, 0.0, 0.0, 1.0, 1.0)
注意:Collectively, the modeling transformations glTranslatef(), glRot
atef() and glScalef() and the viewing transformation gluLookAt() are called modelview transformations.
4.6.2 Simulating a Viewing Transformation with Modeling Transformations
当我们引入 gluLookAt() 时,我们说它模拟了 OpenGL 相机的运动。这是完全正确的。OpenGL 相机永远不会在原点处保留其默认姿势,其镜头指向 -z 方向,顶部沿 +y 方向对齐。换句话说,视锥体(或框)保持在它第一次使用 glFrustum()(或 glOrtho())创建的位置。
也就是做第一帧相机坐标系原点和世界坐标系原点重合
实际上,通过将viewing transformation为等效的modeling transformations序列来模拟viewing transformation。
4.6.3 Orientation and Euler Angles
gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz)
等价于
相机中心eye在世界坐标系中的坐标eye=(eyex,eyey,eyez),通过glTranslatef(-eyex,eyey,-eyez)将相机中心移到世界坐标系原点