当前位置: 首页 > news >正文

安卓进阶——OpenGL ES

 ✅作者简介:大家好,我是 Meteors., 向往着更加简洁高效的代码写法与编程方式,持续分享Java技术内容。
🍎个人主页:Meteors.的博客
💞当前专栏:知识分享
特色专栏:知识分享
🥭本文内容:安卓进阶——OpenGL ES
📚 ** ps **  :阅读文章如果有问题或者疑惑,欢迎在评论区提问或指出。


目录

一、2D

(一)优点

(二) 渲染核心概念

1. 顶点(Vertices)

2.  纹理坐标(Texture Coordinates)

3. 着色器(Shaders)

(1)顶点着色器

(2)片段着色器

4. 程序(Program)

(三)在 Android 中的基本使用步骤

1. 创建渲染表面(GLSurfaceView)

2. 实现渲染器(GLSurfaceView.Renderer)

(四)进阶 2D 技巧

二、3D

(一)介绍

1. 3D 坐标系

2. 矩阵变换

3. 深度测试

4. 3D 模型数据

(二)核心 3D 技术

1. 光照(Lighting)

2. 纹理映射

3. 混合(Blending)

(三)在 Android 中的实现步骤

(四)进阶 3D 技术


一、2D

(一)优点

  • 极高的性能:OpenGL 直接利用 GPU 进行硬件加速。对于需要频繁、大量重绘的界面(如复杂游戏、动态壁纸、高速图表、图片编辑器、视频播放器),它的性能远超基于 CPU 的 Canvas 绘制。
  • 高效的图像处理:OpenGL 的片段着色器可以轻松实现对图像/纹理的实时处理,如美颜滤镜、颜色调整、模糊、边缘检测等。这些效果在 Canvas 上实现要么很慢,要么无法实现。
  • 平滑的动画和特效:可以实现复杂的 2D 变换(旋转、缩放、平移)、粒子系统、光照和阴影效果,并且能保持极高的帧率。
  • 跨平台基础:OpenGL ES 是一个跨平台的图形库。掌握了它在 Android 上的 2D 应用,对理解 iOS、Web(WebGL)的图形编程也大有裨益。

(二) 渲染核心概念

要将一个 2D 图片(例如一张 PNG)绘制到屏幕上,需要理解以下几个关键概念和流程。下图清晰地展示了从2D图像数据到最终屏幕渲染的完整管线流程

1. 顶点(Vertices)

2D 图形本质上是由三角形构成的。一个矩形需要两个三角形(6 个顶点)。每个顶点不仅包含它在坐标系中的位置(x, y),还可以包含其他信息,如纹理坐标、颜色等。

float[] vertices = { -1.0f, -1.0f, // 左下角 - 三角形1 1.0f, -1.0f, // 右下角 - 三角形1 -1.0f, 1.0f, // 左上角 - 三角形1 -1.0f, 1.0f, // 左上角 - 三角形2 1.0f, -1.0f, // 右下角 - 三角形2 1.0f, 1.0f // 右上角 - 三角形2 };

2.  纹理坐标(Texture Coordinates)

纹理坐标定义了顶点在纹理图像(你的 2D 图片)上的对应位置。范围是 [0, 0]到 [1, 1]

// 与顶点数组顺序对应的纹理坐标 (s, t 或 u, v)
float[] textureVertices = {0.0f, 1.0f, // 纹理左下角 - 对应顶点左下角1.0f, 1.0f, // 纹理右下角 - 对应顶点右下角0.0f, 0.0f, // 纹理左上角 - 对应顶点左上角0.0f, 0.0f, // 纹理左上角 - 对应顶点左上角1.0f, 1.0f, // 纹理右下角 - 对应顶点右下角1.0f, 0.0f  // 纹理右上角 - 对应顶点右上角
};

3. 着色器(Shaders)

这是 OpenGL ES 2.0 及以上版本的核心。着色器是运行在 GPU 上的小程序

(1)顶点着色器

  • 作用:处理每个顶点。在这里可以进行坐标变换(如平移、旋转、缩放)。

  • 示例:一个简单的直通着色器,不做任何变换。

    // plain_vertex_shader.glsl
    attribute vec4 a_Position;
    attribute vec2 a_TexCoord;
    varying vec2 v_TexCoord; // 传递给片段着色器void main() {gl_Position = a_Position; // 设置顶点的最终位置v_TexCoord = a_TexCoord;
    }
