新手翻译的FAQ–可视和转换(学opengl两个月) (2)
17.我如何在我的3D渲染上, 绘制我的2D控制器?
基本的策略是对于要描绘的控制点设置一个2D投影.你既可以在你的3D渲染上做这些, 也可以在覆盖面上来完成这个. 假如你选择在3D渲染上绘制这些, 那么在每帧结束的时候, 都要重画这些控制点(在交换缓存前, 立即重画). 假如你选择画在覆盖面上, 你只需要在跟新的时候重画.
为了设置一个2D投影, 你需要改变这个投影矩阵. 正常地,这是非常容易设置一个世界坐标的单元, 来对应于一个屏幕的像素, 像这样:
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D( 0, windowWidth, 0, windowHeight );
gluOrtho2D()设置一个-1.0到1.0的Z轴的范围, 所以你可以使用glVertex2*()函数中的一种来确保你的几何图元不会被zNear和zFar剪裁面所剪裁.
正常地, 当绘制2D控制点的时候, 都把模型视点矩阵设置会单位矩阵.所以你会发现非常容易做些别的( 例如, 你可以使用交叉存取的平移矩阵来重复的画控制点).
假如需要精确的像素操作, 你可以把一个小型的平移放入模型视点矩阵, 如下所示:
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glTranslatef( 0.375, 0.375, 0.0 );
假如你在一个3D 深度缓存的图像上进行绘制, 当你绘制你的2D几何图元你需要关闭深度测试(depth test). 你可以通过调用 glDisable( GL_DEPTH_TEST ) 或者是glDepthFunc( GL_ALWASY ).根据你的程序, 你可能在开始2D绘制的时候简单的清除了深度缓存. 最终, 绘制2D几何图元在你最你的Z轴深度上, 也不失为一个解决方法。
在2D投影矩阵按如上所示的建立后,你就可以渲染正确的OpenGL图元到屏幕上, 详细的定义他们的X, Y像素的地址( 使用OpenGL 中心的屏幕坐标, (0, 0)在左下角).
———————————————————————————
18.我如何越过OpenGL矩阵变换而直接让2D坐标光栅化?
这并不是OpenGL矩阵转换中的一种开关模式. 然而, 假如你使用了glLoadIdentity()调用设置了设置了两个单位矩阵, 典型的OpenGL执行有足够的智力,来知道这是一个单位矩阵转换是不用操作的并因此来行动.
更多的有关纯粹把OpenGL当做一个光栅化的API的信息, 请参考:OpenGL Game Developer’s FAQ.
———————————————————————————-
19.使用绝对坐标,或者是相对坐标的好处, 和坏处?
一些OpenGL可能需要在单一的场景中使用更多的坐标来渲染同样的物体.OpenGL有两中方法做这个:
1.使用”绝对坐标”.支持每个物体的多份拷贝, 每个都有他们自己唯一的顶点集合.你不需要去改变
模型视点矩阵在需要的位置对物体进行渲染.
2.使用”相对坐标”.会保存物体的唯一一 份拷贝, 通过压入模型视点矩阵堆栈, 设置需要的转换,
发送几何数据, 并弹出堆栈来对他们进行多次的渲染. 对每个物体重复这些步骤.
一般来说, 经常的改变状态, 例如模型视点矩阵, 会对你的效率有负面的影响. 假如你没有对每个单独的图元都在模型视点转换使用大量的转换, 那OpenGL是可以快速的处理你的几何数据的.
然而,你有时候可能需要权衡对图像进行多分的拷贝来放弃对内存的节省. 让我们假设你定义了一个高拟真度的门把手,使用了大约200, 300个三角形, 而你对一个房子的建模将会使用50个门把手,它们都使用了相同的门把手. 这个更加好的是使用单独门把手的显示列表, 然后定义多个单独的矩阵转换, 而不是在内存使用大约10-15K的空间来定义绝对坐标.
当有更多的计算主题时, 那么不得不在CPU时间和存储空间之间进行选择. 而你也不得不对此进行更多的尝试.
———————————————————————————-
20.我如何在我的场景中绘制更多的视图?
你可以通过使用glViewport()调用在同样的窗口绘制两个视图. 设置glViewport()对准那个你想作为第一个视图的地区, 设置你的场景观察, 和渲染. 然后设置glViewport()对准那个你想作为第二个视图的地区, 再一次设置你的场景观察, 和渲染.
你可能需要注意有些操作并不会为glViewport()所注意, 例如glSwapBuffers()和glClear(). glSwapBuffers()总是交换当前的整个窗口. 然而, 你可以通过使用剪取矩形来限制你的glClear()专门只对一个矩形窗口.
你的程序有可能只允许在不同的窗口中使用不同的视图. 假如这样, 你需要在两个渲染器中执行一个针对当前的操作. 假如两个窗口共享一个上下文环境, 你需要向上面描述的那样改变场景的视图. 假如你的程序对每个窗口使用分离的上下文, 那么这些操作是没有必要的.
———————————————————————————-
21.我如何使我的物体围绕一个固定的坐标系统, 而不是物体的本地坐标系统?
假如你使一个物体, 围绕Y轴旋转, 你就会发现X轴, 和Y轴在围绕这个物体旋转.一个后续的旋转围绕这些轴进行新的转换轴旋转, 而不使用原始的轴. 在一个固定坐标轴系统中, 而不是物体本地坐标系统中, 执行转换是非常另人满意的.
这个 OpenGL Game Developer’s FAQ 包含了使用四元数来存储转换的信息, 在解决这类问题时, 这些资料有可能很有用.
导致这个问题的主要原因是OpenGL矩阵操作是在矩阵堆栈上的自右乘法, 因此导致了出现在物体空间的转换.为了影响屏幕空间转换, 你需要使用自左乘法. OpenGL并没有提供模式的开关来对应矩阵相乘的顺序, 所以你需要手动自左乘法.一个程序可能在每帧后通过取回当前矩阵来对此进行实现. 这个程序乘以新的转换来对应于单位矩阵上的下一帧, 并乘以使用glMultiMatrix()的矩阵上的堆积的当前转换(来自上一帧).
你需要注意的就是每次得到一帧的模型视点矩阵都可能对你的程序执行效率有负面的影响. 然而你可以对这些操作进行测试, 因为这个执行效率是对应于不同的执行而不同的 .
———————————————————————————-
22.使用glFrustum相对于gluPerspective的好处, 于坏处?为什么我们
glFrustum ()和gluPerspective()都会产生透视投影矩阵, 它们把眼坐标转换到剪裁坐标而来.这个主要的不同在于glFrustum更多的是产生和允许离轴(off-axis)的矩阵, 而gluperspective可以产生对称的on-axis的矩阵. 确实, 你可以使用glFrustum来顶替gluPerspective. 然而,除了这个层次的调用本身是glu接口本身的一部分外, 使用glFrustum所产生的矩阵对于 gluPerspective来说, 本身也没有什么特殊的性能优势.
自从glFrustum相对于gluPerspective更加的常规, 你就可以在万一不能使用gluPerspective的时候, 使用它. 一些例子包括在 projection shadeows, 平铺渲染, 和立体视图中.
平铺渲染 使用在多非轴线矩阵来渲染场景中的不同地方. 这个结果是集合了一个大型的图象数据数组来产生最终的图象 这个是非常必要的当这个最终渲染的期望的维数超过了OpenGL所能执行的最大视口维数.
在立体视图中, 同样的场景的两个渲染图将会被作为有轻微的改变的可视观察位置. 当这个观察轴在眼睛的右边, 每个视图都必须使用轻微的off-axis矩阵到任一边, 来达到正确的可视结果.
———————————————————————————
23.我如何执行一个到glFrustum的调用来匹配我对gluPerspective()?
这个你的glFrustum()调用的可视的角度(fov)是:
fov * 0.5 = arctan( (top - bottom) * 0.5 / near )
当 bottom == -top 对于由gluPerspective所产生的对称投影, 然后:
top = (tan( fov * 0.5)) * near;
bottom = -top
注意: 上面公式中的fov的单位必须使用弧度,来配合在C语言的数学库. 假如你想使用角度计算FOV(例如调用gluPerspective), 需要这样进行计算:
top = tan( fov * 3.1415926 / 360 )
左边和右边的参数,只需要简单的使用 top, bottom, aspect来计算:
left = aspect * bottom
right = aspect * topp
OpenGL Reference Manual 显示了使用双函数所产生的矩阵.
———————————————————————————-
24. 我如何画一个全屏幕的四边形?
这个问题通常的意思是, ” 我如何画一个可以填充我全部视口的四边形?” 有很多方法可以做到.
最直接的方法就是先设置你所希望的颜色, 设置投影和模型视点矩阵为单位矩阵, 并调用glRectf()来画一个和GL_QUADS相等的图元. 你的矩形或者四面体的Z轴深度应该在-1.0到1.0之间, -1.0映射到zNear剪裁面, 1.0映射到zFar剪裁面.
这里有个例子, 这儿有个如何画一个全屏幕的四面体在zNear剪裁面上:
glMatrixMode( GL_MODELVIEW );
glPushMatrix();
glLoadIdentity();
glMatrixMode( GL_PROJECTION );
glPushMatrix();
glLoadIdentity();
glBegin( GL_QUADS );
glVertex3i( -1, -1, -1 );
glVertex3i( 1, -1, -1 );
glVertex3i( 1, 1, -1 );
glVertex3i( -1, 1, -1 );
glEnd();
glPopMatrix();
glMatrixMode( GL_MODELVIEW );
glPopMatrix();
你的程序, 有可能是需要相应的矩形有个最大的Z轴值, 那样的话, 你只需要修改Z轴用1来代替-1.
当绘制一个全屏幕的矩形时, 你可能屏蔽掉一些缓冲区来让只有特殊的缓冲区可被影响. 例如, 你可能屏蔽掉颜色缓冲区并设置深度函数为 GL_ALWAYS, 所以就只有深度缓冲区被绘制.你也可以设置 码区(mask)来允许模板缓存被设置或者更多的别的复合缓存.
———————————————————————————-
25.我如何找到一个给定物体的空间坐标所对应的屏幕坐标?
假如你只打算得到少数的几个点, 你可以使用GLU库中中的gluProject()函数. 假如要大堆的坐标, 使用系统的回掉机制可以更有效率.
为了使用gluProject(),你必须提供模型视点矩阵, 投影矩阵, 视口, 和输入的物体空间坐标.屏幕空间的坐标将会被以 X, Y, Z的形式返回, 其中Z会被归一化(normalize).
———————————————————————————-
26.我如何可以找到屏幕上一个像素所在的物体空间坐标?
这个GLU库提供了gluUnProject()函数来实现这个操作.
你需要读取深度缓存来获得屏幕上的坐标的Z轴值在 X, Y处, 这段代码可以如下.
GLdouble z;
glReadPixels( x, y, 1, 1, GL_DEPTH_COMPONENT, GL_DOUBLE, &z );
注意 X和Y是相对于屏幕左下角的(0, 0)位置.
你需要提供屏幕空间的X, Y和Z值作为 gluUnProject的输入,另外还有当前像素正在被渲染的模型视点矩阵, 投影矩阵, 和视口.
———————————————————————————-
27.我如何可以找到被模型视点矩阵转换过的像素坐标?
这个通常被用于得到顶点的眼坐标空间的值(i.e,物体的空间顶点被模型视点所转换).你可以通过得到当前的模型视点矩阵并执行向量和矩阵的乘法来得到.
———————————————————————————-28.我如何计算观察者到给定点的物体空间距离?
转换这点到眼坐标空间,通过乘以模型视点矩阵. 然后简单的计算它到原点的距离.(假如这个不能正常的工作, 你可能是把视点转换放入了投影矩阵堆栈中了.)
来得到当前的模型视点矩阵:
GLfloat m[16];
glGetFloatv( GL_MODELVIEW_MATRIX, m );
对于使用任何的OpenGL调用, 你都可以使用glGet*()函数来得当前的窗口的上下文.
———————————————————————————-
29.在一个窗口被重新定义大小后, 我如何保持我的比例系数?
这个是基于你是如何设置你的投影矩阵. 对于任何一种情况, 你都需要知道你的窗口的维数(宽和高). 如何得到这些是基于你使用的平台. 在GLUT, 例如, 这个维数是作为参数传递给 reshape回调函数.
接下来的是假设你的视口都使用和你的窗口同样的尺寸. 假如你没有, 使用窗口高度和宽度来代替视口的高度和宽度.
假如你使用gluPerspective来设置你的投影矩阵, 第二个参数是控制这个比例系数. 当你的程序捕捉到了一个新的大小, 你需要修改的投影矩阵如下:
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( fov, (float) width/(float) height, zNear, zFar );
假如你使用glFrustum, 这个比例系数是将会是使用视景体的宽度和高度. 你可是使用下面的重设窗口大小代码来支持1:1的比例系数:
float cx, halfWidth = windowWidth * 0.5f;
float aspect = (float)windowWidth/(float)windowHeight;
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
/* cx is the eye space center of the zNear plane in X */
glFrustum( cx - halfWidth*aspect, cx + halfWidth*aspect, bottom, top, zNear, zFar);
glOrtho()和gluOrho2D() 的调用视和glFrustum()非常相似的.
———————————————————————————-
30. 我可以让OpenGL使用一个左手系空间?
OpenGL 并没有这样一个模式开关来转换左右手的坐标. 然而,你可以非常容易的通过乘以以个负的Z轴缩放到模型视点矩阵来得到左手坐标系统. 例如:
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glScalef( 1.0, 1.0, -1.0 );
/* 像平常那样乘以视点转换 */
/* 像平常那样乘以模型转换 */
———————————————————————————-
31.我如何转换一个物体使它指向或者跟随场景中的一个物体,或点?
你需要构造一个矩阵 , 它可以转换你的物体的本地坐标系统到一个你所希望面对的方向的坐标系统. 看 example code , 是如何建造这类矩阵的.
假如你仅仅指是想渲染一个物体, 使它可以面对观察者, 你可以考虑通过在眼坐标空间中把模型视点矩阵设置成单位矩阵来实现.
———————————————————————————-
32.我如何转变我的物体使用一个给定的倾角, 仰角, 和斜角?
转换矩阵的左上角的3X3部分, 是由自右转换坐标空间的 X, Y, Z所构成.
———————————————————————————-
33.我该如何渲染处镜面效果?
把你的场景渲染两次, 其中一次, 作为你的镜面的反光. 还有一次,作为来自正常的视图(非反射的). Example Code 示范了这种技术.
对于一个轴对齐的镜面, 例如一个在YZ平面上的镜面. 这个反射场景可以使用简单的缩放和平移. 使用 -1.0来乘以镜面的法向量, 并进行缩放. 并平移两倍的镜面距离.使用适当的转换来渲染这些场景会产生场景在镜面中的反射.使用矩阵堆栈来保存上一次的可视转换的值.
下一步, 使用glClear( GL_DEPTH_BUFFER_BIT )来清空深度缓存.然后渲染镜面.对于一个完美反射的镜面, 只需要简单的渲染深度缓存. 真正的镜面通常是不完美反射的, 他们很容易被一些别的光线所扰乱。为了建造这种效果, 使用混合技术来渲染一个镜面使用0.05的 alpha值. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_ALPHA)对于这个目的来说, 是个很好的混合函数.
最终, 渲染这些非反射场景. 当这个完全的反射场景存在于颜色缓存中, 而不是部分的场景在镜面中.你可能需要改变反射场景中的全部像素来覆盖这个区域,这样它们会变的不可见.
——————————————————————————–
34.我如何做我自己的透视缩放?
OpenGL 会用模型视点矩阵乘以你的坐标系, 然后通过投影矩阵来得到剪裁坐标系统. 然后会执行透视除法来获得规范化设备坐标系.在透视除法阶段会建立一个透视渲染, 使一个在远处的几何体会比在近处的小.透视除法阶段通过用裁减坐标X, Y, Z的值来除以W来完成的. 例如:
Xndc = Xcc/Wcc;
Yndc = Ycc/Wcc;
Zndc = Zcc/Wcc;
为了执行你自己的透视纠正, 你需要获得裁减坐标W的值.回掉(feedback)缓存提供了同样的坐标系统对于XYZ设备坐标和W剪裁坐标.你可以使用 glGetFloatv(GL_CURRENT_RASTER_POSITION, … ), 而且W值会再一次在裁减坐标系统中, XYZ在设备坐标系统中。
标签:纹理映射相关日志
If you enjoyed this post, please consider to leave a comment or subscribe to the feed and get future articles delivered to your feed reader.
