单页式网站 seo深圳平台推广
news/
2025/9/23 22:30:35/
文章来源:
单页式网站 seo,深圳平台推广,软件开发技术培训课程,app免费下载入口1、OpenGL 基础知识
1.1 OpenGL 简介
OpenGL#xff08;Open Graphics Library#xff09;是图形领域的工业标准#xff0c;是一套跨编程语言、跨平台、专业的图形编程#xff08;软件#xff09;接口。它用于二维、三维图像#xff0c;是一个功能强大#xff0c;调用…1、OpenGL 基础知识
1.1 OpenGL 简介
OpenGLOpen Graphics Library是图形领域的工业标准是一套跨编程语言、跨平台、专业的图形编程软件接口。它用于二维、三维图像是一个功能强大调用方便的底层图形库。它与硬件无关可以在不同的平台如 Windows、Linux、Mac、Android、IOS 之间进行移植。因此支持 OpenGL 的软件具有很好的移植性可以获得非常广泛的应用比如 PS 在部分功能和操作中使用 OpenGL 加速以提高图像处理和渲染的性能。
对于 Android 系统而言它支持的是 OpenGL ES。OpenGL ES 是针对手机、PDA 和游戏主机等嵌入式设备设计的 OpenGL API 的子集。下面是 Android 系统版本与 OpenGL ES 的支持版本的一般对照表具体的设备和制造商可能会在特定型号上进行适配和调整
Android 4.0Ice Cream SandwichOpenGL ES 2.0Android 4.1 - 4.3Jelly BeanOpenGL ES 2.0/3.0Android 4.4KitKatOpenGL ES 2.0/3.0/3.1Android 5.0 - 5.1LollipopOpenGL ES 3.1/3.2Android 6.0MarshmallowOpenGL ES 3.1/3.2Android 7.0 - 12.0OpenGL ES 3.2
OpenGL 官方上提供了很多资料作为 Android 开发者需要关注 OpenSL ES可以在OpenGL 官网页面找到两个 OpenSL ES 的资料 第一个是 OpenGL ES 的官方文档 可以在右侧选择版本 第二个是 OpenGL ES 的 API 快速查找手册 1.2 OpenGL 绘制原理
OpenGL 是可以绘制 3D 图像的比如绘制一头牛的过程如下 Vertex Processing首先将牛抽象为 3D 网格在网格中牛由无数个像素点组成将这些像素点视为顶点每 3 个顶点可以构成一个三角形这个三角形可以视为一个纹理多个纹理可以组成一个面最终形成一头牛。其中将 3D 网格转化为顶点的过程是由顶点着色器完成的Rasterization光栅化将几何图形转换为在屏幕上显示的像素网格得到片元Raster Operations进行纹理过滤使用片元着色器对片元上色Fragment Processing经过片元处理得到最终要输出的图像
以上过程涉及到一些 OpenGL 的专业名词解释如下
纹理Texture是将图像或图案应用到几何图形上的一种技术。 纹理可以理解为在三维空间中的一个图像或图案它可以覆盖在三维对象的表面上给出了表面的颜色和细节。实际上是通过将二维的纹理映射到三维的几何图形的表面上从而实现对几何图形进行贴图使其呈现出真实的外观和细节。纹理通常是由像素组成的二维图像可以是从图像文件加载的位图或者是通过代码生成的程序纹理。在 OpenGL 中纹理坐标Texture Coordinates被用来映射纹理到三维几何图形上的对应位置。纹理可以看作是一个二维数组由一系列像素组成。每个像素包含了图像的颜色信息或其他数据。纹理可以包含不同的图像格式例如 RGB、RGBA、灰度图等。 顶点着色器Vertex Shader顶点着色器是一种在图形渲染管线中执行的程序用于处理输入的顶点数据。它以顶点为基本单位对每个输入顶点进行处理和变换例如对顶点位置、颜色、法线等进行变换、计算和插值。顶点着色器通常用于执行顶点变换、几何操作和顶点属性的计算。光栅化Rasterization是计算机图形学中的一个重要概念用于将几何图形转换为在屏幕上显示的像素图像。光栅化的过程涉及将连续的几何图形转换为离散的像素表示。在三维图形渲染中几何图形通常是由三角形组成的三角网格Triangle Mesh。光栅化将每个三角形映射到屏幕上的像素并确定每个像素的颜色和深度值。其主要步骤包括 顶点处理Vertex Processing在光栅化之前对每个顶点进行变换、投影和其他处理以将顶点从三维空间转换到屏幕空间。三角形设置Triangle Setup确定每个三角形的边界框bounding box或边界区域以限定光栅化的范围。逐像素处理Pixel Processing对于每个位于三角形边界框内的像素确定其是否位于三角形内部并计算其颜色、纹理坐标、深度值等属性。插值计算Interpolation根据顶点属性的值通过插值计算来获取每个像素的准确属性值如颜色插值、纹理坐标插值等。像素输出Pixel Output根据计算得到的像素属性值将其写入帧缓冲区Frame Buffer最终形成屏幕上的图像 片元着色器Fragment Shader片元着色器是一种在图形渲染管线中执行的程序用于处理光栅化后的片元像素数据。它以片元为基本单位对每个片元进行处理和计算例如计算片元的颜色、法线、光照等。片元着色器通常用于执行光照模型、纹理采样、混合和其他片元级别的计算。
顶点着色器和片元着色器需要程序员自行实现。
1.3 Android 绘制原理
Android 在绘制时实际上就用到了 OpenGL。比如绘制一个 Button 大致步骤如下
LayoutInflater 解析布局文件将 Button 从 xml 的标签解析成包含边界和宽高等信息的 Button 对象CPU 对 Button 对象内的边界、宽高、颜色等进行计算处理成纹理多维的向量图形交给 GPUGPU 负责填充将向量图形栅格化转换成像素位图图像再画到屏幕上
实际上 OpenGL 就是工作在 GPU 当中的下图从 CPU、GPU 和屏幕的维度描绘了绘制过程 既然提到 CPU 与 GPU就要简单看一下二者的区别与联系 1.4 OpenGL 坐标系
OpenGL 编程会常用到如下三种坐标系 OpenGL世界坐标系的边界为1 Android坐标系原点是屏幕左上角 纹理坐标系原点是左下角
OpenGL 世界坐标系的原点 (0,0) 在中心边界最大坐标为 1Android 屏幕坐标系的原点 (0,0) 在左上角边界也是 1OpenGL 纹理坐标系的原点 (0,0) 在左下角边界还是 1
OpenGL 世界坐标系与 Android 屏幕坐标系的顶点是有对应关系的如图所示 2、OpenGL SL 语法基础
OpenGL 编程使用的着色器语言。
着色器语言Shader Language是一种特定于图形编程的编程语言用于编写顶点着色器、片元着色器和其他类型的着色器程序。在 OpenGL 中常用的着色器语言是 GLSLOpenGL Shading Language它是一种高级着色器语言用于描述和执行图形渲染管线中的着色器程序。GLSL 提供了丰富的语法和内置函数使得开发者可以编写灵活和高效的着色器代码。
以下是后续 Demo 中常用的语法内容
数据类型描述float浮点型vec2含两个浮点型数据的向量vec4含四个浮点型数据的向量xyzw、rgba、stpqsampler2D2D纹理采样器代表一层纹理用uniform来修饰
修饰符描述attribute属性变量。只能用于顶点着色器中。一般用该变量来表示一些顶点数据如顶点坐标、纹理坐标、颜色等。uniform一致变量。在着色器执行期间一致变量的值是不变的。与 const 常量不同的是这个值在编译时期是未知的是由着色器外部初始化的。varying易变变量。是从顶点着色器传递到片元着色器的数据变量。
内建函数描述texture2D(采样器, 坐标) 采样指定位置的纹理
这里顺便了解一下 OpenGL 函数的命名格式 函数库前缀 根命令 可选的参数数量 可选的参数类型 使用参数数量和参数类型是因为面向过程的 C 语言中没有函数重载。
内建变量描述gl_Positionvec4类型表示顶点着色器中顶点位置gl_FragColorvec4类型表示片元着色器中颜色
精度名称描述precision lowp低精度precision mediump中精度precision highp高精度
举个示例比如要声明一个 4 * 4 的矩阵
mat4 m1 mat4(1.0, 2.0, 3.0, 4.0,1.0, 2.0, 3.0, 4.0,1.0, 2.0, 3.0, 4.0,1.0, 2.0, 3.0, 4.0
)mat4 m2 mat4(2.0)mat4 m3 mat4(2.0, 0.0, 0.0, 0.0,0.0, 2.0, 0.0, 0.0,0.0, 0.0, 2.0, 0.0,0.0, 0.0, 0.0, 2.0
)其中 m2 初始化的结果与 m3 相同即初始化左上角到右下角对角线上位置上的数据其余位置均为 0。
3、OpenGL SL 简单使用
这一节我们使用 OpenGL SL 在 Android 屏幕上画一个等腰直角三角形。
3.1 OpenGL SL 简介
首先我们要清楚OpenGL SL 本质上是一个 NDK 的动态库在 NDK 的 toolchains 中可以找到它们 但是 Google 将 NDK 接口封装为 Java API 方便上层调用 图形渲染管线
OpenGL 图形渲染管线计算机图形学中用于将三维模型转换为最终渲染图像的流程和算法的简化流程图它展示了顶点着色器和片元着色器之间的数据流 OpenGL 绘制时需要自行实现顶点着色器和片元着色器
顶点着色器的输入数据包括顶点属性如位置、法线等以及可选的采样器。输出数据是变换后的顶点位置gl_Position、正面朝向标志gl_FrontFacing以及其他临时变量 首先顶点着色器接收来自应用程序的顶点数据这些数据通常包含顶点的位置、法线、纹理坐标等信息。顶点着色器的主要任务是对这些顶点进行变换例如应用模型矩阵、视图矩阵和投影矩阵以将顶点从对象空间转换到屏幕空间然后顶点着色器会生成一个 gl_Position 变量它是经过变换后的顶点位置表示该顶点在屏幕上的位置。此外顶点着色器还可以生成其他临时变量用于后续的计算或传递给片元着色器接下来顶点着色器会将变换后的顶点数据传递给片元着色器。在这个过程中顶点着色器还会生成一些 Varying 变量这些变量可以用来存储顶点之间的一些信息比如纹理坐标或者光照参数 片元着色器输入数据包括从顶点着色器传递过来的 Varying 变量如纹理坐标以及一些内置变量如点大小 gl_PointSize 和片段坐标 gl_FragCoord。输出数据是一个颜色值gl_FragColor它将被用于最终的图像渲染 片元着色器接收到顶点着色器传递来的 Varying 变量以及一些内置变量如 gl_FragCoord 和 gl_PointSize 等。片元着色器的主要任务是对每个像素的颜色进行计算这可能涉及到纹理采样、光照计算、混合操作等最后片元着色器会生成一个 gl_FragColor 变量这是最终的颜色值它会被用于绘制像素。如果需要片元着色器也可以生成其他临时变量用于后续的计算或传递给其他阶段
可编程管线
再来看 OpenGL 2.0 增加的可编程管线它显示了图形渲染管线的基本工作流程 各部分职能
顶点着色器是图形渲染管线的第一步它主要负责处理顶点数据包括顶点位置、法线、纹理坐标等。顶点着色器可以根据需要修改顶点数据例如应用变换矩阵、添加顶点动画效果等图元装配将顶点数据组装成图元的过程常见的图元有三角形、矩形等。图元装配的结果是一组有序的顶点列表这些顶点将被发送到光栅化阶段光栅化将图元转换为像素的过程它根据图元的几何形状和屏幕分辨率确定哪些像素应该被填充。光栅化阶段还负责执行裁剪、深度测试、模板测试等操作片元着色器是图形渲染管线中的一个重要步骤它负责处理每个像素的颜色信息。片元着色器可以从顶点着色器获取数据例如顶点位置、法线、纹理坐标等然后根据这些数据计算出每个像素的颜色逐片元处理在片元着色器之后的一个阶段它负责执行各种后期处理效果例如模糊、抗锯齿、景深等。这些效果可以在片元着色器之后应用从而实现更复杂的视觉效果帧缓冲区是图形渲染管线的最后一个阶段它负责将渲染结果保存到屏幕上。帧缓冲区可以是硬件设备的一部分也可以是软件模拟的内存区域
顶点着色器只是确定了图形的外部边界轮廓而片元着色器才负责绘制内部的纹理纹路与颜色。
主要组件
最后来了解 Android OpenGL ES 包含的主要组件
GLSurfaceView继承自 SurfaceView其内嵌的 Surface 专门负责 OpenGL 渲染。它支持 管理 Surface 与 EGL允许自定义渲染器 Render让渲染器在独立的线程里运作和 UI 线程分离按需渲染on-demand和连续渲染continuous EGLOpenGL 是一个跨平台操作 GPU 的 API但 OpenGL 需要与本地视窗系统进行交互这就需要一个中间控制层EGL 就是链接 OpenGL ES 和本地窗口系统的接口引入 EGL 就是为了屏蔽不同平台上的区别类似于上层通过 JNI 实现与本地代码的交互
知道大致流程后可以进入代码阶段了。
3.2 初始化
首先在 AndroidManifest 中声明使用 OpenGL
uses-feature android:glEsVersion0x00020000 android:requiredtrue/然后声明一个自定义 View 继承 GLSurfaceView并设置 EGL 版本、渲染器和渲染模式
// GLSurfaceView 继承了 SurfaceView它自带一个线程 GLThread 进行渲染工作
class GLView(context: Context?, attrs: AttributeSet?) : GLSurfaceView(context, attrs) {constructor(context: Context?) : this(context, null)init {// 设置 EGL 版本setEGLContextClientVersion(2)// 设置渲染器setRenderer(GLRender())// 设置渲染模式// RENDERMODE_WHEN_DIRTY 会在 Surface 被创建或调用 requestRender() 时渲染// RENDERMODE_CONTINUOUSLY 会每隔一段时间自动渲染renderMode RENDERMODE_WHEN_DIRTY}
}再声明自定义渲染器 GLRender 继承 GLSurfaceView.Render
class GLRender : GLSurfaceView.Renderer {// GLSurfaceView.Renderer startoverride fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {// 将画布清空为黑色GLES20.glClearColor(0f, 0f, 0f, 0f)}override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {}override fun onDrawFrame(gl: GL10?) {}// GLSurfaceView.Renderer end
}3.3 初始化三角形
我们新建一个 Triangle 来处理三角形的绘制并且在 GLRender 绘画回调时将回调同步给 Triangle
class GLRender : GLSurfaceView.Renderer {override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {// 将画布清空为黑色GLES20.glClearColor(0f, 0f, 0f, 0f)triangle Triangle()}// 当外界调用 requestRender 时会触发本方法回调// 类似于 invalidate 与 onDraw 的关系override fun onDrawFrame(gl: GL10?) {GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)// 具体的渲染工作交给 Triangle 对象triangle.onDrawFrame(gl)}
}接下来要确定被绘制的三角形是什么样的。比如我想画一个在 z 0 这个平面的等腰直角三角形如下图所示 那么该三角形的顶点数组声明如下 companion object {val triangleCoordinates floatArrayOf(0.5f, 0.5f, 0.0f,-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f)}接下来需要创建顶点着色器和片元着色器了。首先我们在 AS 中安装 GLSL Support 插件可以对 GLSL 进行高亮提示。然后在 /res/raw/ 目录下新建顶点着色器文件 vertex_shader.glsl
// attribute 声明了包含四个浮点数的向量 vPosition
attribute vec4 vPosition;
// main 是着色器程序的入口点
void main() {// gl_Position 是语言内置变量代表了顶点的位置gl_PositionvPosition;
}然后是片元着色器 fragment_shader.glsl
precision mediump float;
uniform vec4 vColor;
void main() {// 将颜色向量赋值给内置变量OpenGL 就会根据该颜色绘制gl_FragColorvColor;
}接下来你可以将两个着色器的代码声明为字符串变量或者通过 IO 流读取文件代码到字符串。这里为了方便我们采用第一种方式后续 Demo 会采用第二种方式 private val vertexShaderCode attribute vec4 vPosition;\n void main() {\n gl_PositionvPosition;\n }private val fragmentShaderCode precision mediump float;\n uniform vec4 vColor;\n void main() {\n gl_FragColorvColor;\n }最后在 Triangle 的构造方法中做如下的初始化工作 private var vertexBuffer: FloatBufferprivate var program 0init {// 在 GPU 中申请内存三角形有 3 个顶点每个顶点有 3 个维度都是 4 个字节的 float 类型val byteBuffer ByteBuffer.allocateDirect(triangleCoordinates.size * 4)// ByteBuffer 内元素排列顺序采用 Native 顺序byteBuffer.order(ByteOrder.nativeOrder())// GL 语言代码需要通过 vertexBuffer 传入 GPUvertexBuffer byteBuffer.asFloatBuffer()vertexBuffer.put(triangleCoordinates)vertexBuffer.position(0)// 创建顶点着色器并在 GPU 中进行编译val vertexShader: Int GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)GLES20.glShaderSource(vertexShader, vertexShaderCode)GLES20.glCompileShader(vertexShader)// 创建片元着色器并在 GPU 中进行编译val fragmentShader: Int GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER)GLES20.glShaderSource(fragmentShader, fragmentShaderCode)GLES20.glCompileShader(fragmentShader)// 创建 Program 并绑定两个着色器program GLES20.glCreateProgram()GLES20.glAttachShader(program, vertexShader)GLES20.glAttachShader(program, fragmentShader)// 连接到着色器程序GLES20.glLinkProgram(program)}3.4 绘制三角形
最后在 GLRender 传入的绘制回调方法 onDrawFrame() 中绘制三角形 private val colors floatArrayOf(1.0f, 1.0f, 1.0f, 1.0f)fun onDrawFrame(gl: GL10?) {// 渲染GLES20.glUseProgram(program)// 获取 GPU 中 vPosition 变量的地址实际上是一个 Native 指针val positionHandle GLES20.glGetAttribLocation(program, vPosition)// 开启允许对变量读写与关闭方法成对出现GLES20.glEnableVertexAttribArray(positionHandle)// 变量地址数据尺寸数据类型是否正常行跨度偏移GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, vertexBuffer)val colorHandle GLES20.glGetUniformLocation(program, vColor)GLES20.glUniform4fv(colorHandle, 1, colors, 0)// 让 OpenGL 绘制三角形GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3)// 关闭对变量读写GLES20.glDisableVertexAttribArray(positionHandle)}实际上就是把实际的顶点坐标 vertexBuffer 赋值给 GPU 中的 vPosition 变量将颜色数组写入到 GPU 的 vColor 变量。
运行程序确实画出了一个直角三角形但是却不是我们想要的等腰直角三角形 原因是 OpenGL 绘制的是三维图形将三维图形绘制到 Android 屏幕这个二维平面上是需要进行投影的。投影的目的是将三维物体的空间位置和形状转换为屏幕上的二维图像使观察者可以从适当的视角观察和理解物体。在图形渲染过程中投影通常是在三维物体的模型空间或世界空间与屏幕空间之间进行的转换。
投影方法主要有两种
透视投影Perspective Projection透视投影模拟了人眼或相机在观察场景时的视角效果。它产生了近大远小的效果使离观察者较远的物体显得较小。透视投影常用于创建逼真的三维效果在虚拟现实、游戏和模拟等领域广泛应用。正交投影Orthographic Projection正交投影是一种平行投影保持了物体在不同距离上的大小不变。它独立于观察者的位置和角度适用于需要保持物体大小和形状一致的场景如工程制图、CAD 应用和二维游戏等。
投影过程涉及到三个要素相机观察物体的位置、被观察的物体与投影面。
为了让最终看到的是等腰直角三角形我们需要对顶点着色器中的顶点进行变换变换方式就是对其左乘矩阵
attribute vec4 vPosition;
uniform mat4 vMatrix;
void main() {gl_PositionvMatrix*vPosition;
}现在问题转移到 vMatrix 是如何计算的。首先我们让 GLRender 将 Surface 的变化同步给 Triangle override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {triangle.onSurfaceChanged(gl, width, height)}在 Triangle 的 onSurfaceChanged() 中设置投影矩阵、相机矩阵并计算出变换矩阵 private val viewMatrix FloatArray(16)private val projectMatrix FloatArray(16)private val mvpMatrix FloatArray(16)fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {// 计算宽高比val ratio: Float width.toFloat() / height// 设置投影矩阵Matrix.frustumM(projectMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 120f)// 计算相机矩阵Matrix.setLookAtM(viewMatrix, 0,0f, 0f, 7f, // 摄像机的坐标0f, 0f, 0f, // 目标物的中心坐标0f, 1f, 0f // 相机看目标物的方向)// 计算变换矩阵将 projectMatrix 与 viewMatrix 相乘Matrix.multiplyMM(mvpMatrix, 0, projectMatrix, 0, viewMatrix, 0)}更新顶点着色器字符串并向 GPU 传入刚刚计算出的变换矩阵 private val vertexShaderCode attribute vec4 vPosition;\n uniform mat4 vMatrix;\n void main() {\n gl_PositionvMatrix*vPosition;\n }fun onDrawFrame(gl: GL10?) {// 渲染GLES20.glUseProgram(program)// 获取 GPU 中 vMatrix 变量的地址并将 mvpMatrix 赋值给 vMatrixval matrixHandle GLES20.glGetUniformLocation(program, vMatrix)GLES20.glUniformMatrix4fv(matrixHandle, 1, false, mvpMatrix, 0)// 获取 GPU 中 vPosition 变量的地址实际上是一个 Native 指针val positionHandle GLES20.glGetAttribLocation(program, vPosition)// 开启允许对变量读写与关闭方法成对出现GLES20.glEnableVertexAttribArray(positionHandle)// 变量地址数据尺寸数据类型是否正常行跨度偏移GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, vertexBuffer)val colorHandle GLES20.glGetUniformLocation(program, vColor)GLES20.glUniform4fv(colorHandle, 1, colors, 0)GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3)// 关闭对变量读写GLES20.glDisableVertexAttribArray(positionHandle)}这样就能画出一个等腰直角三角形 最后总结一下绘制步骤
创建顶点数组创建顶点着色器和片元着色器将上层定义的顶点数组和颜色数组通过 OpenGL ES 提供的 API 接口传递给 GL 语言的变量
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/914095.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!