(2)片段着色器

  • 作用:处理每个像素(片段)的颜色。在这里进行纹理采样(从图片上取颜色)。

  • 示例

    ​
    // plain_fragment_shader.glsl
    precision mediump float; // 设置精度
    varying vec2 v_TexCoord; // 从顶点着色器传来
    uniform sampler2D u_TextureUnit; // 纹理采样器void main() {// texture2D 函数,根据纹理坐标从纹理上取样颜色gl_FragColor = texture2D(u_TextureUnit, v_TexCoord);
    }​

4. 程序(Program)

将顶点着色器和片段着色器链接成一个完整的 OpenGL ES 程序,供 GPU 执行。


(三)在 Android 中的基本使用步骤

1. 创建渲染表面(GLSurfaceView)

GLSurfaceView是 Android 提供的专门用于显示 OpenGL 画面的 View,它管理着 EGLContext和渲染线程。

<!-- activity_main.xml -->
<android.opengl.GLSurfaceViewandroid:id="@+id/gl_surface_view"android:layout_width="match_parent"android:layout_height="match_parent" />
// MainActivity.java
public class MainActivity extends AppCompatActivity {private GLSurfaceView mGLSurfaceView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mGLSurfaceView = findViewById(R.id.gl_surface_view);// 设置 OpenGL ES 2.0 上下文mGLSurfaceView.setEGLContextClientVersion(2);// 设置自定义的渲染器MyRenderer renderer = new MyRenderer(this);mGLSurfaceView.setRenderer(renderer);// 设置渲染模式:持续渲染(RENDERMODE_CONTINUOUSLY)// 或按需渲染(RENDERMODE_WHEN_DIRTY)mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);}@Overrideprotected void onResume() {super.onResume();mGLSurfaceView.onResume();}@Overrideprotected void onPause() {super.onPause();mGLSurfaceView.onPause();}
}

2. 实现渲染器(GLSurfaceView.Renderer)

public class MyRenderer implements GLSurfaceView.Renderer {private Context mContext;private int mProgramId;private int mTextureId;// ... 其他变量,如顶点缓冲对象(VBO)的句柄public MyRenderer(Context context) {mContext = context;}@Overridepublic void onSurfaceCreated(GL10 glUnused, EGLConfig config) {// 1. 设置清屏颜色GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// 2. 编译和链接着色器,创建 OpenGL 程序int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);mProgramId = GLES20.glCreateProgram();GLES20.glAttachShader(mProgramId, vertexShader);GLES20.glAttachShader(mProgramId, fragmentShader);GLES20.glLinkProgram(mProgramId);// 3. 创建纹理并加载图片(例如从 Resources)mTextureId = loadTexture(mContext, R.drawable.my_image);// 4. 将顶点数据和纹理坐标数据加载到 ByteBuffer 中,并传入 OpenGL// ... (代码略长,涉及 FloatBuffer 和 glVertexAttribPointer)}@Overridepublic void onSurfaceChanged(GL10 glUnused, int width, int height) {// 设置视口(Viewport),当 Surface 尺寸改变时调用(如屏幕旋转)GLES20.glViewport(0, 0, width, height);}@Overridepublic void onDrawFrame(GL10 glUnused) {// 每一帧的绘制工作// 1. 清空颜色缓冲区GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);// 2. 使用我们创建的程序GLES20.glUseProgram(mProgramId);// 3. 启用顶点属性并传递数据// ... (代码略长,涉及 glEnableVertexAttribArray 和 glVertexAttribPointer)// 4. 绑定纹理到纹理单元GLES20.glActiveTexture(GLES20.GL_TEXTURE0);GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);// 5. 告诉着色器使用哪个纹理单元(这里是 0)int textureUniformHandle = GLES20.glGetUniformLocation(mProgramId, "u_TextureUnit");GLES20.glUniform1i(textureUniformHandle, 0);// 6. 执行绘制!GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6); // 绘制 6 个顶点(2个三角形)}// 辅助方法:加载和编译着色器private int loadShader(int type, String shaderCode) { ... }// 辅助方法:从资源加载图片生成 OpenGL 纹理private int loadTexture(Context context, int resourceId) { ... }
}

