OpenGL ES 学习笔记(1)-基本使用

1. OpenGL ES 基础

1.1. OpenGL 坐标

屏幕坐标系中横轴是x轴,纵轴是y轴,分别向右是正方向,向下是正方向.原点在左上角
数学坐标系中原点在左下角,向上是y轴正方向.
在OpenGL3D坐标系中,稍有不同,使用的是笛卡尔坐标系,表示空间坐标位置.和数学坐标系相似,左下角是原点,向右和向上是正值.还要添加第三个量就是z轴.z轴方向指向你.
下图是OpenGL ES笛卡尔坐标系.

OpenGL ES笛卡尔坐标系

实际上我们有几种坐标系和空间,在OpenGL中,每个空间位置会按照如下变换:

  • 对象空间,相对于每个对象自身
  • 相机,眼睛,空间是针对视点
  • 投影,剪裁,空间是平面的屏幕或者是图像上的视口.

1.2. 视景体

也叫平截头体(frustum),是一个棱台,表示的是照相机能够捕捉到画面的一个区域.
下图为视景体


视景体

视景体数学模型

定义视景体非常简单通过定义一个几何体就可以了,空间中,任何平截头体内的物体都能够在屏幕上找到相应的位置,只要没有被其他物体覆盖.
Frustum也用于指定你的field-of-view(FOV),比如相机的广角与长焦镜头.角度越大看到的景象越多,成像就越小.
可以使用gl.glMatrixMode(GL_MODELVIEW)方法对模型视图矩阵进行平移和旋转操作.还需要定义操纵投影矩阵(projection matrix).
透视投影是我们观察世界的正常形势,物体越远越小,如果对frustum内的这种效果移除,我们就会得到正交投影,就是不论物体远近,他们的大小不发生改变,这主要用于机械制图等.
经常需要控制操纵的是哪个矩阵,可通过gl.glMatrixMode()实现,如果不知道操纵的是那个矩阵很容易出错.

投影类型:

  • 透视投影:有深度,越远越小.
  • 正投影:没有深度,相同大小.
    矩阵模式,openGL是基于状态的,操纵很多矩阵,通过该函数指定使用哪个矩阵.
    常用的矩阵有:
  • GL10.GL_PROJECTION:投影矩阵.
  • GL10.GL_MODELVIEW:模型视图矩阵.
    指定使用哪个矩阵之后,需要先加载单位矩阵(使用gl.loadIdentity()方法,类似于矩阵归零).

1.3. 定义视口

gl.glViewPort(...);需要注意的是,坐标从左下角开始.

视口

1.4. 使用正交投影

gl.glOrtho(..)
//使用正交矩阵栈,需要重新设置坐标系,告诉OpenGL,以后所有变换都会影响该矩阵.
gl.glMatrixMode(GL_PROJECTION);
gl.glLoadIdentity();

1.5. 双缓冲

所有图形程序最重要的特性之一就是双缓冲.他允许在一个屏幕之外的缓冲区中执行绘制代码,然后使用一条交换命令把绘制完成的图形立即显示在屏幕上.
双缓冲用途两个:
一是一些复杂绘图时间较长,不希望在屏幕上看到每个步骤.双缓冲可以合成一幅图像完成后再显示.
用户不会看到不完整的图像.
二是用于动画.每个帧都在屏幕之外的缓冲区绘制,完成后快速交换到屏幕上.

1.6. OpenGL状态机

绘制3D图形是一项复杂任务.状态机是一个抽象的模型,表示一组状态变量的集合.每个状态变量可以有各种不同的值,打开关闭等.OpenGL绘图时,每次指定这些变量明显不合适.因此OpenGL给出了一中状态模型(状态机)来跟踪所有的OpenGL状态变量.一个状态变量被设置后,以后一直保持这个状态,直到下次对他进行修改.使用如下命令可进行状态变
量的切换.
gl.glEnable(xx);//gl.glDisable(xx);
//例如光照
gl.glEnable(GL_LIGHGTING);

2. 使用OpenGL编码步骤

2.1. 创建GLSurfaceView对象

