OpenGL ES 2.0 (iOS)[04]:坐标空间 与 OpenGL ES 2 3D空间


目录

一、多坐标系

1.  世界坐标系
2.  物体(模型)坐标系
3.  摄像机坐标系
4.  惯性坐标系

二、坐标空间

1.  世界空间
2.  模型空间
3.  摄像机空间
4.  裁剪空间
5.  屏幕空间

三、OpenGL ES 2 3D 空间

1.  变换发生的过程
2.  各个变换流程分解简述
3.  四次变换与编程应用

四、工程例子

五、参考书籍


一、多坐标系

1. 世界坐标系

  • 即物体存在的空间,以此空间某点为原点,建立的坐标系

  • 世界坐标系是最大的坐标系,世界坐标系不一定是指“世界”,准确来说是一个空间或者区域,就是足以描述区域内所有物体的最大空间坐标,是我们关心的最大坐标空间;

  • 例子

    • ep1:
      比如我现在身处广州,要描述我现在所在的空间,对我而言最有意义就是,我身处广州的那里,而此时的广州就是我关心的“世界坐标系”,而不用描述我现在的经纬坐标是多少,不需要知道我身处地球的那个经纬位置。
      这个例子是以物体的方向思考的最合适世界坐标系;(当然是排除我要与广州以外的区域进行行为交互的情况咯!)
  • ep2:
    如果现在要描述广州城的全貌,那么对于我们而言,最大的坐标系是不是就是广州这个世界坐标系,也就是所谓的我们最关心的坐标系;
    这个例子是以全局的方向思考的最合适世界坐标系;

  • 世界坐标系主要研究的问题:

  1. 每个物体的位置和方向
  2. 摄像机的位置和方向
  3. 世界的环境(如:地形)
  4. 物体的运动(从哪到哪)

2. 物体(模型)坐标系

  • 模型自身的坐标系,坐标原点在模型的某一点上,一般是几何中心位置为原点

  • 模型坐标系是会跟随模型的运动而运动,因为它是模型本身的 “一部份” ;

  • 模型内部的构件都是以模型坐标系为参考进而描述的;

  • ep:
    比如有一架飞机,机翼位于飞机的两侧,那么描述机翼最合适的坐标系,当然是相对于飞机本身,机翼位于那里;飞机在飞行的时候,飞机本身的坐标系是不是在跟随运动,机翼是不是在飞机的坐标中同时运动着。

3. 摄像机坐标系

  • 摄像机坐标系就是以摄像机本身为原点建立的坐标系,摄像机本身并不可见,它表示的是有多少区域可以被显示(渲染)

  • 白色线所围成的空间,就是摄像机所能捕捉到的最大空间,而物体则位于空间内部;

  • 位于摄像机捕捉空间外的图形会直接被剔除掉;

4. 惯性坐标系

  • 它的 X 轴与世界坐标系的 X 轴平行且方向相同,Y 轴亦然,它的原点与模型坐标系相同

  • 它的存在的核心价值是,简化坐标系的转换,即简化模型坐标系到世界坐标系的转换;


二、坐标空间

坐标空间就是坐标系形成的空间

1. 世界空间

世界坐标系形成的空间,光线计算一般是在此空间统一进行;

2. 模型空间

模型坐标系形成的空间,这里主要包含模型顶点坐标和表面法向量的信息;


第一次变换
模型变换(Model Transforms):就是指从模型空间转换到世界空间的过程


3. 摄像机空间

摄像机空间

摄像机空间,就是黄色区域所包围的空间;
摄像机空间在这里就是透视投影,透视投影用于 3D 图形显示,反映真实世界的物体状态;

透视知识扩展 《透视》


第二次变换
视变换(View Transforms):就是指从世界空间转换到摄像机空间的过程


  • 摄像机空间,也被称为眼睛空间,即可视区域;
  • 其中,LookAt(摄像机的位置) 和 Perspective(摄像机的空间) 都是在调整摄像空间;

4. 裁剪空间

图形属于裁剪空间则保留,图形在裁剪空间外,则剔除(Culled)

摄像机 带注解

标号(3)[视景体] ,所指的空间即为裁剪空间,这个空间就由 Left、Right、Top、Bottom、Near、Far 六个面组成的四棱台,即视景体。


