计算机图形:二维观察
二维观察流水线
- 基本概念
裁剪窗口(clipping window):二维场景中要显示的部分。区域外场景都要裁去,只有裁剪窗口内的场景才能在屏幕上显示。裁剪窗口有时暗指世界窗口(world window)或观察窗口(viewing window)。裁剪窗口变小,可以观察到场景细节。
视口(viewport):用来控制在显示窗口的定位。对象在裁剪窗口内的部分映射到显示窗口中指定位置的视口中。多个视口,可在不同位置观察场景的不同部分,改变视口的尺寸可改变显示对象的尺寸和位置。
裁剪窗口与视口不同点:窗口选择要看什么内容,视口指定输出设备在什么位置观察。
相同点:一般都是正则矩形(边与坐标轴平行),也可以用多边形,圆等形状。
二维观察变换(two-dimensional viewing transformation):场景描述从二维世界坐标系到设备坐标系的映射。也称窗口到视口的变换(window-to-viewport transformtion)或窗口变换(windowing transformation)。
二维观察变换步骤:
tips:规范化设备坐标系指设备坐标范围0 to 1或者-1 to 1。
裁剪窗口
图形软件仅支持正则矩形(边平行x/y轴)的矩形裁剪窗口,要实现特殊裁剪效果,如椭圆/样条曲线等形状,需要自己实现裁剪和坐标变换算法。
观察坐标系裁剪窗口
通常,在世界坐标系(world coordinate)中指定一个观察坐标系(view coordinate),再选定裁剪窗口:
建立观察坐标系方法:
-
世界坐标系下,位置\(P_0(x_0, y_0)\)作为观察坐标系原点,向量V作为\(y_{view}\)轴方向,则V称为二维观察向上向量(view up vector)。
-
在世界坐标系中旋转x、y轴一定角度,从而获得观察向上向量;然后将原点平移至目标位置。
设\(u=(u_x, u_y), v=(v_x, v_y)\)分别为观察坐标系$ x_{view}, y_{view}$轴方向向量,对象坐标转换(世界坐标=>观察坐标)矩阵:
T是将观察原点由\(P_0\)到世界原点的平移变换,R是平移变换后的观察坐标系与世界坐标系重合的旋转变换。
注意:点的坐标变换与坐标系的变换方向相反,参见计算机图形:三维坐标系变换。
规范化和视口变换
视口坐标:0~1,即位于单位正方形内。裁剪后,单位正方形映射到输出显示设备。视口边界在与显示窗口对应的屏幕坐标系指定。
有些图形系统,将规范化早窗口-视口转换之前,有些合并成一步。
裁剪窗口->规范化视口
一般过程:
1)定义一个视口,坐标0~1;
2)按点的变换方式,将对象描述变换到该视口;
窗口内的点(xw, yw) -> 视口的点(xv, yv):
视口中的对象与窗口有相同的相对位置,满足:
可得关于(xv, yv)的方程:
其中,\(s_x, s_y\)称为缩放系数,\(t_x, t_y\)称为平移参数。
式(3)是通过相对位置不变,来求出裁剪窗口到视口的变换。也可以通过平移、缩放操作,得到变换矩阵:
- 以点\((xw_{min}, yw_{min})\)为中心执行缩放变换,将窗口变换成视口的大小;
- 将\((xw_{min}, yw_{min})\)平移到\((xv_{min}, yv_{min})\)。
复合转换矩阵:
其中,\(s_x, s_y, t_x, t_y\)含义同(4)。
注意:只有裁剪窗口、视口具有相同纵横比,才能保持对象的相对比例不变。
规范化正方形是中心在原点、长宽都为2(x/y坐标都是-1~1)的正方形。
OpenGL二维观察函数
OpenGL只提供三维观察函数,不过可用于二维观察,且核心库包含一个视口函数。
GLU库提供指定二维裁剪窗口的函数,GLUT库提供处理显示窗口的函数。
OpenGL投影模式
选择裁剪窗口和视口前,必须先选择投影模式,将裁剪窗口到参数作为投影变换到一部分来设置。
glMatrixMode(GL_PROJECTION);
该函数将指定投影矩阵作为当前矩阵,它原来设定为单位矩阵。
下面函数将初始化当前矩阵为单位阵:
glLoadIdentity();
GLU裁剪窗口函数
定义一个二维裁剪窗口:
// xwmin, xwmax, ywmin, ywmax是二维世界坐标系下裁剪窗口边界
gluOrtho2D(xwmin, xwmax, ywmin, ywmax);
将场景映射到屏幕的正交投影。对于二维平面,只是将对象位置转换到规范化坐标系。
如果没有指定裁剪窗口,就用默认坐标\((xw_{min}, yw_{min})=(-1.0,-1.0), (xw_{max},yw_{max})=(1.0,1.0)\)。
OpenGL视口函数
指定视口参数:
// xvmin, yvmin 指定视口左下角位置, 与显示窗口左下角对应
// vpwidth vpHeight 视口的宽度像素数和高度像素数
glViewport(xvmin, yvmin, vpWidth, vpHeight);
如果没有设置视口,则默认视口位置、大小与显示窗口一样(注意不是裁剪窗口)。
GLUT显示窗口
- 创建GLUT显示窗口
GLUT库建立和管理显示窗口,不依赖与任意特定计算机。
// 初始化GLUT库
glutInit(&argc, argv);
// 设置窗口属性
glutInitWindowPosition(xTopLeft, yTopLeft); // 窗口位置
glutInitWindowSize(dwWidth, dwHeight); // 窗口大小
glutCreateWindow("Title of Display Window"); // 创建窗口并指定标题
如果没有设置窗口属性,则默认尺寸300x300,默认位置(-1, -1)(交给窗口管理系统)。
- GLUT显示窗口模式和颜色
选择颜色模式(RGB或索引号和不同的缓存组合,参数用逻辑或方式组合。
glutInitDisplayMode(mode);
默认模式:单缓存和RGB(或RGBA, GLUT_RGB等价于GLUT_RGBA)颜色模式。<=>
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
设置显示窗口颜色:
1)在RGB模式下
glClearColor(red, green, blue, alpha);
2)在颜色索引模式下
// index 颜色表中对应颜色值的索引
glClearIndex(index);
- GLUT显示窗口标识(标题)
GLUT会从1开始,为每个窗口设定一个显示窗口标识(display-window Identifier)。
窗口标识windowID 标识窗口,后续可用于显示参数和删除显示窗口。
// 返回windowID, 保存了显示窗口标识
int windowID = glutCreateWindow("A display window");
- 删除GLUT显示窗口
glutDestroyWindow(windowID);
- 当前GLUT显示窗口
指定的任何一个显示窗口操作,都针对当前显示窗口,即最后创建的当前显示窗口(current display window),或者用下面命令指定:
glutSetWindow(windowID);
查询当前的显示窗口:
currentWindowID = glutGetWindow();
没有显示窗口或当前显示窗口已被删除时,返回0。
- 修改GLUT显示窗口位置、大小
设置当前显示窗口的位置
// xNewTopLeft, yNewTopLeft 显示窗口左上角相当于屏幕左上角的新位置
glutPositionWindow(xNewTopLeft, yNewTopLeft);
设置当前显示窗口的尺寸
glutReshapeWindow(dwNewWidth, dwNewHeight);
扩展窗口到整个屏幕
glutFullScreen();
改变窗口尺寸/位置后,可能改变窗口纵横比,可用下面命令调整显示窗口到变化:
glutReshapeFunc(winReshapeFcn);
- 管理多个GLUT显示窗口
将当前显示窗口变成一个图符,通过小图片或符号形式表示窗口
// 图符使用窗口的名字标记
glutIconifyWindow();
改变图符名字
glutSetIconTitle("Icon Name");
改变显示窗口名字
glutSetWindowTitle("New Window Name");
多窗口下,可指定某个显示窗口为当前窗口,调用“pop-window”使其在其他窗口之前,“push-window”。使其在其他窗口之后。
将窗口置于顶层
glutSetWindow(windowID);
glutPopWindow();
将窗口置于底层
glutSetWindow(windowID);
glutPushWindow();
隐藏窗口
glutHideWindow();
显示窗口
glutShowWindow();
- GLUT子窗口
可以在一个选中的显示窗口中建立任意数量的二级显示窗口,称为子窗口。用于将显示窗口分成不同的显示区域。
创建子窗口:
// windowID 标识父窗口
// BottomLeft, yBottomLeft指定子窗口位置; width, height指定子窗口大小
glutCreateSubWindow(windowID, xBottomLeft, yBottomLeft, width, height);
- 显示窗口屏幕光标形状
设置当前窗口屏幕光标形状。可选择的光标形状:按选定方向指向的箭头、双向箭头、旋转箭头、十字游丝、手表、问号,头盖骨和交叉腿骨图案。可以形状依赖于OS。
e.g. GLUT_CURSOR_UP_DOWN 上下箭头;GLUT_CURSOR_CYCLE旋转箭头;GLUT_CURSOR_WAIT手表图案;GLUT_CURSOR_DESTROY头盖骨和交叉腿骨图案。
glutSetCursor(shape);
- GLUT显示窗口中观察图形对象
创建显示窗口并选定其位置、大小、颜色和其他特征后,需要指定窗口中显示什么。
// pictureDescrip 作为回调函数,指定当前窗口显示什么
glutDisplayFunc(pictureDescrip);
如果创建了多个显示窗口,则需要为每个显示窗口或子窗口重复这一过程。
如果显示窗口在重新显示过程中被破坏,且pop-window命令后调用了glutDisplayFunc,则需要用下面函数指出当前显示窗口到内容应更新。例如,显示窗口弹出式菜单。
glutPostRedisplay();
执行app(main loop)
程序装载完毕,创建、初始化显示窗口后,需要进入GLUT处理循环(GLUT processing loop),监听鼠标、键盘等交互事件。
glutMainLoop();
背景函数(idle function)
没有其他事件需要系统处理时,可以设置一个背景函数或更新动画参数:
glutIdleFunc(function);
查询系统参数
查询系统某些参数的当前值。e.g. GLUT_WINDOW_X 获取当前显示窗口左上角相当于屏幕左上角的x坐标位置;GLUT_WINDOW_WIDTH或GLUT_SCREEN_WIDTH获取当前显示窗口宽度或屏幕宽度。
glutGet(stateParam);
裁剪算法
裁剪算法(cliiping algorithm):用来消除指定区域内或区域外的图形部分的过程,称为裁剪算法,简称裁剪。通常用正则矩形进行裁剪,边界\(xw_{min}, xw_{max}, yw_{min}, yw_{max}\)。
任何裁剪窗口外内容,都从将要送到输出设备上显示的场景中消除。
根据不同图元类型,二维裁剪算法主要包括:
- 点裁剪
- 线段裁剪(直线段)
- 区域的裁剪(多边形)
- 曲线的裁剪
- 文字的裁剪
点裁剪
假设裁剪窗口是一个在标准位置的矩形(边平行于x、y轴),如果点P(x, y)满足:
线段裁剪
线段和标准矩形裁剪区域关系:
1)线段位于矩形内;
2)线段与矩形相交;
3)线段位于矩形外。
线段裁剪以后的裁剪,涉及的裁剪算法较为复杂,后续在专门文章中描述。