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

Android OpenGLES2.0开发(三):绘制一个三角形

我们总是对陌生人太客气,而对亲密的人太苛刻

上一篇文章中,我们已经将OpenGL ES环境搭建完成。接下来我们就可以开始我们的绘图之旅了。该篇我们讲解最基本图形三角形的绘制,这是一切绘制的基础。在OpenGL ES的世界里一切图形都可以由三角形拼接绘制而成。

在Android官方文档中也介绍了三角形的绘制,本文案例我们参照了官方文档,但也做了进一步的改进,希望你能通过本节对OpenGL ES绘制图形有一个初步的认识。

坐标系

在绘制图形前我们要了解OpenGL ES的坐标系,我们知道Android的坐标系左上角为原点[0,0],而OpenGL ES的坐标系如下图:
请添加图片描述

OpenGL ES的坐标系中原点[0,0]在屏幕的中心,无论屏幕是正方形还是长方形,四个点的坐标都如上图所示,也就是边长都是为2的正方形

聪明的同学可能已经有疑问了,OpenGL ES坐标系明显是按照屏幕是正方形来的,那么如果屏幕为长方形,我按照比例绘制图形肯定会变形的。这里给出肯定的回答:是的!至于怎么解决先放下。

接下来我们先画一个三角形,至于遇到的问题,我们一个个解决^_^

三角形绘制

  1. 设置要绘制图形的坐标和颜色数据
  2. 定义顶点着色器片段着色器
  3. 创建Shader程序并链接编译好
  4. 绘制图形:使用Shader程序,将顶点、颜色数据传递到显存

1. 顶点颜色数据定义

在OpenGL ES中绘制图形必须先定义好坐标,确定图形的位置才能进行绘制。我们为坐标定义浮点数的顶点数组,然后我在构造方法中将浮点数组转化为ByteBuffer,后续我们会将它传递到OpenGL ES图像管道进行处理。

public class Triangle {// 顶点坐标缓冲区private FloatBuffer vertexBuffer;// 此数组中每个顶点的坐标数static final int COORDS_PER_VERTEX = 3;// 三角形三个点的坐标,逆时针绘制static float triangleCoords[] = {   // 坐标逆时针顺序0.0f, 0.616f, 0.0f, // top-0.5f, -0.25f, 0.0f, // bottom left0.5f, -0.25f, 0.0f  // bottom right};// 设置颜色为红色float color[] = {1.0f, 0.0f, 0.0f, 1.0f};public Triangle() {// 初始化形状坐标的顶点字节缓冲区ByteBuffer bb = ByteBuffer.allocateDirect(// (number of coordinate values * 4 bytes per float)triangleCoords.length * 4);// use the device hardware's native byte orderbb.order(ByteOrder.nativeOrder());// create a floating point buffer from the ByteBuffervertexBuffer = bb.asFloatBuffer();// add the coordinates to the FloatBuffervertexBuffer.put(triangleCoords);// set the buffer to read the first coordinatevertexBuffer.position(0);}
}

根据代码中定义的三角形的坐标,我们大概画出的三角形如下图,应该是一个等边三角形
在这里插入图片描述

形状面和环绕

在 OpenGL 中,形状的面是由三维或更多点定义的三维表面 空间。一组三个或更多个三维点(在 OpenGL 中称为顶点)具有一个正面以及一个背面。如何知道哪一面为正面,哪一面为背面呢?这个问题问得好! 答案与环绕或者定义形状点的方向有关

请添加图片描述
在此示例中,三角形的点按如上顺序定义,这也决定了他们是按逆时针的方向绘制,绘制这些坐标的顺序定义了形状的环绕方向。默认情况下,在OpenGL中,逆时针绘制的面是正面,而另外一面是背面

为什么重要的是要知道形状的哪个面是正面?答案与OpenGL的常用功能有关,称为面部剔除。面部剔除是OpenGL环境的一个选项,它允许渲染管道忽略(不计算或绘制)形状的背面,从而节省时间,内存和处理周期:

// enable face culling feature
gl.glEnable(GL10.GL_CULL_FACE);
// specify which faces to not draw
gl.glCullFace(GL10.GL_BACK);

请务必按照逆时针绘制顺序定义 OpenGL 形状的坐标

2. 创建着色器代码