视景体

图中紫色区域为视场角


fov & zoom

从而引出,视场缩放为:


zoom
  • 其次,顶点是用齐次坐标表示{x, y, z, w}, 3D 坐标则为{x/w, y/w, z/w}而 w 就是判断图形是否属于裁剪空间的关键:
锥面 关系
Near z < -w
Far z > w
Bottom y < -w
Top y > w
Left x < -w
Right x > w

即坐标值,不符合这个范围的,都会被裁剪掉

坐标 值范围
x [-w , w]
y [-w, w]
z [-w, w]

第三次变换
投影变换(Projection Transforms): 当然包括正交、透视投影了,就是指从摄影机空间到视景体空间的变换过程


5. 屏幕空间

它就是显示设备的物理屏幕所在的坐标系形成的空间,它是 2D 的且以像素为单位,原点在屏幕的几何中心点

屏幕坐标空间.jpg


第四次变换(最后一次)
视口变换(ViewPort Transforms): 指从裁剪空间到屏幕空间的过程,即从 3D 到 2D


这里主要是关注像素的分布,即像素纵横比;因为图形要从裁剪空间投影映射到屏幕空间中,需要知道真实的环境的像素分布情况,不然图形就会出现变形;

《OpenGL ES 2.0 (iOS)[02]:修复三角形的显示》这篇文章就是为了修复屏幕像素比例不是 1 : 1 引起的拉伸问题,而它也就是视中变换中的一个组成部分。

  • 像素纵横比计算公式
像素缩放比

三、OpenGL ES 2 3D 空间

1. 变换发生的过程

OpenGL ES 2 变换流程图
  • 这个过程表明的是 GPU 处理过程(渲染管线);

  • 变换过程发生在,顶点着色与光栅化之间,即图元装配阶段;

  • 编写程序的时候,变换的操作是放在顶点着色器中进行处理;

  • 右下角写明了,总共就是四个变换过程:模型变换、视变换、投影变换、视口变换,经过这四个变换后,图形的点就可以正确并如愿地显示在用户屏幕上了;

  • 侧面反应,要正确地渲染图形,就要掌握这四种变换;

2. 各个变换流程分解简述

  • 阶段一:追加 w 分量为 1.0 (第一个蓝框)

    这个阶段不需要程序员操作

    这里的原因是,OpenGL 需要利用齐次坐标去进行矩阵的运算,核心原因当然就是方便矩阵做乘法咯(R(4x4) 点乘 R(4x1) 嘛)!

  • 阶段二:用户变换 (第二个蓝框)

    这个阶段需要程序员操作,在 Vertex Shader Code 中进行操作

    这个阶段主要是把模型正确地通过 3D 变换(旋转、缩放、平移)放置于摄像机的可视区域(视景体)中,包括处理摄像机的位置、摄像机的可视区域占整个摄像机空间的大小。

    这个阶段过后,w 就不在是 1.0 了

  • 阶段三:重新把齐次坐标转换成 3D 坐标 (第三个蓝框)

    这个阶段不需要程序员操作

    要重新转换回来的原因,也很简单 ---- 齐次坐标只是为了方便做矩阵运算而引入的,而 3D 坐标点才是模型真正需要的点位置信息。

    这个阶段过后,所有的点坐标都会标准化(所谓标准化,就是单位为1),x 和 y 值范围均在 [-1.0, 1.0 ]之间,z 就在 [ 0.0, 1.0 ] 之间;

    x 和 y 值范围均在 [-1.0, 1.0 ]之间,才能正确显示,原因是 OpenGL 的正方体值范围就是 [ -1.0, 1.0 ] 不存在其它范围的值;而 z 的值范围是由摄像机决定的,摄像机所处的位置就是 z = 0,的位置,所以 0 是指无限近,摄像机可视区的最远处就是 z = 1, 所以 1 是指无限远;

  • 阶段四:重新把齐次坐标转换成 3D 坐标 (第四个蓝框)

    *这个阶段需要程序员操作,在图形渲染前要进行操作,即在 gldraw 前 **

    这个阶段核心的就是 ViewPort 和 DepthRange 两个,前者是指视口,后者是深度,分别对应的 OpenGL ES 2 的 API 是:

