数码控科技猎奇Iphone动漫星座游戏电竞lolcosplay王者荣耀攻略allcnewsBLOGNEWSBLOGASKBLOGBLOGZSK全部技术问答问答技术问答it问答代码软件新闻开发博客电脑/网络手机/数码笔记本电脑互联网操作系统软件硬件编程开发360产品资源分享电脑知识文档中心IT全部全部分类 全部分类技术牛文全部分类教程最新 网页制作cms教程平面设计媒体动画操作系统网站运营网络安全服务器教程数据库工具网络安全软件教学vbscript正则表达式javascript批处理更多»编程更新教程更新游戏更新allitnewsJava 新闻网络医疗信息化安全创业站长电商科技访谈域名会议专栏创业动态融资创投创业学院 / 产品经理创业公司人物访谈营销 开发数据库服务器系统虚拟化云计算 嵌入式移动开发作业作业1常见软件all电脑网络手机数码生活游戏体育运动明星影音休闲爱好文化艺术社会民生教育科学医疗健康金融管理情感社交地区其他电脑互联网软件硬件编程开发360相关产品手机平板其他电子产品摄影器材360硬件通讯智能设备购物时尚生活常识美容塑身服装服饰出行旅游交通汽车购房置业家居装修美食烹饪单机电脑游戏网页游戏电视游戏桌游棋牌游戏手机游戏小游戏掌机游戏客户端游戏集体游戏其他游戏体育赛事篮球足球其他运动球类运动赛车健身运动运动用品影视娱乐人物音乐动漫摄影摄像收藏宠物幽默搞笑起名花鸟鱼虫茶艺彩票星座占卜书画美术舞蹈小说图书器乐声乐小品相声戏剧戏曲手工艺品历史话题时事政治就业职场军事国防节日风俗法律法规宗教礼仪礼节自然灾害360维权社会人物升学入学人文社科外语资格考试公务员留学出国家庭教育学习方法语文物理生物工程学农业数学化学健康知识心理健康孕育早教内科外科妇产科儿科皮肤科五官科男科整形中医药品传染科其他疾病医院两性肿瘤科创业投资企业管理财务税务银行股票金融理财基金债券保险贸易商务文书国民经济爱情婚姻家庭烦恼北京上海重庆天津黑龙江吉林辽宁河北内蒙古山西陕西宁夏甘肃青海新疆西藏四川贵州云南河南湖北湖南山东江苏浙江安徽江西福建广东广西海南香港澳门台湾海外地区

OpenGL ES 学习札记– 透视

来源:本网整理
OpenGL ES 学习笔记– 透视

?

现在你已经知道OpenGL是怎样绘图的了,让我们回头谈谈一个很重要的概念:OpenGL视口(viewport)。 许多人对3D编程还很陌生,那些使用过像Maya, Blender, 或 Lightwave之类3D图形程序的人都试图在OpenGL虚拟世界中找到“摄像机”。但OpenGL并不存在这样的东西。它所有的是在3D空间中定义可见的物体。虚拟世界是没有边界的,但计算机不可能处理无限的空间,所以OpenGL需要我们定义一个可以被观察者看到的空间。

如果我们从大部分3D程序具有的摄像机对象的角度出发来考虑,视口端点的中心就是摄像机。也就是观察者站的位置。它是一个观察虚拟世界的虚拟窗口。观察者可见的空间有一定限制。她看不见她身后的东西。她也看不见视角之外的东西。而且她还不能看见太远的东西。可以认为视口是通过“观察者可见”参数所确定的形状。很简单,对吗?

不幸的是,并非如此。要解释原因,我们首先需要讨论的是在OpenGL ES中具有的两种不同的视口类型:正交和透视。

?

正交和透视

为更好地理解,我们先看看铁路轨道,好吗?要正常工作,铁路的两条铁轨之间必须具有固定的距离。其固定的距离是由铁轨根据承载什么样的火车而决定。重要的是铁轨(以及火车的轮子)必须具有相同的距离。如果不是这样,火车根本不可能运行。

如果我们从上方观察铁轨,这个事实很明显。

tracks

但是如果你站在铁轨上向下观察会怎么样。不要说“你会被火车撞”,我假设你会足够聪明,会在没有火车开动时进行观察。

tracks-perspective

是的,铁轨看上去越远与靠近。感谢二年级美术老师,可能你已经知道这就是所谓透视(perspective)。

OpenGL可以设定的视口中的一种就是使用透视。当你这样设置视口时,物体会随着移远而越来越小,视线会在物体移离观察者时最终交汇。这是对真实视觉的模拟;人们就是以这种方式观察世界的。

另一种看设置的视口称为正交(orthogonal)?视口。这种类型的视口,视线永远不会交汇而且物体不会改变其大小。没有透视效果。对于CAD程序以及其他各种目的是十分方便的,但因为它不像人们眼睛观察的方式所以看上去是不真实的,通常也不是你所希望的。

