ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 官方培训课程中文版:使用 OpenGL ES 2.0 绘制形状(Drawing Shapes)实战指南

Android 官方培训课程中文版:使用 OpenGL ES 2.0 绘制形状(Drawing Shapes)实战指南 文档教程移动开发【免费下载链接】android-training-course-in-chineseAndroid官方培训课程中文版项目地址https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese点击查看免费下载本文基于开源仓库 android-training-course-in-chinese 中 graphics/opengl/draw.md 翻译整理。仓库是对 Google Android Training 官方课程的完整中文译本本文聚焦其中绘制形状Drawing Shapes一课讲解在定义好几何形状之后如何通过 OpenGL ES 2.0 的着色器Shader、Program 与渲染管线把图形真正画到屏幕上。导读在定义形状Defining Shapes一课中你已经学会了用顶点数组 ByteBuffer定义三角形、矩形等几何对象但定义了形状和屏幕上出现图形之间还有一段距离——OpenGL ES 2.0 采用可编程渲染管线绘制每个形状都需要你亲手提供顶点着色器、片段着色器并把它们编译、链接进一个 Program 对象再逐帧提交顶点数据与绘制指令。本文将以Triangle类为例逐步拆解从初始化形状到在渲染器中调用draw()的完整流程并给出可直接照抄进自己项目的完整代码骨架。学完本课你将掌握为什么应该在onSurfaceCreated()中初始化形状对象顶点着色器与片段着色器的职责划分以及 GLSL 着色器代码的写法loadShader()、Program 创建/链接等辅助方法的实现顶点属性attribute、uniform、顶点缓冲区与glDrawArrays()的完整绘制调用链直接绘制无投影/相机变换时三角形为何会变形以及该问题的解法去向。从定义到绘制为什么需要更多代码OpenGL ES 2.0 与 OpenGL ES 1.x 的最大区别在于 2.0 引入了可编程着色器渲染流程不再是一堆固定的状态机开关而是由你编写的着色器代码驱动。因此画一个形状这个动作本身需要你显式提供以下三样东西这也是本课核心要素英文作用顶点着色器Vertex Shader处理每个顶点的位置输出裁剪坐标系下的顶点坐标gl_Position片段着色器Fragment Shader处理形状表面每个像素的颜色或纹理采样结果输出gl_FragColor程式Program一个 OpenGL ES 对象打包并链接了着色器供后续绘制引用绘制任何形状至少需要一个顶点着色器 一个片段着色器它们必须先被编译再链接进 Program最后通过glUseProgram()激活后才能发起绘制。仓库中本课程的姊妹篇 graphics/opengl/shapes.md 已经给出了Triangle类的顶点与颜色数据定义如triangleCoords[]、COORDS_PER_VERTEX 3、color[]及vertexBuffer本课将在此基础上继续补齐着色器、Program 与绘制逻辑。初始化形状Initialize Shapes在开始绘制之前要先初始化并加载期望绘制的图形对象。官方建议只要形状的原始坐标结构在执行期间不会变化就在渲染器的onSurfaceCreated()回调中完成初始化这是出于内存与执行效率的考量——onSurfaceCreated()在整个 Surface 生命周期内只被调用一次是加载 GPU 资源的正确时机。public class MyGLRenderer implements GLSurfaceView.Renderer { ... private Triangle mTriangle; private Square mSquare; public void onSurfaceCreated(GL10 unused, EGLConfig config) { ... // 初始化三角形 mTriangle new Triangle(); // 初始化正方形 mSquare new Square(); } ... }关于渲染器三回调onSurfaceCreated()/onDrawFrame()/onSurfaceChanged()各自的作用与触发时机可回看本系列课程的 graphics/opengl/environment.md构建 OpenGL ES 环境。其中GLSurfaceView、GLSurfaceView.Renderer分别是图形容器与绘制控制者本课中的渲染器会通过onDrawFrame()驱动形状的draw()。编写着色器顶点着色器与片段着色器在Triangle类内部用两个字符串定义最基础的着色器代码GLSLOpenGL Shading Languagepublic class Triangle { private final String vertexShaderCode attribute vec4 vPosition; void main() { gl_Position vPosition; }; private final String fragmentShaderCode precision mediump float; uniform vec4 vColor; void main() { gl_FragColor vColor; }; ... }逐行解读顶点着色器声明一个attribute vec4 vPosition接收每帧传入的顶点位置main()中直接把位置写到内置输出变量gl_Position裁剪空间坐标。这里的attribute是**逐顶点per-vertex**变量由glVertexAttribPointer()从顶点缓冲供给。片段着色器第一行precision mediump float;为 float 指定中精度OpenGL ES 着色器中必须显式声明精度限定符声明uniform vec4 vColoruniform是**全局per-draw**变量对整个绘制批次恒定main()中把颜色赋给gl_FragColor决定每个像素的输出颜色。这段代码对应的顶点数据与颜色数据来自 graphics/opengl/shapes.md 中定义的vertexBuffer与color[]RGBA 形式例如{ 0.63671875f, 0.76953125f, 0.22265625f, 1.0f }。编译着色器loadShader() 辅助方法着色器代码必须先在 OpenGL 环境中被编译才能投入使用。官方建议在渲染器类中准备一个可复用的辅助方法loadShader()public static int loadShader(int type, String shaderCode){ // 创建着色器对象type 为 GLES20.GL_VERTEX_SHADER顶点 // 或 GLES20.GL_FRAGMENT_SHADER片段 int shader GLES20.glCreateShader(type); // 把着色器源码填入对象并编译 GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); return shader; }要点说明glCreateShader()返回一个整数形式的着色器句柄Handle失败时返回 0glShaderSource()加载源码glCompileShader()执行编译编译成功后返回句柄供下一步链接进 Program 使用。创建 Program 并链接着色器为绘制形状必须把编译好的着色器添加进一个 OpenGL ES Program 对象并执行链接。官方特别强调编译与链接对 CPU 周期和处理时间消耗巨大应避免重复执行。如果着色器内容在执行期不会变化应当在构建应用时只创建一次并缓存复用。因此这些步骤放在绘制对象Triangle的构造函数里只执行一次public class Triangle() { ... private final int mProgram; public Triangle() { ... int vertexShader MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); // 创建空的 OpenGL ES Program mProgram GLES20.glCreateProgram(); // 把顶点着色器添加到 Program GLES20.glAttachShader(mProgram, vertexShader); // 把片段着色器添加到 Program GLES20.glAttachShader(mProgram, fragmentShader); // 生成 OpenGL ES Program 可执行体 GLES20.glLinkProgram(mProgram); } }这一段对应完整的编译 → 创建 Program → 挂载着色器 → 链接流水线是 OpenGL ES 2.0 中每个可绘制对象都会重复出现的固定套路。编写 draw()提交顶点数据并执行绘制至此准备工作完成接下来需要定义绘制时向渲染管线提交的信息。由于绘制属性顶点、颜色、坐标个数随形状不同而变化官方建议把绘制逻辑内聚在形状类内部。下面给出Triangle中完整的draw()方法其中用到了 graphics/opengl/shapes.md 中定义的vertexBuffer、COORDS_PER_VERTEX、color与triangleCoordsprivate int mPositionHandle; private int mColorHandle; private final int vertexCount triangleCoords.length / COORDS_PER_VERTEX; private final int vertexStride COORDS_PER_VERTEX * 4; // 每个顶点 4 字节float 大小 public void draw() { // 把 Program 加入 OpenGL ES 环境激活它 GLES20.glUseProgram(mProgram); // 获取顶点着色器中 vPosition 属性的位置句柄 mPositionHandle GLES20.glGetAttribLocation(mProgram, vPosition); // 启用顶点属性数组 GLES20.glEnableVertexAttribArray(mPositionHandle); // 准备三角形顶点坐标数据 GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); // 获取片段着色器中 vColor uniform 的位置句柄 mColorHandle GLES20.glGetUniformLocation(mProgram, vColor); // 设置绘制三角形的颜色 GLES20.glUniform4fv(mColorHandle, 1, color, 0); // 执行三角形绘制 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount); // 关闭顶点属性数组 GLES20.glDisableVertexAttribArray(mPositionHandle); }逐行解读绘制调用链调用作用关键参数glUseProgram(mProgram)激活链接好的 Program启用其着色器—glGetAttribLocation(mProgram, vPosition)查询顶点着色器中 attribute 变量的位置变量名须与着色器源码一致glEnableVertexAttribArray(mPositionHandle)启用该属性数组允许向着色器输入顶点数据—glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GL_FLOAT, false, vertexStride, vertexBuffer)描述顶点数据的布局每个顶点 3 个分量、类型为 float、不归一化、跨距为COORDS_PER_VERTEX * 4字节、数据来自vertexBuffervertexStride按字节计glGetUniformLocation(mProgram, vColor)查询片段着色器中 uniform 变量的位置—glUniform4fv(mColorHandle, 1, color, 0)传入 4 分量RGBA颜色作用于整个绘制批次1表示 1 个向量0为偏移glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)发出绘制指令以三角形为图元从顶点 0 开始绘制vertexCount个顶点图元类型决定顶点如何组装glDisableVertexAttribArray(mPositionHandle)绘制完成后关闭属性数组避免影响后续绘制—两个值得注意的实现细节vertexCount triangleCoords.length / COORDS_PER_VERTEX三角形 9 个坐标分量 ÷ 每顶点 3 个分量 3 个顶点vertexStride COORDS_PER_VERTEX * 4每个顶点在缓冲中占3 × 4 12字节float 占 4 字节即4 bytes per vertex注释的含义。在渲染器中驱动绘制完成上述代码后只需要在渲染器的onDrawFrame()回调中调用mTriangle.draw()三角形就会出现在屏幕上public void onDrawFrame(GL10 unused) { ... mTriangle.draw(); }结合本系列课程的环境搭建内容graphics/opengl/environment.md可以理解整体运行链路MyGLSurfaceView通过setRenderer()挂载渲染器 → 系统回调onSurfaceCreated()初始化Triangle/Square→ 每次 Surface 需要重绘时回调onDrawFrame()→ 调用draw()完成一帧渲染。如果你同时配置了GLSurfaceView.RENDERMODE_WHEN_DIRTY则渲染仅在数据变化配合requestRender()时发生默认的连续渲染模式下onDrawFrame()会被持续调用。运行效果如下图——直接在GLSurfaceView上、未做任何投影/相机变换所画出的三角形不使用投影或者相机视图画出来的三角形黄绿色三角形黑色背景已知问题与下一步形变与静止官方在课程末尾明确指出了这个第一版三角形存在的两个问题理解它们有助于衔接后续课程形变问题这个三角形看上去有些扁且当设备屏幕方向改变时形状也随之变化。原因是对象的顶点坐标没有根据GLSurfaceView显示区域的长宽比进行修正——在默认坐标系中[0,0,0]对应 View 中心、[1,1,0]对应右上角、[-1,-1,0]对应左下角直接绘制时并未针对屏幕宽高比做归一化。解决方式是投影Projection与相机视角Camera View相关内容见本系列课程的 graphics/opengl/projection.md通过Matrix.frustumM()在onSurfaceChanged()中计算投影矩阵用Matrix.setLookAtM()建立相机视角在onDrawFrame()中Matrix.multiplyMM()合成 MVP 矩阵并把顶点着色器升级为uniform mat4 uMVPMatrix最终图形即可按正确比例显示。静止问题三角形是静态的看起来比较无聊。后续的添加移动Adding Motion课程会引入旋转矩阵Matrix.setRotateM()把旋转矩阵与 MVP 矩阵相乘实现旋转动画响应触摸事件Responding to Touch Events课程则通过重写GLSurfaceView.onTouchEvent()把手指滑动换算成旋转角度配合volatile的mAngle与RENDERMODE_WHEN_DIRTY实现触控驱动的交互。小结本课完成了 OpenGL ES 2.0 绘制形状的完整闭环在onSurfaceCreated()中初始化形状对象 → 用 GLSL 编写顶点/片段着色器 → 通过loadShader()编译 → 在构造函数中创建并链接 Program → 在draw()中激活 Program、查询 attribute/uniform 位置、提交顶点缓冲并调用glDrawArrays()→ 最后在渲染器onDrawFrame()中驱动绘制。这一套流程是后续学习投影、相机视角、旋转动画与触摸交互的共同基石掌握了它你就具备了在 Android 上自由驾驭可编程 GPU 渲染管线的入门能力。要继续深入学习可依次阅读本仓库 graphics/opengl/index.md 课程总览及其下各课环境搭建 graphics/opengl/environment.md → 形状定义 graphics/opengl/shapes.md → 本课绘制形状 graphics/opengl/draw.md → 投影与相机 graphics/opengl/projection.md → 添加移动 graphics/opengl/motion.md → 触摸交互 graphics/opengl/touch.md。赞分享文档教程移动开发【免费下载链接】android-training-course-in-chineseAndroid官方培训课程中文版项目地址https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese点击查看免费下载相关推荐zjuthesis模板封面类型配置架构解析与技术实现zjuthesis模板封面类型配置架构解析与技术实现 浙江工业大学学位论文LaTeX模板zjuthesis作为一款高度模块化的学术排版系统其封面类型配置机制体文档教程移动开发《Android 官方培训课程中文版》OpenGL ES 系列实战运用投影与相机视角Projection Camera View完整指南《Android 官方培训课程中文版》OpenGL ES 系列实战运用投影与相机视角Projection Camera View完整指南 本文基于本仓文档教程移动开发Android官方培训课程中文版教程Android官方培训课程中文版教程 1. 项目介绍 Android官方培训课程中文版Android Training Course in Chinese是文档教程移动开发上一篇Formily Core Query 对象完全指南字段查询 API 的方法签名、底层实现与实战用法下一篇PaddleSpeech TimeTrigger 解析基于固定时间间隔的训练触发器实现与原理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表