1. 基本概念

在 OpenGL 的世界里,我们只能画点、线、三角形,复杂的图形都是由三角形构成的。

在 OpenGL 里有两个最基本的概念:Vertex 和 Fragment。一切图形都从 Vertix 开始,Vertix 序列围成了一个图形。那什么是 Fragment 呢?为此我们需要了解一下光栅化(Rasterization):光栅化是把点、线、三角形映射到屏幕上的像素点的过程(每个映射区域叫一个 Fragment),也就是生成 Fragment 的过程。通常一个 Fragment 对应于屏幕上的一个像素,但高分辨率的屏幕可能会用多个像素点映射到一个 Fragment,以减少 GPU 的工作。

接下来介绍 Shader(着色器程序):Shader 用来描述如何绘制(渲染),GLSL 是 OpenGL 的编程语言,全称 OpenGL Shader Language,它的语法类似于 C 语言。OpenGL 渲染需要两种 Shader:Vertex Shader 和 Fragment Shader。

每个 Vertex 都会执行一遍 Vertex Shader,以确定 Vertex 的最终位置,其 main 函数中必须设置 gl_Position 全局变量,它将作为该 Vertex 的最终位置,进而把 Vertex 组合(assemble)成点、线、三角形。光栅化之后,每个 Fragment 都会执行一次 Fragment Shader,以确定每个 Fragment 的颜色,其 main 函数中必须设置 gl_FragColor 全局变量,它将作为该 Fragment 的最终颜色。

 2. 坐标系

弄 清楚坐标系很重要,不然会找不着东南西北。下面这张图展示了 OpenGL 通常的处理流程中各个环节的坐标系,以及坐标系之间的转换操作

下面对图中的几个概念作简单说明:

  • Local space:我们为每个物体建好模型的时候,它们的坐标就是 Local space 坐标;
  • World space:当我们要绘制多个物体时,如果直接使用 Local space 的坐标(把所有物体的原点放在一起),那它们很可能会发生重叠,因此我们需要把它们进行合理的移动、排布,最终各自的坐标就是 World space 的坐标了;
  • Model matrix:把 Local space 坐标转换到 World space 坐标所使用的变换矩阵,它是针对每个物体做不同的变换;
  • View space:通常也叫 Camera space 或者 Eye space,是从观察者(也就是我们自己)所在的位置出发,所看到的空间;
  • View matrix:把 World space 坐标转换到 View space 坐标所使用的变换矩阵,它相当于是在移动相机位置,实际上是反方向移动整个场景(所有物体);
  • Clip space:OpenGL 只会渲染坐标值范围在 [-1, 1] 的内容,超出这个范围的内容都会被裁剪掉,这个范围的空间就叫 Clip space,Clip space 的坐标系也叫 normalized device coordinate(NDC);
  • View space:通常也叫 Camera space 或者 Eye space,是从观察者(也就是我们自己)所在的位置出发,所看到的空间;
  • View matrix:把 World space 坐标转换到 View space 坐标所使用的变换矩阵,它相当于是在移动相机位置,实际上是反方向移动整个场景(所有物体);
  • Clip space:OpenGL 只会渲染坐标值范围在 [-1, 1] 的内容,超出这个范围的内容都会被裁剪掉,这个范围的空间就叫 Clip space,Clip space 的坐标系也叫 normalized device coordinate(NDC);
  • Projection matrix:把 View space 坐标转换到 Clip space 坐标所使用的变换矩阵,它会指定一个可见的范围,只有这个范围内的点才会转换到 NDC 中,而这个范围被称作视锥(frustum);projection matrix 有三种创建方式:正投影(Orthographic projection),透视投影(Perspective projection),以及 3D 投影(3D projection);前两种比较常用。
  • Screen space:屏幕上的空间,glViewport 调用指定的区域;
  • Viewport transform:这一步是 Open GL 自动完成的,把 Clip space 坐标转换到 Screen space 坐标;

下面对图中的几个概念作简单:

