寻找《我的世界》远古城市,然后进入城市中最神秘的密室,一定是你玩1.19必须要做的一件事。那么接下来,我们将从如何寻找古代城市的遗迹,到如何找到密室,以及密室里有什么做出详细的介绍。
第一步:种子选择,首先,我们此次并不采用那个很火的“i want ancient city ”的种子(该种子可以在出生点附近直接找到远古城市)。
所以我用了一个随机种子,SEED:-8043339782656042788,结果还有了一个惊喜的发现,在坐标在(-564,Y,-577)的位置发现一个两个生成在一起的远古城市。并且有两个城市中心。
第二步:寻找古城的秘诀;首先,我们需要找到一个古城,远古城市所在的位置就不再多说了,偏向于《我的世界》海拔更高的地方,当然这并不仅仅只限于山峰,有时候较高海拔的高原群系也可以找到玩家所在的位置。有趣的是,通常在海拔最高的位置向下挖,可以刚好对准远古城市的中心。
如果是两座山脉相连,那就有意思了,很可能会出现两座远古城市相连的罕见情况。事实上,我们今天所使用的种子,便是这种两个古代城市面对面坐落在地底的一个巨大结构。
第三步:找到古城廊道;进入古城之后,玩家首先需要找到一个位于古城中的廊道,这座廊道比较显著的一个特征,是在它的两侧有6个大型石柱,石柱上会有只原因只应该出现在地狱中的灵魂灯笼。而这座廊道,就是直接通向Minecraft 1.19新遗迹“远古城市”正中心所在的位置。
第四步:找到一座木桥;顺着廊道继续向前走,你会遇见一个由深色橡木木板和深色橡木栅栏构成的木桥,这个结构其实老mc玩家非常熟悉,典型的就是废弃矿井的结构。等等,难道说地下的那些废弃矿井,都是来自于这个远古城市文明的产物?
第五步:城市中心;走过木桥,映入眼帘的便是这座城市最中心的位置,实际上更是某种象征着权利文明的信仰。不过与其说是一座雕像,更像是一座巨大的可以激活的传送门。PS:确实不排除,未来这里是否会出现什么诡异的激活传送门的方式。
第六步:找到密室的门!事实上远古城市的密室,确实就在靠近城市中心位置的,由黑色橡木木板组成的桥的下方,便可以找到密室的入口。密室其实就在这座中心建筑的正下方,入口处在深色橡木桥的下方可以看到。这里的密室门,是由一个活塞门控制的。活塞门一共分为三种类型,运气好的话可以直接看到一个打开的活塞门。也就是敞开了门的密室。
第七步:开启密室的方法!在网上流传一个非常经典的版本,是通过吃金苹果开启隐藏门。玩家度过木桥之后,在中心“传送门”下方玩家可以找到一个宝箱,开启宝箱后可以100%获得一个金苹果,然后来到下层的靠墙的密室门处。吃下金苹果,就可以开启传送门。
不得不说,这个开启密室的模式确实是很让人赏心悦目,虽然说吃东西发出震动是可以通过幽匿感测体发出红石信号,从而激活石门,但并不是只有吃东西才会发出震动呀?难道说这是假的?
第八步:三种打开密室门的方法。我们根据密室门的结构不同,大体可分为三类:
第一种,激活幽匿感测体或用拉杆激活门,门会开启,但随后活塞门会关闭;
第二种:振动频率刚好等于8的行为,可以打开活塞门。振动频率由幽匿感测体接受,如生物互动、弹射物击中、进食、实体受伤时发出的振动,都可以打开这个活塞门。所以吃金苹果开门,确实是可行的。
第三种:永久敞开的密室门,因为在探索过程中你激活了幽匿感测体,至此活塞门会长期被打开。所以你看到的自然就是一个开启的密室门。
那么现在就剩下最后一个问题了,密室里有什么?首先整个密室的结构大致分为了7个房间,这里目前没有什么100%会生成的战利品,但这里隐藏起来的红石电路,似乎在告诉我们,红石科技或许就是这个地底文明最核心最珍贵最想要隐藏起来的一个秘密。
(本文参考“我的世界wiki百科”)
在开始之前请确保你拥有游戏内操作员(op)权限且世界开启了无敌模式(即“激活作弊”选项)
一、指令是什么,有什么用,怎么用
指令是为快速建造(fill)、传送(tp)、设置游戏模式(gamemode)、获得物品(give)、击杀实体(kill)、召唤实体(summon)等的快速使用(应用)所产生的,但并不是所有指令都有必要使用,因为它们的复杂程度不如直接通过正常方式完成,下面会进行举例。
指令的使用并没有多复杂,在聊天框中使用时需要在前加“/”,在命令方块时可省略。
二、指令中的代词
在我们生活中常常会运用“你、我、他”的代词,而在“我的世界”中使用的指令同样也是。我们可以用@s来代替“自己”,用@a来代替“所有玩家”,用@p来代替“最近的玩家”,用@r来代替“随机玩家”,用@e来代替“所有实体”。这个没有什么记忆方法,自己熟练了就行,但是毕竟有指令提示,实操的时候都有提示。
三、常见问题
1.提示“此世界未开启无敌模式”
如果你所使用的世界由自己创建,并且使用指令时提示“该世界未开启无敌模式”时请开启“激活作弊”即可解决。如果开启了仍不能解决或无法开启则代表该图禁止了指令的使用,同时你也不能通过管理员权限修改自己的游戏模式。
2.指令直接作为信息发送
在指令前加上“/”即可解决
3.提示“未知的指令”或“语法错误”
有多种可能:没有操作员权限、拼写错误、语法错误
四、常用指令举例
tp用于传送实体,一般在指令栏自带。
使用语法:
/tp somebody
(将自己)传送到某人
例如:/tp 1
/tp somebody1 somebody2
将1传送至2
例如:/tp 1 2
time很常用的指令,用于快速调节、查询时间
使用语法:/time set sometime
例如:
/time set day 时间调至天亮
/time set night 时间调至天黑
3.fill
快速填充方块
使用语法:/fill x1 y1 z1 x2 y2 z2 something(注:xyz均可用~代替,以为当前坐标)
例如:/fill ~ ~ ~ ~ ~ ~ lava 玩家(命令方块)填充岩浆
这个指令在很小范围的填充时不建议使用,没有必要。
4.give
给予物品
使用语法:/give somebody something number
例如/give @s command_block 64给予自己64个命令方块。
第二弹会补充常见指令
五.备注
以上的somebody指某人,number指数量,sometime指某时间
以上内容截止发稿日均正确,如确实存在遗漏的问题,望指教。
要说《我的世界》最令人着迷之处,就是可以寻着这个世界的蛛丝马迹,去探索遍布世界各地的遗迹。然而遗迹要么是埋在地底,要么是深藏遥远的海底,许多人苦苦寻觅而不得。那么,在不使用指令的纯净生存下,该如何去寻找呢?
今天,仅以一位老mc这些年来的一些生存经验,来聊聊寻找遗迹这件事。(注:本篇以JA版为主)
1、废弃矿井;这是绝大多数玩家的启蒙遗迹,也是我在《我的世界》遇到的第一个遗迹,因挖矿而巧遇。但只有碰巧和碰运气这一条路么?并非如此!
声音定位法:废弃矿井内有铁轨和矿车,这就意味着它会发出一种声音“矿车:行进”的声音。当然在地面上这种声音很微弱,这个时候JA版的优势就体现出来了。JA版开启声音字幕,寻找声音传来的方向,你就能100%找到。
当你在世界探险时,记得开启这种声音字幕,当矿车声音出现时。你的脚下就是一大片“废弃矿井”!
2、要塞;水井法:运气使然,水井下方也并非100%有要塞。尤其是在1.14村庄更新之后。但水井对应要塞入口的竖井,这个生成之法是被写进代码的。
末影之眼法:传统的“扔末影之眼”寻找要塞之法就不再赘述,数学计算能力超强的玩家倒是可以考虑利用空间直角坐标系用2颗眼便计算出要塞的坐标。所以要塞其实是所有遗迹当中,最好找的一种类型。
3、大型地下洞穴;村庄定位法:细心的玩家你会发现,大多数的村庄周围都有大概率伴生洞穴。并且洞穴大多数纵深极高,与其千里寻大型洞穴,不如寻个村庄。运气好水井下还有一座巨大的要塞。
4、下界要塞;X轴搜寻法:因为下界要塞极其巨大且长,并且以Y轴的方向生成。所以这种遗迹,打开F3开启坐标顺着X轴的方向去寻找,遇山开山,遇海填海。
声音字幕搜寻法:X轴搜寻+开启声音字幕寻找烈焰人的声音,这会极大地提高你的成功率。因为烈焰人可以在下界要塞的最外围活动,它闲时甚至可以飞得很高!有烈焰人声音的地方,就必定要下界要塞。
5、猪灵堡垒;为什么要和1.16新加入的“猪灵堡垒”放一起说呢?除了猪灵堡垒同样巨大之外,堡垒和要塞也有概率相互连通,尤其是当生成距离极近的时候,这种连通的概率会非常大。
6、海底神殿;灯光寻找法;因为海底宫殿的顶端是有“水晶灯”的,故而寻找海底神殿的最优方法是“在夜晚行船”。首先将当前世界可加载的区块开到最大,乘着一艘小船,慢悠悠的起航。当你看到远处海底中散发的光亮,说明你距离海底神殿就不远了。但在海洋更新之后,你看到的光亮也有可能是海底的珊瑚。
地图寻找法;去制图师处购买一张海洋探险家地图,100%有效。
7、沙漠神殿、丛林神庙;这两种极具特色的古老遗迹,其实并不难找。一座大型沙漠中必出神殿,一片茂密的丛林也几乎必有神庙,往往还不止一个。如果没有,换另外一片沙漠,另外一座丛林。
8、雪屋;对于雪屋在1.14之前,确实无规律可循。一片白茫茫的雪地里,有时候即便是碰到的,也只能是擦肩而过。
村民搜寻法;但在1.14之后,村民多了一个特性——睡觉。每到夜晚村民便会寻找一张床,与之绑定,并通过AI计算出自己寻找床的路线。所以当你把一只村民带离村子,夜晚它或许能带你找到雪屋。因为雪屋中便有一张床!
9、林地府邸;同样的,去村庄找制图师,购买一本林地探险家地图即可。唯一的难度在于对于新手而言,攒绿宝石是一个难度。
10、沉船;海豚搜寻法,当你给海豚喂食生鳕鱼或者生鲑鱼时,它们会就近带你到附近最近的一个遗迹。这些遗迹包括乘船、海底废墟、以及藏宝图所指向的埋藏的宝藏。
11、地牢,这是唯一一个在《我的世界》甚至不能用/locate指令定位一个结构。在一个充满苔石的方形空间内,有着一个刷怪箱,这是地牢的构成方案。也几乎是所有遗迹当中几乎是最小的一个结构。所以这个世界上,你除了运气,你没有更好地寻找方案。
倘若有一天你在一个巨大的山脉腹地挖绿宝石,却意外地听到了从山脉里传来的僵尸低吼、骷髅咯咯、蜘蛛嘶嘶。那你要注意了,因为在山脉中间可能藏着一座正在大量刷怪的地牢!
你可能会问:还有末地城和末地船呀?其实在末地朝着任何一个地方蒙头跑,它们很快就会出现在你的眼前。
八、OpenGL变换
OpenGL变换是本篇的重点内容,它包括计算机图形学中最基本的三维变换,即几何变换、投影变换、裁剪变换、视口变换,以及针对OpenGL的特殊变换概念理解和用法,如相机模拟、矩阵堆栈等。学好了这章,才开始真正走进三维世界。
OpenGL基础图形编程(二)OpenGL概念建立8.1、从三维空间到二维平面
8.1.1 相机模拟
在真实世界里,所有的物体都是三维的。但是,这些三维物体在计算机世界中却必须以二维平面物体的形式表现出来。那么,这些物体是怎样从三维变换到二维的呢?下面我们采用相机( Camera )模拟的方式来讲述这个概念,如图8-1所示。
实际上,从三维空间到二维平面,就如同用相机拍照一样,通常都要经历以下几个步骤 (括号内表示的是相应的图形学概念):
第一步,将相机置于三角架上,让它对准三维景物(视点变换,Viewing Transformation)。
第二步,将三维物体放在适当的位置(模型变换,Modeling Transformation)。
第三步,选择相机镜头并调焦,使三维物体投影在二维胶片上(投影变换,Projection Transformation)。
第四步,决定二维像片的大小(视口变换,Viewport Transformation)。
这样,一个三维空间里的物体就可以用相应的二维平面物体表示了,也就能在二维的电脑屏幕上正确显示了。
8.1.2 三维图形显示流程
运用相机模拟的方式比较通俗地讲解了三维图形显示的基本过程,但在具体应用OpenGL函数库编程时,还必须了解三维图形世界中的几个特殊坐标系的概念,以及用这些概念表达的三维图形显示流程。
计算机本身只能处理数字,图形在计算机内也是以数字的形式进行加工和处理的。大家都知道,坐标建立了图形和数字之间的联系。为了使被显示的物体数字化,要在被显示的物体所在的空间中定义一个坐标系。这个坐标系的长度单位和坐标轴的方向要适合对被显示物体的描述,这个坐标系称为世界坐标系。
计算机对数字化的显示物体作了加工处理后,要在图形显示器上显示,这就要在图形显示器屏幕上定义一个二维直角坐标系,这个坐标系称为屏幕坐标系。这个坐标系坐标轴的方向通常取成平行于屏幕的边缘,坐标原点取在左下角,长度单位常取成一个象素的长度,大小可以是整型数。
为了使显示的物体能以合适的位置、大小和方向显示出来,必须要通过投影。投影的方法有两种,即正射投影和透视投影。
有时为了突出图形的一部分,只把图形的某一部分显示出来,这时可以定义一个三维视景体(Viewing Volume)。正射投影时一般是一个长方体的视景体,透视投影时一般是一个棱台似的视景体。只有视景体内的物体能被投影在显示平面上,其他部分则不能。在屏幕窗口内可以定义一个矩形,称为视口(Viewport),视景体投影后的图形就在视口内显示。
为了适应物理设备坐标和视口所在坐标的差别,还要作一适应物理坐标的变换。这个坐标系称为物理设备坐标系。根据上面所述,三维图形的显示流程应如图8-2所示。
8.1.3 基本变换简单分析 下面举一个简单的变换例子,cube.c:
例8-4 简单变换例程(cube.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void CALLBACK myReshape(GLsizei w, GLsizei h); void CALLBACK display(void); void CALLBACK display (void) { glClear(GL_COLOR_BUFFER_BIT); glColor3f (1.0, 1.0, 1.0); glLoadIdentity (); /* clear the matrix */ glTranslatef (0.0, 0.0, -5.0); /* viewing transformation */ glScalef (1.0, 2.0, 1.0); /* modeling transformation */ auxWireCube(1.0); /* draw the cube */ glFlush(); } void myinit (void) { glShadeModel (GL_FLAT); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glMatrixMode (GL_PROJECTION); /* prepare for and then */ glLoadIdentity (); /* define the projection */ glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0); /* transformation */ glMatrixMode (GL_MODELVIEW); /* back to modelview matrix */ glViewport (0, 0, w, h); /* define the viewport */ } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500, 500); auxInitWindow ("Perspective 3-D Cube"); myinit (); auxReshapeFunc (myReshape); auxMainLoop(display); }
以上程序运行结果就是绘制一个三维的正面透视立方体。其中已经用到了相机模拟中提到的四种基本变换,即视点变换、模型变换、投影变换和视口变换。
下面简单分析一下整个程序过程:
1)视点变换。视点变换是在视点坐标系中进行的。视点坐标系于一般的物体所在的世界坐标系不同,它遵循左手法则,即左手大拇指指向Z正轴,与之垂直的四个手指指向X正轴,四指弯曲90度的方向是Y正轴。而世界坐标系遵循右手法则的。如图8-4所示。当矩阵初始化glLoadIdentity()后,调用glTranslatef()作视点变换。函数参数(x, y, z)表示视点或相机在视点坐标系中移动的位置,这里z=-5.0,意思是将相机沿Z负轴移动5个单位。
通常相机位置缺省值同场景中的物体一样,都在原点处,而且相机初始方向都指向Z负轴。
这里相机移走后,仍然对准立方体。如果相机需要指向另一方向,则调用glRotatef()可以改变。
2) 模型变换 。模型变换是在世界坐标系中进行的。在这个坐标系中,可以对物体实施平移 glTranslatef()、旋转glRotatef()和放大缩小glScalef()。例子里只对物体进行比例变换,glScalef(sx, sy, sz)的三个参数分别是X、Y、Z轴向的比例变换因子。缺省时都为1.0,即物体没变化。程序中物体Y轴比例为2.0,其余都为1.0,就是说将立方体变成长方体。
3) 投影变换 。投影变换类似于选择相机的镜头。本例中调用了一个透视投影函数 glFrustum(),在调用它之前先要用glMatrixMode()说明当前矩阵方式是投影GL_PROJECTION。这个投影函数一共有六个参数,由它们可以定义一个棱台似的视景体。即视景体内的部分可见,视景体外的部分不可见,这也就包含了三维裁剪变换。
4) 视口变换 。视口变换就是将视景体内投影的物体显示在二维的视口平面上。通常,都调用函数glViewport()来定义一个视口,这个过程类似于将照片放大或缩小。
总而言之,一旦所有必要的变换矩阵被指定后,场景中物体的每一个顶点都要按照被指定的变换矩阵序列逐一进行变换。注意:OpenGL 中的物体坐标一律采用齐次坐标,即(x, y, z, w),故所有变换矩阵都采用4X4矩阵。一般说来,每个顶点先要经过视点变换和模型变换,然后进行指定的投影,如果它位于视景体外,则被裁剪掉。最后,余下的已经变换过的顶点x、y、z坐标值都用比例因子w除,即x/w、y/w、z/w,再映射到视口区域内,这样才能显示在屏幕上。
8.2、几何变换
实际上,上述所说的视点变换和模型变换本质上都是一回事,即图形学中的几何变换。
只是视点变换一般只有平移和旋转,没有比例变换。当视点进行平移或旋转时,视点坐标系中的物体就相当于在世界坐标系中作反方向的平移或旋转。因此,从某种意义上讲,二者可以统一,只是各自出发点不一样而已。读者可以根据具体情况,选择其中一个角度去考虑,这样便于理解。
8.2.1 两个矩阵函数解释
这里先解释两个基本OpenGL矩阵操作函数,便于以后章节的讲述。函数解释如下:
void glLoadMatrix{fd}(const TYPE *m)
设置当前矩阵中的元素值。函数参数*m是一个指向16个元素(m0, m1, ..., m15)的指针,这16个元素就是当前矩阵M中的元素,其排列方式如下:
M = | m0 m4 m8 m12 | | m1 m5 m9 m13 | | m2 m6 m10 m14 | | m3 m7 m11 M15 | void glMultMatrix{fd}(const TYPE *m)
用当前矩阵去乘m所指定的矩阵,并将结果存放于m中。当前矩阵可以是用glLoadMatrix() 指定的矩阵,也可以是其它矩阵变换函数的综合结果。
当几何变换时,调用OpenGL的三个变换函数glTranslate()、glRotate()和glScale*(),实质上相当于产生了一个近似的平移、旋转和比例矩阵,然后调用glMultMatrix()与当前矩阵相乘。但是直接调用这三个函数程序运行得快一些,因OpenGL自动能计算矩阵。
8.2.2 平移
平移变换函数如下:
void glTranslate{fd}(TYPE x,TYPE y,TYPE z)
三个函数参数就是目标分别沿三个轴向平移的偏移量。这个函数表示用这三个偏移量生成的矩阵乘以当前矩阵。当参数是(0.0,0.0,0.0)时,表示对函数glTranslate*()的操作是单位矩阵,也就是对物体没有影响。平移示意如图8-5所示。
8.2.3 旋转
旋转变换函数如下:
void glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z)
函数中第一个参数是表示目标沿从点(x, y, z)到原点的方向逆时针旋转的角度,后三个参数是旋转的方向点坐标。这个函数表示用这四个参数生成的矩阵乘以当前矩阵。当角度参数是0.0时,表示对物体没有影响。旋转示意如图8-6所示。
8.2.3 缩放和反射
缩放和反射变换函数如下:
void glScale{fd}(TYPE x,TYPE y,TYPE z)
三个函数参数值就是目标分别沿三个轴向缩放的比例因子。这个函数表示用这三个比例因子生成的矩阵乘以当前矩阵。这个函数能完成沿相应的轴对目标进行拉伸、压缩和反射三项功能。当参数是(1.0, 1.0, 1.0)时,表示对函数glScale*()操作是单位矩阵,也就是对物体没有影响。当其中某个参数为负值时,表示将对目标进行相应轴的反射变换,且这个参数不为1.0,则还要进行相应轴的缩放变换。最好不要令三个参数值都为零,这将导致目标沿三轴都缩为零。缩放和反射示意如图8-7所示。
8.2.5 几何变换举例 以上介绍了三个基本几何变换函数,下面举一个简单的例子进一步说明它们的用法。程序如下:
例 8-5 几何变换例程(geomtrsf.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void draw_(void); void CALLBACK display(void); void CALLBACK myReshape(GLsizei w, GLsizei h); void draw_(void) { glBegin(GL_LINE_LOOP); glVertex2f(0.0, 25.0); glVertex2f(25.0, -25.0); glVertex2f(-25.0, -25.0); glEnd(); } void CALLBACK display(void) { glClearColor (0.0, 0.0, 0.0, 1.0); glClear (GL_COLOR_BUFFER_BIT); /* draw an original */ glLoadIdentity (); glColor3f (1.0, 1.0, 1.0); /* white */ draw_ (); /* translating a along X_axis */ glLoadIdentity (); glTranslatef (-20.0, 0.0, 0.0); glColor3f(1.0,0.0,0.0); /* red */ draw_ (); /* scaling a along X_axis by 1.5 and along Y_axis by 0.5 */ glLoadIdentity(); glScalef (1.5, 0.5, 1.0); glColor3f(0.0,1.0,0.0); /* green */ draw_ (); /* rotating a in a counterclockwise direction about Z_axis */ glLoadIdentity (); glRotatef (90.0, 0.0, 0.0, 1.0); glColor3f(0.0,0.0,1.0); /* blue */ draw_ (); /* scaling a along Y_axis and reflecting it about Y_axis */ glLoadIdentity(); glScalef (1.0, -0.5, 1.0); glColor3f(1.0,1.0,0.0); /* yellow */ draw_ (); glFlush(); } void myinit (void) { glShadeModel (GL_FLAT); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) glOrtho(-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w, 50.0*(GLfloat)h/(GLfloat)w,-1.0,1.0); else glOrtho(-50.0*(GLfloat)w/(GLfloat)h, 50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0,-1.0,1.0); glMatrixMode(GL_MODELVIEW); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500, 500); auxInitWindow ("Geometric Transformations"); myinit (); auxReshapeFunc (myReshape); auxMainLoop(display); }
以上程序运行结果:第一个白色三角形是原始三角形,第二个红色三角形是白三角沿X 负轴平移后的三角形,第三个绿色三角形是白三角分别沿X轴和Y轴比例变换后的三角形,第四个蓝色三角形是白三角绕Z正轴逆时针转90度后的三角形,第五个黄色三角形是白三角沿Y轴方向缩小一倍且相对于X轴作反射后形成的三角形。
8.3、投影变换
投影变换是一种很关键的图形变换,OpenGL中只提供了两种投影方式,一种是正射投影,另一种是透视投影。不管是调用哪种投影函数,为了避免不必要的变换,其前面必须加上以下两句:
glMAtrixMode(GL_PROJECTION);glLoadIdentity();
事实上,投影变换的目的就是定义一个视景体,使得视景体外多余的部分裁剪掉,最终图像只是视景体内的有关部分。本指南将详细讲述投影变换的概念以及用法。
8.3.1 正射投影(Orthographic Projection)
正射投影,又叫平行投影。这种投影的视景体是一个矩形的平行管道,也就是一个长方体,如图8-9所示。正射投影的最大一个特点是无论物体距离相机多远,投影后的物体大小尺寸不变。这种投影通常用在建筑蓝图绘制和计算机辅助设计等方面,这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。
OpenGL正射投影函数共有两个,这在前面几个例子中已用过。一个函数是:
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far)
它创建一个平行视景体。实际上这个函数的操作是创建一个正射投影矩阵,并且用这个矩阵乘以当前矩阵。其中近裁剪平面是一个矩形,矩形左下角点三维空间坐标是(left,bottom,-near),右上角点是(right,top,-near);远裁剪平面也是一个矩形,左下角点空间坐标是(left,bottom,-far),右上角点是(right,top,-far)。所有的near和far值同时为正或同时为负。如果没有其他变换,正射投影的方向平行于Z轴,且视点朝向Z负轴。
这意味着物体在视点前面时far和near都为负值,物体在视点后面时far和near都为正值。另一个函数是:
void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)
它是一个特殊的正射投影函数,主要用于二维图像到二维屏幕上的投影。它的near和far缺省值分别为-1.0和1.0,所有二维物体的Z坐标都为0.0。因此它的裁剪面是一个左下角点为(left,bottom)、右上角点为(right,top)的矩形。
8.3.2 透视投影(Perspective Projection)
透视投影符合人们心理习惯,即离视点近的物体大,离视点远的物体小,远到极点即为消失,成为灭点。它的视景体类似于一个顶部和底部都被切除掉的棱椎,也就是棱台。这个投影通常用于动画、视觉仿真以及其它许多具有真实性反映的方面。
OpenGL透视投影函数也有两个,其中函数glFrustum()在8.1.3节中提到过,它所形成的视景体如图8-10所示。
这个函数原型为:
void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,GLdouble near,GLdouble far);
它创建一个透视视景体。其操作是创建一个透视投影矩阵,并且用这个矩阵乘以当前矩阵。这个函数的参数只定义近裁剪平面的左下角点和右上角点的三维空间坐标,即(left,bottom,-near)和(right,top,-near);最后一个参数far是远裁剪平面的Z负值,其左下角点和右上角点空间坐标由函数根据透视投影原理自动生成。near和far表示离视点的远近,它们总为正值。
另一个函数是:
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);
它也创建一个对称透视视景体,但它的参数定义于前面的不同,如图8-11所示。其操作是创建一个对称的透视投影矩阵,并且用这个矩阵乘以当前矩阵。参数 fovy定义视野在X-Z平面的角度,范围是[0.0, 180.0];参数aspect是投影平面宽度与高度的比率;参数zNear和Far分别是远近裁剪面沿Z负轴到视点的距离,它们总为正值。
以上两个函数缺省时,视点都在原点,视线沿Z轴指向负方向。二者的应用实例将在后续章节中介绍。
8.4、裁剪变换
在OpenGL中,空间物体的三维裁剪变换包括两个部分:视景体裁剪和附加平面裁剪。视景体裁剪已经包含在投影变换里,前面已述,这里不再重复。下面简单讲一下平面裁剪函数的用法。
除了视景体定义的六个裁剪平面(上、下、左、右、前、后)外,用户还可自己再定义一个或多个附加裁剪平面,以去掉场景中无关的目标,如图8-12所示。
附加平面裁剪函数为:
void glClipPlane(GLenum plane,Const GLdouble *equation);
函数定义一个附加的裁剪平面。其中参数equation指向一个拥有四个系数值的数组,这四个系数分别是裁剪平面Ax+By+Cz+D=0的A、B、 C、D值。因此,由这四个系数就能确定一个裁剪平面。参数plane是GL_CLIP_PLANEi(i=0,1,...),指定裁剪面号。
在调用附加裁剪函数之前,必须先启动glEnable(GL_CLIP_PLANEi),使得当前所定义的裁剪平面有效;当不再调用某个附加裁剪平面时,可用glDisable(GL_CLIP_PLANEi)关闭相应的附加裁剪功能。
下面这个例子不仅说明了附加裁剪函数的用法,而且调用了gluPerspective()透视投影函数,读者可以细细体会其中的用法。例程如下:
例8-6 裁剪变换例程 ( clipball.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void CALLBACK myReshape(GLsizei w, GLsizei h); void CALLBACK display(void); void CALLBACK display(void) { GLdouble eqn[4] = {1.0, 0.0, 0.0, 0.0}; glClear(GL_COLOR_BUFFER_BIT); glColor3f (1.0, 0.0, 1.0); glPushMatrix(); glTranslatef (0.0, 0.0, -5.0); /* clip the left part of wire_sphere : x<0 */ glClipPlane (GL_CLIP_PLANE0, eqn); glEnable (GL_CLIP_PLANE0); glRotatef (-90.0, 1.0, 0.0, 0.0); auxWireSphere(1.0); glPopMatrix(); glFlush(); } void myinit (void) { glShadeModel (GL_FLAT); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0); glMatrixMode(GL_MODELVIEW); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGB); auxInitPosition (0, 0, 500, 500); auxInitWindow ("Arbitrary Clipping Planes"); myinit (); auxReshapeFunc (myReshape); auxMainLoop(display); }
8.5、视口变换
在前面几节内容中已相继提到过视口变换,这一节将针对OpenGL来讲述视口变换的原理及其相关函数的用法。运用相机模拟方式,我们很容易理解视口变换就是类似于照片的放大与缩小。在计算机图形学中,它的定义是将经过几何变换、投影变换和裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域通常为矩形,称为视口。OpenGL中相关函数是:
glViewport(GLint x,GLint y,GLsizei width, GLsizei height);
这个函数定义一个视口。函数参数(x, y)是视口在屏幕窗口坐标系中的左下角点坐标,参数width和height分别是视口的宽度和高度。缺省时,参数值即(0, 0, winWidth, winHeight) 指的是屏幕窗口的实际尺寸大小。所有这些值都是以象素为单位,全为整型数。
注意 :在实际应用中,视口的长宽比率总是等于视景体裁剪面的长宽比率。如果两个比率不相等,那么投影后的图像显示于视口内时会发生变形,如图8-14所示。另外,屏幕窗口的改变一般不明显影响视口的大小。因此,在调用这个函数时,最好实时检测窗口尺寸,及时修正视口的大小,保证视口内的图像能随窗口的变化而变化,且不变形。
8.6 矩阵堆栈
学过计算机的人也许都知道这个使用频率极高的名词 — “堆栈”。顾名思义,堆栈指的是一个顶部打开底部封闭的柱状物体,通常用来存放常用的东西。这些东西从顶部依次放入,但取出时也只能从顶部取出,即“先进后出,后进先出”。在计算机中,它常指在内存中开辟的一块存放某些变量的连续区域。因此,OpenGL的矩阵堆栈指的就是内存中专门用来存放矩阵数据的某块特殊区域。
实际上,在创建、装入、相乘模型变换和投影变换矩阵时,都已用到堆栈操作。一般说来,矩阵堆栈常用于构造具有继承性的模型,即由一些简单目标构成的复杂模型。例如,一辆自行车就是由两个轮子、一个三角架及其它一些零部件构成的。它的继承性表现在当自行车往前走时,首先是前轮旋转,然后整个车身向前平移,接着是后轮旋转,然后整个车身向前平移,如此进行下去,这样自行车就往前走了。
矩阵堆栈对复杂模型运动过程中的多个变换操作之间的联系与独立十分有利。因为所有矩阵操作函数如glLoadMatrix()、glMultMatrix()、 glLoadIdentity()等只处理当前矩阵或堆栈顶部矩阵,这样堆栈中下面的其它矩阵就不受影响。堆栈操作函数有以下两个:
void glPushMatrix(void);void glPopMatrix(void);
第一个函数表示将所有矩阵依次压入堆栈中,顶部矩阵是第二个矩阵的备份;压入的矩阵数不能太多,否则出错。第二个函数表示弹出堆栈顶部的矩阵,令原第二个矩阵成为顶部矩阵,接受当前操作,故原顶部矩阵被破坏;当堆栈中仅存一个矩阵时,不能进行弹出操作,否则出错。由此看出,矩阵堆栈操作与压入矩阵的顺序刚好相反,编程时要特别注意矩阵操作的顺序。
为了更好地理解这两个函数,我们可以形象地认为glPushMatrix()就是“记住自己在哪”,glPopMatrix()就是“返回自己原来所在地”。请看下面一例:
例8-7 堆栈操作例程 ( arm.c )
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void drawPlane(void); void CALLBACK elbowAdd (void); void CALLBACK elbowSubtract (void); void CALLBACK shoulderAdd (void); void CALLBACK shoulderSubtract (void); void CALLBACK display(void); void CALLBACK myReshape(GLsizei w, GLsizei h); static int shoulder = 0, elbow = 0; void CALLBACK elbowAdd (void) { elbow = (elbow + 5) % 360; } void CALLBACK elbowSubtract (void) { elbow = (elbow - 5) % 360; } void CALLBACK shoulderAdd (void) { shoulder = (shoulder + 5) % 360; } void CALLBACK shoulderSubtract (void) { shoulder = (shoulder - 5) % 360; } void CALLBACK display(void) { glClear(GL_COLOR_BUFFER_BIT); glColor3f(0.0, 1.0, 1.0); glPushMatrix(); glTranslatef (-0.5, 0.0, 0.0); glRotatef ((GLfloat) shoulder, 0.0, 0.0, 1.0); glTranslatef (1.0, 0.0, 0.0); auxWireBox(2.0, 0.2, 0.5); glTranslatef (1.0, 0.0, 0.0); glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0); glTranslatef (0.8, 0.0, 0.0); auxWireBox(1.6, 0.2, 0.5); glPopMatrix(); glFlush(); } void myinit (void) { glShadeModel (GL_FLAT); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(65.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef (0.0, 0.0, -5.0); /* viewing transform */ } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 400, 400); auxInitWindow ("Composite Modeling Transformations"); myinit (); auxKeyFunc (AUX_LEFT, shoulderSubtract); auxKeyFunc (AUX_RIGHT, shoulderAdd); auxKeyFunc (AUX_UP, elbowAdd); auxKeyFunc (AUX_DOWN, elbowSubtract); auxReshapeFunc (myReshape); auxMainLoop(display); }
从以上例程可以看出,复杂的机械手臂是由两个简单的长方体依据一定的继承关系构成的,而这个继承关系是由矩阵堆栈的顺序决定的。
原文链接:https://blog.csdn/heyuchang666/article/details/51122659