函数 描述
glViewport 调整视窗位置和尺寸
glDepthRange 调整视景体的 near 和 far 两个面的位置 (z)
glViewport
void glViewport(GLint x, GLint y, GLsizei w, GLsizei h)
x, y 以渲染的屏幕坐标系为参考的视口原点坐标值(如:苹果的移动设备都是是以左上角为坐标原点)
w, h 要渲染的视口尺寸,单位是像素
glDepthRange
void glDepthRange(GLclampf n, GLclampf f)
n, f n, f 分别指视景体的 near 和 far ,前者的默认值为 0 ,后者的默认值为 1.0, 它们的值范围均为 [ 0.0, 1.0 ], 其实就是 z 值

3. 四次变换与编程应用

  • 下面这两张图片就是 Vertex Shader Code 中的最终代码
#version 100

attribute vec4 v_Position;
uniform mat4 v_Projection, v_ModelView;

attribute vec4 v_Color;
varying mediump vec4 f_color;

void main(void) {
    f_color = v_Color;
    gl_Position  = v_Projection * v_ModelView * v_Position;
}
 v_Projection 表示投影变换;v_ModelView 表示模型变换和视变换;
  • 第一次变换:模型变换,模型空间到世界空间 ( 1 -> 2 )

请看《OpenGL ES 2.0 (iOS)[02]:修复三角形的显示》 这篇文章,专门讲模型变换的。

  • 余下的几次变换,都是和摄像机模型在打交道
    摄像机里面的模型
Camera Model
要完成摄像机正确地显示模型,要设置摄像机位置、摄像机的焦距:
  1. 设置摄像机的位置、方向 --> (视变换) gluLookAt (ES 没有这个函数),使要渲染的模型位于摄像机可视区域中;【完成图中 1 和 2】
  2. 选择摄像机的焦距去适应整个可视区域 --> (投影变换) glFrustum(视景体的六个面)、gluPerspective(透视) 、glOrtho(正交)( ES 没有这三个函数) 【完成图中 3】
  3. 设置图形的视图区域,对于 3D 图形还可以设置 depth- range --> glViewport 、glDepthRange
  • 第二次变换:视变换,世界空间到摄像机空间 ( 2 -> 3 )

上面提到, ES 版本没有 gluLookAt 这个函数,但是我们知道,这里做的都是矩阵运算,所以可以自己写一个功能一样的矩阵函数即可;

// 我不想写,所以可以用 GLKit 提供给我们的函数
/*
Equivalent to gluLookAt.
*/
GLK_INLINE GLKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ,
                                                 float centerX, float centerY, float centerZ,
                                                 float upX, float upY, float upZ);
Frustum

函数的 eye x、y、z 就是对应图片中的 Eye at ,即摄像机的位置;

函数的 center x、y、z 就是对应图片中的 z-axis 可视区域的中心点;

函数的 up x、y、z 就是对应图片中的 up 指摄像机上下的位置(就是角度);

  • 第三次变换:投影变换,摄像机空间到裁剪空间 ( 3 -> 4 )
view frustum

当模型处于视景体外时会被剔除掉,如果模型有一部分在视景体内时,模型的点信息只会剩下在视景体内的,其它的点信息不渲染;

/*
 Equivalent to glFrustum.
 */
GLK_INLINE GLKMatrix4 GLKMatrix4MakeFrustum(float left, float right,
                                            float bottom, float top,
                                            float nearZ, float farZ);

这个是设置视景体六个面的大小的;

  • 透视投影
透视投影

对应的投影公式 :

完整的透视投影公式

使用 GLKit 提供的函数:

/*
 Equivalent to gluPerspective.
 */
GLK_INLINE GLKMatrix4 GLKMatrix4MakePerspective(float fovyRadians, // 视场角
                                                    float aspect,  // 屏幕像素纵横比
                                                    float nearZ, // 近平面距摄像机位置的距离
                                                    float farZ); // 远平面摄像机位的距离
  • 正交投影
Orthographic projection

对应的投影公式 :

完整的正交投影公式
/*
 Equivalent to glOrtho.
 */