LearnOpenGL - Coordinate Systems更多关于坐标系的内容,可以阅读:LearnOpenGL - Coordinate Systems

3. Hello world

先看一下最简单的完整例子(下文有详细分析,完整代码可以在 GitHub 获取),绘制一个三角形,效果如图一

public class MainActivity extends AppCompatActivity {private GLSurfaceView mGLSurfaceView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (!Utils.supportGlEs20(this)) {Toast.makeText(this, "GLES 2.0 not supported!", Toast.LENGTH_LONG).show();finish();return;}mGLSurfaceView = (GLSurfaceView) findViewById(R.id.surface);mGLSurfaceView.setEGLContextClientVersion(2);mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);mGLSurfaceView.setRenderer(new MyRenderer());mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);}@Overrideprotected void onPause() {super.onPause();mGLSurfaceView.onPause();}@Overrideprotected void onResume() {super.onResume();mGLSurfaceView.onResume();}private static class MyRenderer implements GLSurfaceView.Renderer {private static final String VERTEX_SHADER ="attribute vec4 vPosition;\n"+ "void main() {\n"+ "  gl_Position = vPosition;\n"+ "}";private static final String FRAGMENT_SHADER ="precision mediump float;\n"+ "void main() {\n"+ "  gl_FragColor = vec4(0.5, 0, 0, 1);\n"+ "}";private static final float[] VERTEX = {   // in counterclockwise order:0, 1, 0,  // top-0.5f, -1, 0,  // bottom left1, -1, 0,  // bottom right};private final FloatBuffer mVertexBuffer;private int mProgram;private int mPositionHandle;MyRenderer() {mVertexBuffer = ByteBuffer.allocateDirect(VERTEX.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(VERTEX);mVertexBuffer.position(0);}static int loadShader(int type, String shaderCode) {int shader = GLES20.glCreateShader(type);GLES20.glShaderSource(shader, shaderCode);GLES20.glCompileShader(shader);return shader;}@Overridepublic void onSurfaceCreated(GL10 unused, EGLConfig config) {GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);mProgram = GLES20.glCreateProgram();int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);GLES20.glAttachShader(mProgram, vertexShader);GLES20.glAttachShader(mProgram, fragmentShader);GLES20.glLinkProgram(mProgram);GLES20.glUseProgram(mProgram);mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");GLES20.glEnableVertexAttribArray(mPositionHandle);GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false,12, mVertexBuffer);}@Overridepublic void onSurfaceChanged(GL10 unused, int width, int height) {GLES20.glViewport(0, 0, width, height);}@Overridepublic void onDrawFrame(GL10 unused) {GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);}}
}

3.1. set up

首先我们需要一个 GLSurfaceView,它是让我们渲染的“画布”。然后我们需要一个 GLSurfaceView.Renderer,它将实现我们的渲染逻辑。此外我们还将设置 GL ES 版本,并将 GLSurfaceView 和 Renderer 连接起来:

mGLSurfaceView.setEGLContextClientVersion(2);
mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
mGLSurfaceView.setRenderer(new MyRenderer());
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

RenderMode 有两种,RENDERMODE_WHEN_DIRTYRENDERMODE_CONTINUOUSLY,前者是懒惰渲染,需要手动调用 glSurfaceView.requestRender() 才会进行更新,而后者则是不停渲染。

3.2. GLSurfaceView.Renderer

Renderer 包含三个接口:

public interface Renderer {void onSurfaceCreated(GL10 gl, EGLConfig config);void onSurfaceChanged(GL10 gl, int width, int height);void onDrawFrame(GL10 gl);
}

