当前位置: 首页 > article >正文

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 中,有三种简单的方法可以控制动画:

  1. 通过键盘或鼠标输入以交互方式,借助其回调例程(callback routine)来调用转换
  2. 用glutIdelFunc(idle_function)语句调用idle函数,idle 函数在没有 OpenGL 事件时处于待处理状态时调用。
  3. 通过指定一个常规定时器函数(称为定时器函数),并调用 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)将相机中心移到世界坐标系原点


http://www.kler.cn/a/523668.html

相关文章:

  • 虹科分享 | 汽车NVH小课堂之听音辨故障
  • [STM32 - 野火] - - - 固件库学习笔记 - - -十三.高级定时器
  • Deepseek技术浅析(一)
  • mysql DDL可重入讨论
  • C语言基础3
  • 动态规划DP 数字三角型模型 最低通行费用(题目详解+C++代码完整实现)
  • 高通Yocto项目 - 全解析
  • 【MQ】探索 Kafka
  • 【Unity3D】Unity混淆工具Obfuscator使用
  • 51单片机入门_01_单片机(MCU)概述(使用STC89C52芯片;使用到的硬件及课程安排)
  • PAT甲级-1022 Digital Libiary
  • Python JSON:深入解析与高效应用
  • 21.Word:小赵-毕业论文排版❗【39】
  • PHP 7 新特性
  • JAVA实战开源项目:蜗牛兼职平台(Vue+SpringBoot) 附源码
  • 数论问题74
  • Linux C++
  • 「Unity3D」在Unity中使用C#控制显示Android的状态栏
  • 02数组+字符串+滑动窗口+前缀和与差分+双指针(D5_双指针)
  • Oracle PMON进程清洗功能
  • Unbutu虚拟机+eclipse+CDT编译调试环境搭建
  • DeepSeekMoE:迈向混合专家语言模型的终极专业化
  • 从腾讯云数据仓库TCHouse安全地转移数据到AWS Redshift
  • SQL在DBA手里-改写篇
  • 实验七 带函数查询和综合查询(2)
  • Deepseek-R1性能指标