GLK_INLINE GLKMatrix4 GLKMatrix4MakeOrtho(float left, float right,
                                          float bottom, float top,
                                          float nearZ, float farZ);
  • 第四次变换:视口变换,裁剪空间到屏幕空间 ( 4 -> 5 )

这里就是设置 glViewPort 和 glDepthRange 当然 2D 图形不用设置 glDepthRange ;

  • 实际编程过程中的使用过程

  • 第一步,如果是 3D 图形的渲染,那么要绑定深度渲染缓存(DepthRenderBuffer),若是 2D 可以跳过,因为它的顶点信息中没有 z 信息 ( z 就是顶点坐标的深度信息 );

    1. Generate ,请求 depth buffer ,生成相应的内存标识符
    2. Bind,绑定申请的内存标识符
    3. Configure Storage,配置储存 depth buffer 的尺寸
    4. Attach,装载 depth buffer 到 Frame Buffer 中
      具体的程序代码:
  • 第二步,缩写 Vertex Shader Code
#version 100

attribute vec4 v_Position;
uniform mat4 v_Projection, v_ModelView; // 投影变换、模型视图变换

attribute vec4 v_Color;
varying mediump vec4 f_color;

void main(void) {
     f_color = v_Color;
     gl_Position = v_Projection * v_ModelView * v_Position;
}

一般是把四次变换写成这两个,当然也可以写成一个;因为它们是一矩阵,等同于一个常量,所以使用的是 uniform 变量,变量类型就是 mat4 四乘四方阵(齐次矩阵);

  • 第三步,就是外部程序赋值这两个变量

注意,要在 glUseProgram 函数后,再使用 glUniform 函数来赋值变量,不然是无效的;*

依次完成 模型变换、视变换、投影变换,即可;它们两两用矩阵乘法进行连接即可;

如:modelMatrix 点乘 viewMatrix , 它们的结果再与 projectionMatrix 点乘,即为 ModelViewMatrix ;

GLKit 点乘函数,
GLK_INLINE GLKMatrix4 GLKMatrix4Multiply(GLKMatrix4 matrixLeft, GLKMatrix4 matrixRight);

  • 第四步,如果是 3D 图形,有 depth buffer ,那么要清除深度渲染缓存

使用 glClear(GL_DEPTH_BUFFER_BIT); 进行清除,当然之后就是要使能深度测试 glEnable(GL_DEPTH_TEST); 不然图形会变形;

最好,也使能 glEnable(GL_CULL_FACE); 这里的意思就是,把在屏幕后面的点剔除掉,就是不渲染;判断是前还是后,是利用提供的模型顶点信息中点与点依次连接形成的基本图元的时钟方向进行判断的,这个 OpenGL 会自行判断;


ClockWise & Counterclockwise

左为顺时针,右为逆时针;

  • 第五步,设置 glViewPort 和 glDepthRange

使用 OpenGL ES 提供的 glViewPort 和 glDepthRange 函数即可;


四、工程例子

Github: 《DrawSquare_3DFix》


五、参考书籍

《OpenGL ES 2.0 Programming Guide》
《OpenGL Programming Guide 8th》
《3D 数学基础:图形与游戏开发》
《OpenGL 超级宝典 第五版》
《Learning OpenGL ES For iOS》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容

  • 本文首发于个人博客:Lam's Blog - 【OpenGL ES】入门及绘制一个三角形,文章由MarkDown语...
    格子林ll阅读 7,232评论 2 18
  • 1 前言 一直想沿着图像处理这条线建立一套完整的理论知识体系,同时积累实际应用经验。因此有了从使用AVFounda...
    RichardJieChen阅读 5,637评论 5 12
  • 一周紧张的工作结束了,有点疲惫,但每天都很开心,感觉团队的每个小伙伴都特别好,从心底里喜欢每个人,虽然累,但还是要...
    L莎莎阅读 105评论 0 0
  • 盛夏时节,安徽牛商争霸赛的各位小伙伴又一次齐聚在徽马科技,进行新一场的线下交流活动。这次线下交流的主题为:阿里巴巴...
    安徽饰界舞台桁架阅读 432评论 0 2
  • 来到寝室,打开门便看到三张带上下铺的床架子整整齐齐的摆在两边,右边两张,左边一张再加一个六个人的柜子,洁白的地板上...
    右手心声阅读 220评论 0 0