   onSurfaceCreated 在 surface 创建时被回调,通常用于进行初始化工作,只会被回调一次;onSurfaceChanged 在每次 surface 尺寸变化时被回调,注意,第一次得知 surface 的尺寸时也会回调;onDrawFrame 则在绘制每一帧的时候回调。

3.3. GLSL 程序

和普通的 view 利用 canvas 来绘制不一样,OpenGL 需要加载 GLSL 程序,让 GPU 进行绘制。所以我们需要定义 shader 代码,并在初始化时(也就是 onSurfaceCreated 回调中)加载:

@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);mProgram = GLES20.glCreateProgram();int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);GLES20.glAttachShader(mProgram, vertexShader);GLES20.glAttachShader(mProgram, fragmentShader);GLES20.glLinkProgram(mProgram);GLES20.glUseProgram(mProgram);mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");GLES20.glEnableVertexAttribArray(mPositionHandle);GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false,12, mVertexBuffer);
}static int loadShader(int type, String shaderCode) {int shader = GLES20.glCreateShader(type);GLES20.glShaderSource(shader, shaderCode);GLES20.glCompileShader(shader);return shader;
}

GLSL 的语法并不是本文的主要内容,这里就不深入展开了。

  • 创建 GLSL 程序:glCreateProgram
  • 加载 shader 代码:glShaderSourceglCompileShader
  • attatch shader 代码:glAttachShader
  • 链接 GLSL 程序:glLinkProgram
  • 使用 GLSL 程序:glUseProgram
  • 获取 shader 代码中的变量索引:glGetAttribLocation
  • 启用 vertex:glEnableVertexAttribArray
  • 绑定 vertex 坐标值:glVertexAttribPointer

需要指出的是,我们的 Java 代码需要获取 shader 代码中定义的变量索引,用于在后面的绘制代码中进行赋值,变量索引在 GLSL 程序的生命周期内(链接之后和销毁之前),都是固定的,只需要获取一次。

3.4. 设置 Screen space 的大小

我们可以利用 glViewport 设置 Screen space 的大小,通常在 onSurfaceChanged 中调用:

@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {GLES20.glViewport(0, 0, width, height);
}

3.5. 绘制

我们在 onDrawFrame 回调中执行绘制操作,绘制的过程其实就是为 shader 代码变量赋值,并调用绘制命令的过程:

@Override
public void onDrawFrame(GL10 unused) {GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}

由于顶点坐标已经绑定过了,所以这里无需进行变量赋值,直接调用绘制指令即可。我们可以通过 GLES20.glDrawArrays 或者 GLES20.glDrawElements 开始绘制。注意,执行完毕之后,GPU 就在显存中处理好帧数据了,但此时并没有更新到 surface 上,是 GLSurfaceView 会在调用 renderer.onDrawFrame 之后,调用 eglSwapBuffers,来把显存的帧数据更新到 surface 上的。

这里我们绘制的是一个三角形,OpenGL 坐标原点在屏幕中心,三个顶点分别是:

  • (0, 1, 0),位于屏幕顶部中心点;
  • (-0.5f, -1, 0),位于屏幕底部四分之一点;
  • (1, -1, 0),位于屏幕右下角;

它们的 z 轴坐标都是 0,所以也就是上面图一的效果了。

#4. 投影变换

你肯定已经注意到,OpenGL 坐标系和安卓手机坐标系不是线性对应的,因为手机的宽高比几乎都不是 1。因此我们绘制的形状是变形的,怎么解决这个问题呢?答案是投影变换(projection)。前面我们已经知道,投影变换用于把 View space 的坐标转换为 Clip space 的坐标,在这个转换过程中,它还能顺带处理宽高比的问题。

使用较多的是正投影和透视投影,这里我们使用透视投影:Matrix.perspectiveM。通常坐标系的变换都是对顶点坐标进行矩阵左乘运算,因此我们需要修改我们的 vertex shader 代码:

private static final String VERTEX_SHADER = "attribute vec4 vPosition;\n"+ "uniform mat4 uMVPMatrix;\n"+ "void main() {\n"+ "  gl_Position = uMVPMatrix * vPosition;\n"+ "}";

安卓 OpenGL ES 2.0 完全入门(一):基本概念和 hello world - Piasy的博客 | Piasy Blog