写一个继承自GLSurfaceView的类.

2.2. 创建GLSurfaceView.renderer实现类.

实现GLSurfaceView.Renderer接口,创建渲染器.

2.3. 设置activity的contentView,以及设置view的render对象.

将2.1中的GLSurfaceView设置2.2创建的渲染器,并设置到ContentView中.

2.4. 实现Renderer类的过程

2.4.1. onSurfaceCreate()方法

设置清屏的颜色和启用顶点缓冲区
//设置清屏色
gl.glClearColor(0, 0, 0, 1);
//启用顶点缓冲区.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

2.4.2. onSurfaceChanged()方法

  1. 设置viewport(视口) 输出画面的区域(从左下角开始)
    gl.glViewport(0, 0, width, height);
  2. 操纵投影矩阵,设置平截头体(比例通常和视口比例相同,否则输出画面会走样)
    //矩阵模式,投影矩阵,openGL基于状态机
    gl.glMatrixMode(GL10.GL_PROJECTION);
    //加载单位矩阵
    gl.glLoadIdentity();
    //平截头体 zNear:近平面 zFar:远平面
    gl.glFrustumf(-ratio, ratio, -1f, 1f, 3, 7);

2.4.3. onDrawFrame()方法:

  1. 清除颜色缓冲区
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
  2. 操纵模型视图矩阵,设置眼球的参数
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();//加载单位矩阵
    //eyeX, eyeY, eyeZ :放置眼球的坐标
    //centerX, centerY, centerZ : 眼球的观察点
    //upx, upy, upz : 指定眼球向上的向量(此处比较危险)
    GLU.gluLookAt(gl, 0, 0, 5, 0, 0, 0, 0, 1, 0);
  3. 定义图形顶点坐标值数组
    float[] coords = {
    0f,0.5f,0f,
    -0.5f,-0.5f,0f,
    0.5f,-0.5f,0f
    };
  4. 将顶点坐标转换成缓冲区数据
    //分配字节缓存区空间,存放顶点坐标数据(浮点数4个字节)
    ByteBuffer ibb = ByteBuffer.allocateDirect(coords.length * 4);
    //设置的顺序(本地顺序, 跟操作系统有关)
    ibb.order(ByteOrder.nativeOrder());
    //放置顶点坐标数组
    FloatBuffer fbb = ibb.asFloatBuffer();
    fbb.put(coords);
    //定位指针的位置,从该位置开始读取顶点数据
    ibb.position(0);
  5. 设置绘图颜色
    gl.glColor4f(1f, 0f, 0f, 1f);
  6. 指定顶点缓冲区指针
    //3:3维点,使用三个坐标值表示一个点
    //type:每个点的数据类型
    //stride:0,跨度.
    //ibb:指定顶点缓冲区
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ibb);
  7. 绘图
    //mode: 绘制类型
    //first: 起始点
    //count: 点的个数
    gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

2.5. 封装通用的字节缓冲区转换方法

(持续更新该Utils类)

public class BufferUtil {
    /**
     * 将浮点数组转换成字节缓冲区
     * @param arr 输入待转换的浮点数组
     * @return 字节缓冲区
     */
    public static ByteBuffer arr2ByteBuffer(float[] arr) {
        //分配字节缓存区空间, 存放定点坐标数据
        // 一个字节 8位  浮点数4个字节
        ByteBuffer ibb = ByteBuffer.allocateDirect(arr.length * 4);
        //设置顺序(本地顺序,跟操作系统有关)
        ibb.order(ByteOrder.nativeOrder());
        //放置顶点坐标数据
        FloatBuffer fbb = ibb.asFloatBuffer();
        fbb.put(arr);
        //定位指针的位置, 从该位置开始读取顶点数据
        ibb.position(0);
        return ibb;
    }
 