(四)进阶 2D 技巧

  1. 纹理滤镜(Filtering):当纹理被放大或缩小时,如何采样。GL_LINEAR(平滑)和 GL_NEAREST(像素化)。

  2. 纹理环绕(Wrapping):当纹理坐标超出 [0, 1]时如何处理。GL_REPEAT(重复)和 GL_CLAMP_TO_EDGE(拉伸边缘)。

  3. 矩阵变换(Matrix Transformations):在顶点着色器中使用模型(Model)、视图(View)、投影(Projection)矩阵来控制物体的位置、旋转、缩放以及相机视角。这是实现 2D 动画和复杂场景的基石。可以借助 android.opengl.Matrix类进行矩阵计算。

  4. 粒子系统(Particle Systems):用于实现火焰、烟雾、爆炸等效果,通过一次提交大量顶点并利用着色器进行高效计算。

  5. 帧缓冲区(FrameBuffer Objects - FBOs):离屏渲染。可以先将场景渲染到一个纹理上,然后对这个纹理进行二次处理(如全屏模糊),实现高级后期处理效果。


二、3D

(一)介绍

1. 3D 坐标系

从 2D 的平面坐标系,扩展为三维空间坐标系。

  • 右手坐标系:在 OpenGL 中通常使用右手坐标系。伸出右手,食指指向 x 轴正方向(右),中指指向 y 轴正方向(上),大拇指则指向 z 轴正方向(从屏幕向外指向你)。

  • 模型坐标 -> 世界坐标 -> 视图坐标 -> 裁剪坐标:一个顶点的最终位置需要经过一系列变换

2. 矩阵变换

这是 3D 编程中最核心、最重要的概念。所有 3D 空间中的移动、旋转、缩放以及最终的透视效果,都是通过矩阵乘法完成的。下图清晰地展示了顶点从本地坐标到最终屏幕坐标的变换全过程:

下面解释图中的变换阶段:

  • 模型矩阵(Model Matrix)

    • 作用:将顶点从模型坐标(本地坐标)​ 变换到世界坐标。它定义了模型(一个物体)在世界空间中的位置、大小和朝向。

    • 示例:一个放在世界坐标 (2, 0, -5) 位置,放大 2 倍的立方体。

  • 视图矩阵(View Matrix)

    • 作用:相当于“相机”的放置。它将所有顶点从世界坐标变换到相机坐标(视图坐标)。视图矩阵定义了相机的位置、观察方向和相机的向上方向。

    • 理解:想象一下,不是你移动了场景中的所有物体,而是你扛着相机在移动和旋转来取景。Matrix.setLookAtM方法可以方便地创建视图矩阵。

  • 投影矩阵(Projection Matrix)

    • 作用:将 3D 坐标投影到 2D 屏幕上,并创建透视效果,使远处的物体看起来更小。这是实现 3D 感的关键!

    • 透视投影:模拟人眼看到的真实世界,有“近大远小”的效果。通过 Matrix.frustumM或 Matrix.perspectiveM创建。

    • 正射投影:物体的大小不受距离影响,常用于 CAD 绘图或 2.5D 游戏(如模拟城市)。通过 Matrix.orthoM创建。

这三种变换的矩阵通常在顶点着色器中相乘,共同组成一个 MVP 矩阵(Model-View-Projection Matrix)

顶点着色器示例

// vertex_shader_3d.glsl
uniform mat4 u_MVPMatrix; // 组合好的 Model-View-Projection 矩阵
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;void main() {// 将顶点位置与 MVP 矩阵相乘,得到最终的裁剪坐标gl_Position = u_MVPMatrix * a_Position;v_TexCoord = a_TexCoord;
}

在 Java 代码中,你需要计算这个矩阵:

// 在 onDrawFrame 中计算 MVP 矩阵
float[] modelMatrix = new float[16];
float[] viewMatrix = new float[16];
float[] projectionMatrix = new float[16];
float[] mvpMatrix = new float[16];// 初始化为单位矩阵
Matrix.setIdentityM(modelMatrix, 0);
// 将模型向后移动 5 个单位,否则相机可能会在物体内部
Matrix.translateM(modelMatrix, 0, 0f, 0f, -5f);
// 让模型绕 y 轴旋转
Matrix.rotateM(modelMatrix, 0, angleInDegrees, 0.0f, 1.0f, 0.0f);// 设置相机位置:眼睛在 (0,0,0),看向 z 轴负方向,向上向量为 y 轴
Matrix.setLookAtM(viewMatrix, 0, 0, 0, 0, 0f, 0f, -5f, 0f, 1.0f, 0.0f);// 设置透视投影:45度视野,宽高比,近裁剪面距离 1,远裁剪面距离 10
float ratio = (float) width / height;
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 1, 10);// 计算 MVP = P * V * M (注意顺序,从右向左读)
Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
Matrix.multiplyMM(mvpMatrix, 0, mvpMatrix, 0, modelMatrix, 0);// 将 mvpMatrix 传递到着色器中的 u_MVPMatrix

3. 深度测试

在 3D 空间中,物体有前后关系。OpenGL 使用深度缓冲区来记录每个像素的深度值(z 值)。在绘制一个片段时,会检查其深度值是否比缓冲区中已有值更小(离相机更近)。如果是,则绘制并更新缓冲区;如果不是(被遮挡),则丢弃。

开启深度测试

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {...// 开启深度测试GLES20.glEnable(GLES20.GL_DEPTH_TEST);
}@Override
public void onDrawFrame(GL10 gl) {// 清空颜色和深度缓冲区GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);...
}

4. 3D 模型数据

一个复杂的 3D 模型(如游戏角色)由成千上万个三角形组成。通常我们不会在代码中硬定义这些顶点,而是从 .obj等模型文件中加载顶点、纹理坐标和法线数据。

  • 法线:一个垂直于三角形表面的向量。用于计算光照。


(二)核心 3D 技术

1. 光照(Lighting)

光照是让 3D 场景具有真实感的关键。基本的光照模型(Phong)包含三个部分:

  • 环境光:模拟场景中的间接光,均匀地照亮所有物体。

  • 漫反射:模拟来自一个方向的光源,其强度与表面法线和光线方向的夹角有关。这是物体呈现颜色的主要部分。

  • 镜面反射:模拟物体表面的高光,产生亮斑,与观察视角有关。

计算光照需要在顶点或片段着色器中进行,需要提供法线、光源位置、相机位置等信息。

一个简单的漫反射光照的顶点着色器示例

uniform mat4 u_MVPMatrix;
uniform mat4 u_ModelMatrix; // 需要单独的法线矩阵来变换法线
attribute vec4 a_Position;
attribute vec3 a_Normal; // 顶点法线
varying vec3 v_Color;void main() {gl_Position = u_MVPMatrix * a_Position;// 计算光照(在世界空间)vec3 lightPos = vec3(5.0, 5.0, 5.0); // 光源位置vec3 modelPos = vec3(u_ModelMatrix * a_Position); // 顶点在世界空间的位置vec3 normal = normalize(vec3(u_ModelMatrix * vec4(a_Normal, 0.0))); // 变换法线vec3 lightDir = normalize(lightPos - modelPos);float diffuse = max(dot(normal, lightDir), 0.1); // 计算漫反射强度,0.1是环境光v_Color = vec3(1.0, 0.5, 0.0) * diffuse; // 物体的颜色乘以光照强度
}

2. 纹理映射

与 2D 类似,但需要为 3D 模型的每个顶点指定正确的纹理坐标。这通常在 3D 建模软件中完成。

3. 混合(Blending)

用于实现透明效果(如玻璃、水)。需要开启混合并设置混合函数

GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
// 注意:绘制透明物体时,通常需要从远到近排序,否则混合结果可能不正确。

(三)在 Android 中的实现步骤