public class Triangle {// 顶点着色器代码private final String vertexShaderCode ="attribute vec4 vPosition;\n" +"void main() {\n" +"  gl_Position = vPosition;\n" +"}\n";// 片段着色器代码private final String fragmentShaderCode ="precision mediump float;\n" +"uniform vec4 vColor;\n" +"void main() {\n" +"  gl_FragColor = vColor;\n" +"}\n";...
}
变量名说明备注
vPosition顶点坐标数据,我们第一步定义的顶点数据需要传给这个变量该变量名可随意修改
gl_PositionShader的内置变量,就是图形的顶点位置该变量名不可修改
vColor图元(像素)的颜色,我们第一步定义的颜色需要传给这个变量该变量名可随意修改
gl_FragColorShader的内置变量,图元颜色该变量名不可修改

上面先简单介绍下变量的含义,后续我们会详细讲解GLSL语言。

3. 创建Shader程序并链接

我们需要对上面的着色器语言进行编译链接后,才能在OpenGL ES环境中使用。编译此代码,我们需要一个实用的方法:

public class GLESUtils {/*** 加载着色器代码** @param type* @param shaderCode* @return*/public static int loadShader(int type, String shaderCode) {// create a vertex shader type (GLES20.GL_VERTEX_SHADER)// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)int shader = GLES20.glCreateShader(type);// add the source code to the shader and compile itGLES20.glShaderSource(shader, shaderCode);GLES20.glCompileShader(shader);return shader;}
}

为了匹配Renderer生命周期方法onSurfaceCreated,我们在Triangle中创建surfaceCreated方法用来编译链接Shader程序,创建surfaceChanged来更新OpenGL ES画布大小

public class Triangle() {.../*** OpenGL ES程序句柄*/private int mProgram;public void surfaceCreated() {// 加载顶点着色器代码int vertexShader = GLESUtils.loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);// 加载片段着色器代码int fragmentShader = GLESUtils.loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);// 创建空的OpenGL ES程序mProgram = GLES20.glCreateProgram();// 将顶点着色器添加到程序中GLES20.glAttachShader(mProgram, vertexShader);// 将片段着色器添加到程序中GLES20.glAttachShader(mProgram, fragmentShader);// 链接OpenGL ES程序GLES20.glLinkProgram(mProgram);}public void surfaceChanged(int width, int height) {// 设置OpenGL ES画布大小GLES20.glViewport(0, 0, width, height);}
}

4. 绘制图形

此时我们已经定义好了三角形的顶点坐标数据、片元颜色值、OpenGL ES着色器程序。接下来我们只需要将坐标数据、片元颜色值传递给着色器程序,然后执行绘制我们就可以将三角形画出来了。我们定义一个单独的方法draw用来执行绘制三角形

4.1 将Shader程序添加到OpenGL ES环境

public class Triangle() {public void draw() {// 将程序添加到OpenGL ES环境GLES20.glUseProgram(mProgram);// 重新绘制背景色为黑色GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);...}
}

4.2 传递顶点坐标和片元颜色数据

我们要从java内存将数据传递给OpenGL ES显存环境中,需要获取Shader程序属性的句柄值

注意:获取属性句柄要和Shader程序中定义的属性变量名字一样。获取属性句柄后,我们操作属性句柄就可以将数据传递给对应的变量了。

public class Triangle() {.../*** Shader程序中顶点属性的句柄*/private int positionHandle;/*** Shader程序中颜色属性的句柄*/private int colorHandle;public void surfaceCreated() {// 加载Shader程序代码...// 获取顶点着色器vPosition成员的句柄positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");// 获取片段着色器vColor成员的句柄colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");}
}

draw方法中设置顶点数据和颜色值

public class Triangle() {...private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertexpublic void draw() {// 将程序添加到OpenGL ES环境...// 为三角形顶点启用控制柄GLES20.glEnableVertexAttribArray(positionHandle);// 准备三角坐标数据GLES20.glVertexAttribPointer(positionHandle,     // 执行要配置的属性句柄(编号)COORDS_PER_VERTEX,  // 指定每个顶点属性的分量数GLES20.GL_FLOAT,    // 指定每个分量的数据类型false,              // 指定是否将数据归一化到 [0,1] 或 [-1,1] 范围内vertexStride,       // (步长)指定连续两个顶点属性间的字节数。如果为 0,则表示顶点属性是紧密排列的vertexBuffer        // 指向数据缓冲对象);// 设置绘制三角形的颜色GLES20.glUniform4fv(colorHandle, 1, color, 0);// 画三角形GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);// 禁用顶点阵列GLES20.glDisableVertexAttribArray(positionHandle);}
}

4.3 在GLSurfaceView中绘制三角形

以上我们已经将OpenGL ES绘制三角形的流程全部讲完,接下来我们只需要在GLSurfaceView中创建Triangle类并执行对应的方法

我们在上一篇中Android OpenGLES2.0开发(二):环境搭建已经搭建好了OpenGL ES环境,现在只需在Renderer接口中添加Triangle即可

public class TriangleGLSurfaceView extends GLSurfaceView {private Context mContext;private MyRenderer mMyRenderer;public TriangleGLSurfaceView(Context context) {super(context);init(context);}public TriangleGLSurfaceView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}private void init(Context context) {mContext = context;mMyRenderer = new MyRenderer();setEGLContextClientVersion(2);setRenderer(mMyRenderer);setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);}static class MyRenderer implements Renderer {Triangle mTriangle;public MyRenderer() {mTriangle = new Triangle();}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {mTriangle.surfaceCreated();}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {mTriangle.surfaceChanged(width, height);}@Overridepublic void onDrawFrame(GL10 gl) {mTriangle.draw();}}
}

将GLSurfaceView添加到布局中,运行程序我们可以看到绘制的效果如下图所示:
请添加图片描述
经过上面坐标系的讲解,我们应该能料想到绘制的结果并不是一个正三角形,至于原因我想大家也应该知道,至于怎么解决我们后续再讲

最后

希望你根据上面的步骤一步一步将代码敲出来,相信你肯定对OpenGL ES绘制有一个全面的了解。虽然只是绘制一个三角形,但是上面的代码基本上是后续一切绘制的基础,也算是一个模板代码,后续别的绘制基本上就是对上面的代码微调即可实现。

相关文章:

Android OpenGLES2.0开发(三):绘制一个三角形

我们总是对陌生人太客气,而对亲密的人太苛刻 上一篇文章中,我们已经将OpenGL ES环境搭建完成。接下来我们就可以开始我们的绘图之旅了。该篇我们讲解最基本图形三角形的绘制,这是一切绘制的基础。在OpenGL ES的世界里一切图形都可以由三角形拼…...

数据清洗的重要性与方法

在数据分析和机器学习的世界中,数据清洗是一个不可或缺的步骤。 它涉及到对原始数据进行处理,以便使其适合进一步的分析和建模。 数据清洗的重要性 提高数据质量 数据质量直接影响分析结果的准确性。 脏数据(包含错误、重复、不完整的数据&a…...

AI与大数据的结合:如何从海量数据中提取价值

引言 在当今数字化时代,数据如同新石油,成为推动社会与商业进步的重要资源。随着物联网、社交媒体和企业运营中数据生成的激增,我们正处在一个数据爆炸的时代。然而,面对海量且复杂的数据信息,仅依靠传统的分析方法已经…...

【漏洞复现】孚盟云oa AjaxSendDingdingMessage接口 存在sql注入漏洞

》》》产品描述《《《 孚盟与阿里强强联手将最受青睐的经典C系列产品打造成全新的孚盟云产品,让用户可以用云模式实现信息化管理,让用户的异地办公更加流畅,大大降低中小企业在信息化上成本,用最小的投入享受大型企业级别的信息化…...

【VUE】案例:商场会员管理系统

编写vuedfr实现对会员进行基本增删改查 1. drf项目初始化 请求: POST http://127/0.0.0.1:8000/api/auth/ {"username":"cqn", "password":"123"}返回: {"username":"cqn", "token&q…...

IDEA 最新版创建 Sping Boot 项目没有 JDK8 选项的解决方案

问题 今天新建一个 Java 项目写 demo 时,发现 Idea 上只能勾选 Java 17、21、23 三个版本 解决方案 IDEA 页面创建 Spring 项目,其实是访问 spring initializr 去创建项目。我们可以通过阿里云国服去间接创建 Spring 项目。服务器 URL 地址替换为 ht…...

Unity Asset Store的默认下载位置及更改下载路径的方法

修改Unity Asset Store的默认下载路径 Unity Asset Store默认下载位置 Unity Asset Store里下载资源,默认是下载到C盘里的,如果你不想做C盘战士的话,记得将下载的资源转移到其他盘。 Unity商城默认下载路径是C:\用户\用户名(一般…...

ArcEngine实现要素坐标转换:平移、缩放、旋转(批量处理)

在二维坐标系统中,常见转换坐标:平移、缩放、旋转。在ArcGIS中可以通过工具实现移动 、旋转 和缩放,具体操作如下: (1)移动要素:可通过指针或指定值以交互方式操作所选要素。移动要素&#xf…...

Redis: 主从复制原理

主从复制原理剖析 1 )配置 通过下面的从节点的配置项可以开启主从之间的复制功能slaveof 192.16.10.101 6379这里的复制包含全量复制和增量复制 2 )主节点的主从配置信息解析 查看主从之间的信息,在主节点上 $ info replication 打印出来的…...

PostgreSQL 向量扩展插件pgvector安装和使用

文章目录 PostgreSQL 向量扩展插件pgvector安装和使用安装postgresqlpgvector下载和安装安装错误调试错误调试1尝试解决 AP1 :启动postgresql 错误调试2尝试解决 AP2 : 使用apt-get install postgresql-server 错误调试3尝试解决 AP3 :卸载apt-get 安装 …...

【论文阅读】基于真实数据感知的模型功能窃取攻击

摘要 目的 模型功能窃取攻击是人工智能安全领域的核心问题之一,目的是利用有限的与目标模型有关的信息训练出性能接近的克隆模型,从而实现模型的功能窃取。针对此类问题,一类经典的工作是基于生成模型的方法,这类方法利用生成器…...

线程池:线程池的实现 | 日志

🌈个人主页: 南桥几晴秋 🌈C专栏: 南桥谈C 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据…...

海信和TCL雷鸟智能电视的体验

买了型号为32E2F(9008)的海信智能的电视有一段时间了,要使用这个智能电视还真能考验你的智商。海信电视有很多优点,它的屏幕比较靓丽,色彩好看,遥控器不用对着屏幕就能操作。但也有不少缺点。 1. 海信智能电视会强迫自动更新操作…...

自动化学习3:日志记录及测试报告的生成--自动化框架搭建

一.日志记录 1.配置文件pytest.ini:将日志写入文件方便日后查询或查看执行信息。 需要将文件处理器(文件存放位置/时间/格式等等)添加到配置文件中的【日志记录器】 # pytest.ini [pytest] # ---------------日志文件,需要配合…...

【STM32单片机_(HAL库)】4-1【定时器TIM】定时器中断点灯实验

1.硬件 STM32单片机最小系统LED灯模块 2.软件 timer驱动文件添加定时器HAL驱动层文件添加GPIO常用函数定时器中断配置流程main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "timer.h"int main(void) {H…...

Linux编译安装Mysql笔记

1.Mysql介绍 MySQL是一个广泛使用的开源关系型数据库管理系统(RDBMS),它基于SQL(Structured Query Language)进行操作。MySQL是由瑞典MySQL AB公司开发的,后来被Sun Microsystems收购,最终成为…...

在java后端发送HTTPClient请求

简介 HttpClient遵循http协议的客户端编程工具包支持最新的http协议 部分依赖自动传递依赖了HttpClient的jar包 明明项目中没有引入 HttpClient 的Maven坐标,但是却可以直接使用HttpClient原因是:阿里云的sdk依赖中传递依赖了HttpClient的jar包 发送get请…...

【STM32单片机_(HAL库)】4-3-2【定时器TIM】测量按键按下时间1——编程实现捕获功能

测量按键按下时长思路 测量按键按下时间实验目的 使用定时器 2 通道 2 来捕获按键 (按键接PA0)按下时间,并通过串口打印。 计一个数的时间:1us,PSC71,ARR65535 下降沿捕获、输入通道 2 映射在 TI2 上、不分…...

MySQL:2059 - Authentication plugin ‘caching_sha2_password‘ cannot be loaded

关于MySQL 客户端在尝试连接到 MySQL 服务器时报错:“2059 - Authentication plugin caching_sha2_password cannot be loaded”,具体是由于 MySQL 服务器默认使用的 caching_sha2_password 认证插件无法加载或不被当前客户端支持。 错误原因 MySQL 8.0…...

【JavaSE】反射、枚举、lambda表达式

目录 反射反射相关类获取类中属性相关方法常用获得类相关的方法示例常用获得类中属性相关的方法示例获得类中注解相关的方法 反射优缺点 枚举常用方法优缺点 枚举与反射lambda表达式语法函数式接口简化规则使用示例变量捕获集合中的应用优缺点 反射 Java的反射(refl…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

scikit-learn机器学习

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)

一、OpenBCI_GUI 项目概述 &#xff08;一&#xff09;项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台&#xff0c;其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言&#xff0c;首次接触 OpenBCI 设备时&#xff0c;往…...