    /**
     * 将List集合转换成字节缓冲区
     * @param list 输入待转换的浮点数组
     * @return 字节缓冲区
     */
    public static ByteBuffer list2ByteBuffer(List<Float> list) {
        //分配字节缓存区空间, 存放定点坐标数据
        // 一个字节 8位  浮点数4个字节
        ByteBuffer ibb = ByteBuffer.allocateDirect(list.size() * 4);
        //设置顺序(本地顺序,跟操作系统有关)
        ibb.order(ByteOrder.nativeOrder());
        //放置顶点坐标数据
        FloatBuffer fbb = ibb.asFloatBuffer();
        for (Float aFloat : list) {
            fbb.put(aFloat);
        }
        //定位指针的位置, 从该位置开始读取顶点数据
        ibb.position(0);
        return ibb;
    }
}

2.6. 渲染模式

OpenGL ES有两种渲染模式:
GLSurfaceView.RENDERMODE_CONTINUOUSLY: 持续渲染(默认)
GLSurfaceView.RENDERMODE_WHEN_DIRTY: 脏渲染, 命令渲染(需要使用myGLSurfaceView.requestRender();方法主动请求渲染)

2.7. 坐标系旋转

//angle: 旋转角度
//x,y,z: 沿着哪个轴旋转(向量) 迎面轴正方向看去, 顺时针: 负值 逆时针: 正值
gl.glRotatef(xrotate, 1, 0, 0);//绕x轴旋转
gl.glRotatef(yrotate, 0, 1, 0);//绕y轴旋转

2.8. 缓冲区

缓冲区分为:

  • 顶点缓冲区(VertexBuffer)
  • 颜色缓冲区(ColorBuffer)

使用方式:
gl.glColorPointer(xx);//指定颜色缓冲区
gl.glVertexPointer(xx);//指定顶点指针
注意:不要忘记在onSurfaceCreated() 方法中启用两个缓冲区
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//启用顶点缓冲区
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);//启动颜色缓冲区

3. 空间绘图

OpenGL ES支持三种绘图模式:点, 线和三角形.

3.1. 点

像素是计算机监视器上最小的元素.在彩色系统中,像素可以是许多可用颜色的一种.屏幕上的一个位置对应一个点,并为之指定颜色.然后,在此基础上,用最擅长的计算机语言生成直线,多边形,圆形以及其他图形,甚至整个GUI.
下图把可视区域看成是三维画布,可使用OpenGL命令和函数进行绘图.


画点:
gl.glDrawArray(GL_POINTS,..)
设置点大小:
gl.glPiontSize(xx);
示例(点变化大小的螺旋):

//计算点坐标
float r = 0.5f;//半径
//  旋转3圈
float x = 0f, y = 0f, z = 1f;
float zstep = 0.01f;//z的步长
float psize   = 1.0f;//点的大小
float pstep = 0.5f;//点的步长
//循环绘制
for (float alpha = 0f; alpha < Math.PI * 6; alpha = (float) (alpha + Math.PI/32)) {
    x = (float) (r * Math.cos(alpha));
    y = (float) (r * Math.sin(alpha));
    z = z - zstep;
    //设置点的大小
    gl.glPointSize(psize = psize + pstep);
    //指定顶点指针
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, BufferUtil.arr2ByteBuffer(new float[]{x, y, z}));
    //画数组
    gl.glDrawArrays(GL10.GL_POINTS, 0, 1);
}
preview

3.2. 线

画线分为三类:

  • lines: 线集(线段集合)
  • line_strip: 线带(首尾相接不闭环)
  • line_loop: 线环(首尾相接闭环)

线带

线环

画线:
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fbb);
gl.glDrawArrays(GL10.GL_LINES, 0, 4);
设置直线宽度:
gl.glLinewidth(xx);
示例(使用线带画螺旋线):

float r = 0.5f;//半径
List<Float> coordsList = new ArrayList<>();//坐标点集合
//  旋转3圈
float x = 0f, y = 0f, z = 1f;
float zstep = 0.01f;//步长
for (float alpha = 0f; alpha < Math.PI * 6; alpha = (float) (alpha + Math.PI/32)) {
    x = (float) (r * Math.cos(alpha));
    y = (float) (r * Math.sin(alpha));
    z = z - zstep;
    coordsList.add(x);
    coordsList.add(y);
    coordsList.add(z);
}
//指定顶点指针
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, BufferUtil.list2ByteBuffer(coordsList));
//画数组
gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, coordsList.size() / 3);
preview