框架与 2D 完全相同(GLSurfaceViewRenderer),主要区别在于 onDrawFrame中的逻辑。

  1. 数据:顶点数据从 2D 矩形变为 3D 模型(如立方体、球体)。一个立方体至少需要 36 个顶点(6个面 * 2个三角形 * 3个顶点)。

  2. 着色器:顶点着色器必须包含 MVP 矩阵变换。通常会加入光照计算。

  3. 绘制:清屏时必须同时清空颜色和深度缓冲区。

  4. 矩阵管理:需要在 Java 端(使用 android.opengl.Matrix)为每个物体计算其模型矩阵、视图矩阵和投影矩阵,并组合成 MVP 矩阵传入着色器。

一个简单的 3D 立方体渲染器 onDrawFrame示例

@Override
public void onDrawFrame(GL10 gl) {GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);// 计算相机视角和投影(通常放在 onSurfaceChanged 或当屏幕旋转时计算)Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 0, 0f, 0f, -5f, 0f, 1.0f, 0.0f);Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 1, 10);// 为当前绘制的立方体计算模型矩阵(例如,让它旋转)Matrix.setIdentityM(mModelMatrix, 0);Matrix.translateM(mModelMatrix, 0, 0, 0, -5.0f); // 移到视野内Matrix.rotateM(mModelMatrix, 0, mAngle, 0.0f, 1.0f, 0.0f); // 绕 y 轴旋转mAngle += 1.0f; // 更新旋转角度// 计算 MVP 矩阵Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);// ... 其余部分(使用程序、传递顶点数据、传递 MVP 矩阵、绑定纹理、绘制)与 2D 类似GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 36); // 立方体有 36 个顶点
}

(四)进阶 3D 技术

  • 加载复杂模型:使用 AssetManager读取 .obj文件,解析顶点、法线、纹理坐标。

  • 多遍渲染:先渲染场景到纹理(使用 FBO),再进行后处理(如模糊、Bloom 效果)。

  • 天空盒:一个巨大的立方体包围整个场景,模拟天空或远山。

  • 粒子系统:用于火焰、烟雾、魔法效果。

  • 骨骼动画:让 3D 角色动起来。

  • 着色器特效:如法线贴图(增加表面细节)、凹凸贴图、卡通渲染等。

http://www.dtcms.com/a/581498.html

相关文章:

  • 做app动态界面的网站有哪些网站建设从哪入手
  • RV1126 NO.41:利用OPENCV的API计算轮廓面积
  • 15 langgraph基本组件
  • 网站开发答辩ppt上海网站排名
  • LeetCode 面试经典 150_二叉树_翻转二叉树(69_226_C++_简单)(DFS)
  • 【PLM实施专家宝典】离散制造企业ECO管理优化方案:构建自动化、零错误的变更引擎
  • go tool command
  • 网站流量如何做cms网站开发流程
  • HTML ASCII 编码解析与应用
  • Javascript函数之函数的参数以及默认参数?
  • LNMP部署及应用
  • 优质做网站哪家正规wordpress附件修复
  • Java-HTTP响应以及HTTPS(下)
  • [人工智能-大模型-135]:词向量的演进,对词向量的对Transformer架构理解的前提与关键。
  • 【1Panel】1、安装1Panel
  • JAVA:Spring Boot3 新特性解析的技术指南
  • 数据结构系列之十大排序算法
  • Spring Boot接收前端参数的注解总结
  • .c .o .a .elf .a2l hex map 这些后缀文件的互相之间的联系和作用
  • 纯静态网站seowordpress内页模板
  • 包装公司网站模板下载哈尔滨网络seo公司
  • 基于协同过滤算法的小说推荐系统_django+spider
  • VSCODE 插件 rust-analyzer 使用遇到的问题 快捷键查看定义
  • 个人网页设计制作网站模板西宁做网站制作的公司
  • Ubuntu24.10禁用该源...+vmware无法复制黏贴“天坑闭环”——从 DNS 诡异解析到 Ubuntu EOL 引发的 apt 404排除折腾记
  • npm i / npm install 卡死不动解决方法
  • 安装GPT4Free(也就是g4f)的最新版:g4f-6.5.7
  • h5四合一网站建设做新闻的网站怎样赚钱
  • SG-CAN-4G-410(4 路 CAN 转 4G 网关)
  • 潍坊做网站的做网站销售一个星期的计划