对于正交视口,你看将摄像机置于铁轨上,但这些铁轨永远不会交汇。它们将随着远离你的视线而继续保持等距。即使你定义了一个无限大的视口(OpenGL ES并不支持),这些线仍保持等距。

正交视口的优点是容易定义。因为线永不交汇,你只需定义一个像箱子一样的3D空间,像这样:

viewport

设置正交视口

在使用glViewport()函数定义视口前,你可以通过glOrthof()通知OpenGL ES你希望使用正交视口。下面是一个简单的例子:

    CGRect rect = view.bounds;
    glOrthof(-1.0,                                          // Left
              1.0,                                          // Right
             -1.0 / (rect.size.width / rect.size.height),   // Bottom
              1.0 / (rect.size.width / rect.size.height),   // Top
              0.01,                                         // Near
              10000.0);                                     // Far
    glViewport(0, 0, rect.size.width, rect.size.height);

这不难理解。首先我们获取视窗的尺寸。然后设定视口空间的宽度为两个单位,沿x轴从 -1.0 到 +1.0。很容易吧。

接着怎样设定底部和顶部?我们希望我们定义空间的X和Y坐标的宽高比与视窗的宽高比(也就是iPhone全屏时的宽高比)一样。由于iPhone的宽度与高度不同,我们需要确保视口的x和y坐标不同,但遵循一样的比例。

之后,我们定义了?near(远)和?far(近)?范围来描述观察的深度。?near?参数说明了视口开始的位置。如果我们站在原点处,视口就位于我们的面前,所以习惯上使用?.01?或?.001?作为正交视口的起点。这使得视口处于原点“前方”一点点。far?可以根据你程序的需要来设定。如果你程序中的物体永远不会远过20个单位,那么你不需要将?far设置为20,000 个单位。具体的数字随程序的不同而不同。

调用?glOrthof()之后,我们使用视窗矩形来调用?glViewport()。

这是比较简单的情况。

设置透视视口

另一种情况就不那么简单,这里是原因。如果物体随着远离观察者而变小,那么它和你定义的可见空间的形状有什么关系。随着视线越来越远,你可以看到更广阔的世界,所以如果你使用透视,那么你定义的空间将不是一个立方体。是的,当使用透视时可见空间的形状称为锥台(frustum)。 是的,我知道,奇怪的名字。但却是真实的。我们的锥台看上去像这样:

frustum

请注意当我们离视口越来越远时(换句话说,当z值减小时),观察体的x和y坐标都会越来越大。

要设置透视视口,我们不使用?glOrthof(),我们使用一个不同的函数?glFrustumf()。此函数使用同样的六个参数。很容易理解,但我们应该怎样确定传递给?glFrustumf()的参数?

near?和?far?容易理解。你可以同样方式理解它们。near 使用类似?.001的数值,然后根据不同程序的需要确定?far?值。

但是?left,?right,?bottom, 和?top 呢??为设置这些值,我们需要一点点数学计算。

要计算锥台,我们首先要理解视野(field of vision)的概念,它是由两个角度定义的。让我们这样做:伸出双臂手掌合拢伸向前方。你的手臂现在指向你自己锥台的z轴,对吗?好,现在慢慢分开你的双臂。由于在你双臂展开时肩膀保持不动,你定义了一个逐渐增大的角度。这就是用于定义观察锥台的两个角度之一。它定义了视野的宽度。另一个角度的定义原理一样,只是这次你向上下展开你的双臂。如果你的双手间距只有三英寸,那么x角度将非常小。

narrow_field

这称为窄视野。

如果你双手分开两英尺,视野的宽度变得很大。

wide_field

这就是所谓?宽视角(广角)。

如果用摄影术语描述,你可将视野当作虚拟相机的虚拟光圈的焦距。窄视野很像摄远镜头,它造就了一个缓慢增长的长锥台。宽视角就像广角镜,它造就了一个增长很快的锥台。

我们选择一个中间值,例如45°。 使用这个值,我们怎样计算我们的观察锥台?我们先看下两个角度中的一个。想象一下,从顶部看锥台是什么样子。下面是示意图:

topview

从上向下看,它就像一个砍掉一个点的三角形。但对我们而言,它已经足够接近一个三角形。你还记得三角课上的正切吗?正切函数定义为直角对边与相邻边的比率。

240px-TrigonometryTriangle.svg

但是,我们没有直角,是吗?

实际上,我们有两个直角… 如果我们沿z轴向下画一条直线的话:

split_triangle

中心虚线就是两个直角的“相邻边”。所以,锥台远端宽度的一半就是视野角度正切的一半。如果我们将此值乘以?near值,就可以得到?right值。right值取反就是?left。