3.3. 三角形

使用画线方式绘制的图形的线框样式的,即都是直线无法进行
颜色填充,多边形是闭合形状.可以选择颜色进行填充.
有三种模式:

3.3.1. 环绕(GL_TRIANGLES)

注意连接顶点的箭头方向,第一个三角形的顺序是v0->v1->v2->v0,该顺序是有定点的指定次序决定的.该顺序为顺时针方向.第二个三角形也是一样.顶点的指定次序以及方向的组合成为环绕(winding).左图看成是顺时针环绕,如果将v4和v5两点互换,得到逆时针环绕.如右图:


3.3.2. 三角形带(GL_TRIANGLES_STRIP)

GL_TRIANLTE_STRIP,可绘制一串相邻的三角形,这些顶点不是按照他们指定的顺兴进行遍历的,这是为了保持每个三角形的环绕方向(逆时针).他的绘制模式是v0->v1->v2,接着v2->v1->v3,然后是v2->v3->v4,依次类推.

3.3.3. 三角形扇(GL_TRIANGLES_FAN)

围绕一个中心点的相连三角形.


3.3.4. 示例(正方形)

float r = 0.5f;//半径
float[] coords = {
        -r, r, 0,
        -r, -r, 0,
        r, r, 0,
        r, -r, 0,
};
 
//指定顶点指针
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, BufferUtil.arr2ByteBuffer(coords));
//画数组
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, coords.length/ 3);

3.4 实心物体

3.4.1. 设置颜色

我们知道画点画线或者三角形都要指定顶点缓冲区,同样的也会颜色缓冲区.
需要注意的是, 顶点缓冲区中的点与颜色缓冲区中的点进行对应.

颜色缓冲区

顶点的着色模式:

  1. smmoth: 平滑模式,也称渐变(默认)
  2. flat: 单调模式以三角形最后一个点的颜色值为准
    设置着色模式:
    gl.glShadeMode(GL_FLAT|GL_SMOOTH);//单调|平滑
    注意: 三角形面设置的颜色, 跟最后一个顶点的颜色相同.

3.4.2. 深度测试

深度测试是一种有效地的用于隐藏表面消除的技巧,当一个像素绘制时,他将被设置一个值(称为z值),来表示他和观察者之间的距离.以后,当该位置需要绘制另一个像素时,新像素的z值和原先的值进行比较.如果z值越高,距离观察者越近,就位于以前像素的前面.反之位于后面.内部是通过深度缓冲区实现的.他存储了屏幕上每个像素的深度值.


使用深度测试

不使用深度测试

启用深度测试:(不要忘记在绘图开始时清除深度缓冲区)
gl.glClear(GL10.GL_DEPTH_BUFFER_BIT);//清除深度缓冲区
gl.glEnable(GL10.GL_DEPTH_TEST);//启用深度测试

3.4.3. 剔除

使用深度测试,性能上需要有开销,因为每次绘制像素都需要比较.如果知道那个面肯定不能绘制,就可以使用剔除这种技巧了.消除已经知道肯定不会绘制的几何图形.也不会向OpenGL驱动程序发送这个几何图形,性能能够显著提升.
剔除技巧之一就是背面剔除,消除一个表面的背面.
//启用表面剔除
gl.glEnable(GL10.GL_CULL_FACE);
//指定正面
//ccw: counter clock wise -> 逆时针(默认)
//cw: clock wise -> 顺时针
gl.glFrontFace(GL10.GL_CCW);
//剔除背面
//GL10.GL_FRONT:正面
//GL10.GL_BACK背面
gl.glCullFace(GL10.GL_BACK);

3.4.4. 使用剪刀进行裁剪

不在ViewPort整个视口进行渲染.
使用方式:
//启用剪裁测试
gl.glEnable(GL_SCISSOR_TEST);
//剪裁: x, y: 剪裁距离 width,height:屏幕宽高
gl.glScissor(160, 240, 160, 50);

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

推荐阅读更多精彩内容