OpenGL ES 2.0 完全入门相关推荐

  1. Android OpenGL ES 2.0 屏幕坐标和3D世界坐标转换

    Android OpenGL ES 2.0 屏幕坐标和3D世界坐标转换 查看全文 http://www.taodudu.cc/news/show-6705596.html 相关文章: word中如何加 ...

  2. Android音视频学习系列(六) — 掌握视频基础知识并使用OpenGL ES 2.0渲染YUV数据

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  3. OpenGL ES 2.0 for Android教程(一)

    OpenGL ES 2 前言&第一章 文章传送门 OpenGL ES 2.0 for Android教程(二) OpenGL ES 2.0 for Android教程(三) OpenGL ES ...

  4. 利用JNI技术在Android中调用C++形式的OpenGL ES 2.0函数

    1.                 打开Eclipse,File-->New-->Project--->Android-->AndroidApplication Projec ...

  5. cocos2d-x 2.X for Android中需要使用OpenGL ES 2.0

    cocos2d-x 2.X for Android中需要使用OpenGL ES 2.0 到了2.X版本中,cocos2d-x for Android已经不再支持(或者说放弃支持)opengl es 1 ...

  6. OpenGL ES 3.0 基础知识

    首先要了解OpenGL的图形管线有哪些内容,再分别去了解其中的相关的关系: 管线分别包括了顶点缓冲区/数组对象,定点着色器,纹理,片段着色器,变换反馈,图元装配,光栅化,逐片段操作,帧缓冲区.其中顶点 ...

  7. LINUX 下构建OpenGL ES 3.0

    Ubuntu LINUX 下构建OpenGL ES 3.0 Category : OpenGL LINUX 下构建OpenGL ES 3.0 软件:PowerVRSDKSetup-4.0.run-x6 ...

  8. 《OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例》一6.6 本章小结

    本节书摘来异步社区<OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例>一书中的第6章,第6.6节,作者: 吴亚峰 责编: 张涛,更多章节内容可以访问云栖社区"异步社 ...

  9. 【AR实验室】OpenGL ES绘制相机(OpenGL ES 1.0版本)

    0x00 - 前言 之前做一些移动端的AR应用以及目前看到的一些AR应用,基本上都是这样一个套路:手机背景显示现实场景,然后在该背景上进行图形学绘制.至于图形学绘制时,相机外参的解算使用的是V-SLA ...

最新文章

  1. git本地仓库关联远端仓库
  2. 全志 添加PWM7参数
  3. 【iCore4 双核心板_ARM】例程二十:LWIP_TCP_CLIENT实验——以太网数据传输
  4. lua运行外部程序_一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩
  5. 尝试Office 2003 VSTO的开发、部署
  6. 现在能不能升级鸿蒙,能不能升级鸿蒙系统?
  7. “一个人自修没感觉!”
  8. git 的 merge 与 no-ff merge 的不同之处
  9. php管道的概念,管道线的概念定义及分析技巧的讲解
  10. 浅谈K短路算法(KSP)之一(A*算法求解)
  11. python并行计算进程池通信_Python使用进程池管理进程和进程间通信
  12. filedisk学习资料
  13. 向中级程序员转变的10个建议
  14. 无法定位程序输入点K32Get Module File Name Ex于动态链接库KERNEL32.dll上 的错误解析
  15. 第二讲:高性能计算关键技术和趋势分析
  16. HZD区块链导航网站好站点,你知道的都在这里
  17. 长生不老:从秦始皇到基因编辑
  18. docker安装包安装
  19. 考研数据结构判断题整合
  20. 阿翔编程学-Axis

热门文章

  1. c语言表达式怎么理解,C语言中是什么意思?ab怎么理解?
  2. logistic函数和softmax函数
  3. 基于云开发的微信小程序-miNi相册(开发环境介绍与登录)
  4. office在线编辑,多人协同方案onlyoffice
  5. OTG + USB ID + USB VBUS
  6. javascript中的polyfill是什么,polyfill和babel的关系
  7. Cheating Engine
  8. 关于ECONNRESET错误
  9. 考虑关系的图卷积神经网络R-GCN的一些理解以及DGL官方代码的一些讲解
  10. 第二章--第三节 成本函数和损失函数推导过程