我们希望视野具有与屏幕一样的长宽比,所以按照?glOrthof()中相同的方法(将?right 乘以屏幕的长宽比)来计算top 和 bottom 值。代码如下:

CGRect rect = view.bounds;
GLfloat size = .01 * tanf(DEGREES_TO_RADIANS(45.0) / 2.0); 

glFrustumf(-size,                                           // Left
            size,                                           // Right
           -size / (rect.size.width / rect.size.height),    // Bottom
            size / (rect.size.width / rect.size.height),    // Top
            .01,                                          // Near
            1000.0);                                          // Far

注意:关于?glFrustum() 怎样使用传递的参数计算锥台的形状将在我们讨论矩阵时讨论。现在,我们暂且相信计算是正确的,好吗?

让我们运用到程序中。我修改了上篇文章中最终的?drawView:方法,我们将沿z轴向下显示了三十个二十面体。下面是新的?drawView:?方法:

- (void)drawView:(GLView*)view;

{
    static GLfloat rot = 0.0;

    static const Vertex3D vertices[]= {
        {0, -0.525731, 0.850651},             // vertices[0]
        {0.850651, 0, 0.525731},              // vertices[1]
        {0.850651, 0, -0.525731},             // vertices[2]
        {-0.850651, 0, -0.525731},            // vertices[3]
        {-0.850651, 0, 0.525731},             // vertices[4]
        {-0.525731, 0.850651, 0},             // vertices[5]
        {0.525731, 0.850651, 0},              // vertices[6]
        {0.525731, -0.850651, 0},             // vertices[7]
        {-0.525731, -0.850651, 0},            // vertices[8]
        {0, -0.525731, -0.850651},            // vertices[9]
        {0, 0.525731, -0.850651},             // vertices[10]
        {0, 0.525731, 0.850651}               // vertices[11]
    };

    static const Color3D colors[] = {
        {1.0, 0.0, 0.0, 1.0},
        {1.0, 0.5, 0.0, 1.0},
        {1.0, 1.0, 0.0, 1.0},
        {0.5, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.5, 1.0},
        {0.0, 1.0, 1.0, 1.0},
        {0.0, 0.5, 1.0, 1.0},
        {0.0, 0.0, 1.0, 1.0},
        {0.5, 0.0, 1.0, 1.0},
        {1.0, 0.0, 1.0, 1.0},
        {1.0, 0.0, 0.5, 1.0}
    };

    static const GLubyte icosahedronFaces[] = {
        1, 2, 6,
        1, 7, 2,
        3, 4, 5,
        4, 3, 8,
        6, 5, 11,
        5, 6, 10,
        9, 10, 2,
        10, 9, 3,
        7, 8, 9,
        8, 7, 0,
        11, 0, 1,
        0, 11, 4,
        6, 2, 10,
        1, 6, 11,
        3, 5, 10,
        5, 4, 11,
        2, 7, 9,
        7, 1, 0,
        3, 9, 8,
        4, 8, 0,
    };

    glLoadIdentity();
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glColorPointer(4, GL_FLOAT, 0, colors);
    for (int i = 1; i <= 30; i++)
    {
        glLoadIdentity();
        glTranslatef(0.0f,-1.5,-3.0f * (GLfloat)i);
        glRotatef(rot, 1.0, 1.0, 1.0);
        glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);
    }
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot+=50 * timeSinceLastDraw;
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}

如果你把上述代码加入OpenGL Xcode项目模板?项目中(它使用glFrustumf()设置了一个具有45°视野的透视视口),你将看到下面图形:

frustum_simulator

很好。随着几何体远离你,它们会变得越来越小,正像火车铁轨一样。

如果你只是将?glFrustumf()改为?glOrthof(),看上去就完全不同了:

iPhone SimulatorScreenSnapz002

没有透视,第一个二十面体后面的二十九个二十面体完全被第一个挡住了。因为没有透视,后面的各几何体的形状完全取决于其前方的物体。

好了,这是一个很沉闷的主题,事实上你现在可以完全忘却三角课学的知识了。只要复制基于视野角度计算锥台的两行代码就好,而且你可能再也不需要记住它的原理了。

继续下一次激动人心的冒险旅程…

下一篇文章,我们将为二十面体增加光效,使它看上去更真实。

lights

?

  • 本文相关:
  • 转:OPENGL 如何画线画面
  • 怎么查看那个进程开放了某个端口
  • object-c 的错误构造,并抛出
  • HttpClient的运用1
  • 系统剔除照片动画
  • 拖拽Listview 背景替黑色
  • TableLayout格局中容易混淆的属性
  • xcode4.2出现ARC异常解决办法
  • openGLES学习札记——glOrthof
  • tabActivity的容易实现
  • 免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved