LearnOpenGL - Android OpenGL ES 3.0 绘制纹理

系列文章目录

  • LearnOpenGL 笔记 - 入门 01 OpenGL
  • LearnOpenGL 笔记 - 入门 02 创建窗口
  • LearnOpenGL 笔记 - 入门 03 你好,窗口
  • LearnOpenGL 笔记 - 入门 04 你好,三角形
  • OpenGL - 如何理解 VAO 与 VBO 之间的关系
  • LearnOpenGL - Android OpenGL ES 3.0 绘制三角形

文章目录

  • 系列文章目录
  • 一、前言
  • 二、数据流:顶点着色器到片元着色器
    • 2.1 顶点着色器
      • 直观解释
      • 示例
      • 执行流程
    • 2.2 片元着色器
      • 片元着色器执行次数
      • 示例
      • 片元着色器代码示例
      • 渲染流程
      • 总结
    • 2.3 顶点着色器到片元着色器
  • 三、纹理绘制流程
    • 3.1 顶点着色器
    • 3.2 片元着色器
    • 3.3 验证着色器
    • 3.4 代码编写
      • `prepare`函数
        • 1. 编译着色器
        • 2. 准备顶点缓冲区
        • 3. 生成VAO、VBO和EBO
        • 4. 绑定并设置VAO
        • 5. 设置VBO数据
        • 6. 设置EBO数据
        • 7. 设置VAO属性
        • 8. 解绑VAO
        • 9. 准备纹理
        • 10. 设置纹理过滤
        • 11. 设置纹理图像数据
        • 12. 解绑纹理
        • 13. 使用着色器程序并设置纹理位置
      • `draw`函数
        • 1. 清除颜色缓冲区
        • 2. 绑定VAO
        • 3. 激活并绑定纹理
        • 4. 绘制元素
        • 5. 解绑VAO
  • 四、其他问题
  • 4.1 为什么图片填充至纹理后是颠倒的
  • 参考


一、前言

在 LearnOpenGL - Android OpenGL ES 3.0 绘制三角形 中我们学会了如何在 Android 下搭建 GLES 环境,并绘制三角形。本文我们将讨论如何绘制纹理。
本文代码在 TextureDrawer

二、数据流:顶点着色器到片元着色器

在进入具体的代码细节先,我想说明顶点着色器与片元着色器是如何工作的,它们之间是如何联系在一起的,它们的输入和输出分别是什么。

2.1 顶点着色器

顶点着色器的输入是顶点属性。这些属性通常包括顶点的位置、颜色、法线、纹理坐标等。顶点着色器处理这些输入并生成顶点的输出,这些输出通常会被传递给片元着色器。、

如果有 4 个顶点,那么顶点着色器会被执行 4 次。每次执行时,顶点着色器都会处理一个顶点的属性,并生成对应的输出。下面是一个更详细的解释:

直观解释

  1. 顶点数据: 你定义了一组顶点数据。例如,假设你有一个包含 4 个顶点的简单模型,每个顶点都有位置和颜色属性。

  2. 顶点着色器执行:

    • 当你开始渲染这个模型时,渲染管线会将顶点数据传递给顶点着色器。
    • 顶点着色器会为每个顶点单独执行一次。因此,如果有 4 个顶点,顶点着色器会执行 4 次。
    • 每次执行时,顶点着色器都会读取一个顶点的属性数据(例如位置和颜色),进行必要的处理(例如变换位置,传递颜色),并生成对应的输出。

示例

假设你有以下顶点数据(每个顶点包含位置和颜色):

GLfloat vertices[] = {
    // 位置        // 颜色
    0.0f,  0.5f, 0.0f,  1.0f, 0.0f, 0.0f, 1.0f, // 顶点 1: 红色
    -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f, 1.0f, // 顶点 2: 绿色
    0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f, 1.0f, // 顶点 3: 蓝色
    0.5f,  0.5f, 0.0f,  1.0f, 1.0f, 0.0f, 1.0f  // 顶点 4: 黄色
};

对于这些顶点数据,顶点着色器类似于以下内容:

#version 300 es
layout(location = 0) in vec3 aPosition; // 顶点位置
layout(location = 1) in vec4 aColor;    // 顶点颜色

out vec4 vColor; // 传递给片元着色器的输出变量

void main()
{
    gl_Position = vec4(aPosition, 1.0); // 将顶点位置转换为齐次坐标
    vColor = aColor; // 将顶点颜色传递给片元着色器
}

执行流程

  • 第一次执行: 处理第一个顶点,输入 aPosition = vec3(0.0, 0.5, 0.0)aColor = vec4(1.0, 0.0, 0.0, 1.0)
  • 第二次执行: 处理第二个顶点,输入 aPosition = vec3(-0.5, -0.5, 0.0)aColor = vec4(0.0, 1.0, 0.0, 1.0)
  • 第三次执行: 处理第三个顶点,输入 aPosition = vec3(0.5, -0.5, 0.0)aColor = vec4(0.0, 0.0, 1.0, 1.0)
  • 第四次执行: 处理第四个顶点,输入 aPosition = vec3(0.5, 0.5, 0.0)aColor = vec4(1.0, 1.0, 0.0, 1.0)

每次执行时,顶点着色器根据输入的顶点属性计算输出的 gl_PositionvColor。这些输出随后传递给后续的渲染管线阶段,例如图元装配、光栅化和片元着色器。

2.2 片元着色器

片元着色器的执行次数取决于最终渲染到屏幕上的像素数量,而不是输入的顶点数量。片元着色器会为每个生成的片元(像素)执行一次。具体执行次数取决于渲染的几何图形覆盖的屏幕区域。以下是详细的解释:

片元着色器执行次数

  1. 图元的类型:

    • 图元类型可以是点、线、三角形等。
    • 通常情况下,渲染的是三角形。
  2. 图元覆盖的屏幕区域:

    • 图元被光栅化(rasterized)成片元。
    • 每个片元对应屏幕上的一个像素。
  3. 片元着色器执行:

    • 片元着色器会为每个片元(像素)执行一次。
    • 如果一个三角形覆盖了 100 个像素,片元着色器就会执行 100 次。

示例

假设我们有一个包含 4 个顶点的四边形,由两个三角形组成,渲染到屏幕上覆盖一定区域。

GLfloat vertices[] = {
    // 位置         // 颜色
    -0.5f,  0.5f, 0.0f,  1.0f, 0.0f, 0.0f, 1.0f, // 顶点 1: 红色
    -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f, 1.0f, // 顶点 2: 绿色
     0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f, 1.0f, // 顶点 3: 蓝色
     0.5f,  0.5f, 0.0f,  1.0f, 1.0f, 0.0f, 1.0f  // 顶点 4: 黄色
};

GLuint indices[] = {
    0, 1, 2, // 第一个三角形
    0, 2, 3  // 第二个三角形
};

片元着色器代码示例

#version 300 es
precision mediump float;

in vec4 vColor; // 从顶点着色器传递过来的颜色
out vec4 FragColor; // 输出的片元颜色

void main()
{
    FragColor = vColor; // 将颜色输出
}

渲染流程

  1. 顶点着色器阶段:

    • 对每个顶点执行一次,共执行 4 次。
    • 顶点着色器输出顶点的齐次坐标和颜色。
  2. 图元装配和光栅化阶段:

    • 将顶点装配成两个三角形。
    • 将两个三角形转换为片元(像素)。
  3. 片元着色器阶段:

    • 每个片元调用一次片元着色器。
    • 如果两个三角形覆盖了 200 个像素,片元着色器会执行 200 次。

总结

  • 顶点着色器: 执行次数与顶点数量相同,每个顶点执行一次。
  • 片元着色器: 执行次数与片元(像素)数量相同,每个片元执行一次。

因此,片元着色器的执行次数通常远多于顶点着色器的执行次数,因为片元数量通常大于顶点数量。具体执行次数取决于渲染的几何图形在屏幕上的覆盖范围。

2.3 顶点着色器到片元着色器

用 WebGL编程指南 中一张图片来总结上面提到的内容
在这里插入图片描述
在这里插入图片描述

三、纹理绘制流程

先从着色器出发,思考如何编写着色器,然后根据着色器来编写 OpenGL ES 代码

3.1 顶点着色器

输入:为了绘制一张矩形的图片,我们需要 4 个顶点,每个顶点的属性包括顶点位置和纹理坐标。因此,需要定义两个输入。
输出:将纹理坐标传递给片元着色器,以便在片元着色器中进行纹理采样和其他操作。因此需要定义一个输出。

#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texcoord;
out vec2 v_texcoord;
void main()
{
    gl_Position = a_position;
    v_texcoord = a_texcoord;
}

3.2 片元着色器

输入:绘制纹理,那么自然需要一张纹理;此外,还有纹理坐标(已经被插值过了)
输出:片元的颜色

#version 300 es
uniform sampler2D texture0;
in vec2 v_texcoord;
out vec4 fragColor;
void main(void)
{
    fragColor = texture(texture0, v_texcoord);
}

3.3 验证着色器

在编写 OpenGL ES 代码前,让我们先验证下 shader 是否正确,这里推荐使用 KodeLife,它支持顶点着色器和片元着色器,网上很多在线的 shader 网站只支持片元着色器。

本人在 Mac 运行 KodeLife ,想要运行上面代码需要做一些修改,你需要将 version 信息修改为 “#version 330”,接着在 KodeLife 上导入一张纹理即可。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到我们的 shader 能够正常的渲染出图片,只是图片颠倒了,但这个问题可以在 Android 加载图片的时候进行处理。

3.4 代码编写

完整代码在 TextureDrawer

代码分为两个部分:prepare函数和draw函数。

prepare函数

prepare函数用于初始化和准备所有需要的OpenGL资源,包括着色器、VAO、VBO、EBO以及纹理。

1. 编译着色器
sharer.prepareShaders()
checkGlError("compile shader")

调用sharer.prepareShaders()编译着色器,并使用checkGlError检查是否有任何OpenGL错误。

2. 准备顶点缓冲区
val vertexBuffer = ByteBuffer
    .allocateDirect(vertices.size * Float.SIZE_BYTES)
    .order(ByteOrder.nativeOrder())
    .asFloatBuffer()
    .apply {
        put(vertices)
        position(0)
    }

创建一个直接字节缓冲区,并将顶点数据放入缓冲区中。

3. 生成VAO、VBO和EBO
GLES30.glGenVertexArrays(1, vaos)
GLES30.glGenBuffers(1, vbos)
GLES30.glGenBuffers(1, ebo)
checkGlError("gen vertex array and buffer")

生成一个VAO,一个VBO和一个EBO,并检查是否有任何OpenGL错误。

4. 绑定并设置VAO
GLES30.glBindVertexArray(vaos[0])

绑定生成的VAO。

5. 设置VBO数据
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbos[0])
GLES30.glBufferData(
    GLES30.GL_ARRAY_BUFFER,
    Float.SIZE_BYTES * vertices.size,
    vertexBuffer,
    GLES30.GL_STATIC_DRAW
)
checkGlError("glBufferData")

绑定VBO并将顶点数据传递给缓冲区。

6. 设置EBO数据
val indexBuffer = ByteBuffer
    .allocateDirect(indices.size * Int.SIZE_BYTES)
    .order(ByteOrder.nativeOrder())
    .asIntBuffer()
    .apply {
        put(indices)
        position(0)
    }
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ebo[0])
GLES30.glBufferData(
    GLES30.GL_ELEMENT_ARRAY_BUFFER,
    Int.SIZE_BYTES * indices.size,
    indexBuffer,
    GLES30.GL_STATIC_DRAW
)
checkGlError("glBufferData for indices")

创建索引缓冲区并将索引数据传递给EBO。

7. 设置VAO属性
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 5 * Float.SIZE_BYTES, 0)
GLES30.glEnableVertexAttribArray(0)
GLES30.glVertexAttribPointer(
    1,
    2,
    GLES30.GL_FLOAT,
    false,
    5 * Float.SIZE_BYTES,
    3 * Float.SIZE_BYTES
)
GLES30.glEnableVertexAttribArray(1)

设置顶点属性指针和启用顶点属性。这里有两个属性:位置(3个float)和纹理坐标(2个float)。

8. 解绑VAO
GLES30.glBindVertexArray(0)

解绑VAO。

9. 准备纹理
GLES30.glGenTextures(texIds.capacity(), texIds)
checkGlError("glGenTextures")
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texIds[0])
checkGlError("glBindTexture")

生成纹理ID并绑定纹理。

10. 设置纹理过滤
GLES30.glTexParameteri(
    GLES30.GL_TEXTURE_2D,
    GLES30.GL_TEXTURE_MIN_FILTER,
    GLES30.GL_NEAREST
)
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR)
checkGlError("glTexParameteri")

设置纹理过滤参数。

11. 设置纹理图像数据
val options = BitmapFactory.Options()
options.inScaled = false
var bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.lye, options)
val matrix = android.graphics.Matrix()
matrix.preScale(1.0f, -1.0f)
bitmap = android.graphics.Bitmap.createBitmap(
    bitmap,
    0,
    0,
    bitmap.width,
    bitmap.height,
    matrix,
    false
)
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0)
checkGlError("texImage2D")
bitmap.recycle()

解码资源中的图片,并垂直翻转,然后将图像数据传递给纹理。

12. 解绑纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)

解绑纹理。

13. 使用着色器程序并设置纹理位置
sharer.use()
sharer.setInt("texture1", 0)

使用着色器程序,并设置纹理单元。

draw函数

draw函数用于实际绘制帧。

1. 清除颜色缓冲区
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

清除颜色缓冲区。

2. 绑定VAO
GLES30.glBindVertexArray(vaos[0])

绑定VAO。

3. 激活并绑定纹理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texIds[0])

激活纹理单元并绑定纹理。

4. 绘制元素
GLES30.glDrawElements(GLES30.GL_TRIANGLES, indices.size, GLES30.GL_UNSIGNED_INT, 0)

绘制元素。

5. 解绑VAO
GLES30.glBindVertexArray(0)

解绑VAO。

四、其他问题

4.1 为什么图片填充至纹理后是颠倒的

我是这么想的,图片填充时使用的内存起始地址是图片的左上角,然后一行一行地从上到下将数据拷贝到 GPU 纹理上,而纹理的起始地址是右下角,因为这种差异导致了颠倒。
在这里插入图片描述
因此为了让图片正确显示,我们可以

  1. 在图像加载阶段进行调整:在将图像数据传递给OpenGL之前,将图像上下翻转。这种方法在代码中已经实现,通过Matrix.preScale(1.0f, -1.0f)完成。
// Flip the bitmap vertically
val matrix = android.graphics.Matrix()
matrix.preScale(1.0f, -1.0f)
bitmap = android.graphics.Bitmap.createBitmap(
    bitmap,
    0,
    0,
    bitmap.width,
    bitmap.height,
    matrix,
    false
)
  1. 在着色器阶段进行调整:在顶点着色器或片段着色器中,翻转纹理坐标。
#version 300 es

layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texcoord;

out vec2 v_texcoord;

void main()
{
    gl_Position = a_position;
    v_texcoord = vec2(a_texcoord.x, 1.0 - a_texcoord.y); // Flip y axis
}

参考

  • NDK OpenGLES 3.0 开发(二):纹理映射

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/729057.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ATA-4051C高压功率放大器在压电电机中的作用是什么

压电电机是一种特殊的电机,其工作原理基于压电效应,这是一种将电能转化为机械振动的现象。压电电机通常用于精密定位、振动控制和声波生成等应用。为了驱动和控制压电电机,需要高压功率放大器。下面将介绍高压功率放大器在压电电机中的作用&a…

外贸SEO工具有哪些推荐?

"我们作为一个专业的Google SEO团队,比较推荐一下几个适合外贸SEO的工具。Ahrefs 是一个非常强大的工具,可以帮助你深入分析竞争对手的表现,找到有潜力的关键词,还可以监控你的网站链接状况。另外,SEMrush 也很不…

ubuntu访问windows共享文件夹

方法: Ubuntu访问Windows共享文件夹的方法-CSDN博客 基于交换机的PC端网络通信_服务器交换机pc端-CSDN博客 补充说明: 在这里面输入: smb://192.168.0.30/WindowsShareToLinux

NGINX_二十 nginx 监控

二十 nginx 监控 1 nginx的基础监控 进程监控端口监控 注意: 这两个是必须要加在zabbix监控,加触发器有问题及时告警。 web 服务器 nginx 以其高性能与抗并发能力越来越多的被用户使用 作为一款服务器产品,其运行状态是运维密切关注的&a…

解决IDEA使用卡顿的问题,设置JVM内存大小和清理缓存

解决IntelliJ IDEA中卡顿问题,可以尝试以下几个常见且有效的步骤: 1 增加IDEA的JVM内存分配: 位于IDEA安装目录的bin文件夹下,找到对应的操作系统配置文件(idea64.exe.vmoptions(Windows)或id…

数学建模理论学习:线性规划模型

三要素:目标函数、约束条件(s.t.)、决策变量(x) 目标函数:z ax1 bx2 cx3 ... 其中c为一个序列,从左到右依次从x1到xn的系数 解决下面的线性规划问题: % 目标函数系数&#xf…

信息打点web篇----web后端源码专项收集

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 专栏描述:因为第一遍过信息收集的时候,没怎么把收集做回事 导致后来在实战中,遭遇资产获取少,可渗透点少的痛苦,如今决定 从头来过,全面全方位…

AI创作在论文写作中扮演什么角色?

近年来,随着科技的快速发展,AI已经逐渐渗透到了生活中的方方面面,其中也包含着学术领域。 作为学生党,你是否还在为期末论文,大学生实践报告而发愁? 有了这些AI写作神器,大学生们再也不用在期…

实验一:Ubuntu系统中的USB设备绑定实验

实验一:Ubuntu系统中的USB设备绑定实验 一、实验目的二、实验原理三、实验环境四、实验步骤任务 1:绑定不同USB设备任务 2:绑定多个相同设ID的串口设备 五、注意事项六、拓展练习 一、实验目的 学习Ubuntu中USB设备命名方法;掌握…

北航数据结构与程序设计图部分选填题

一、 抓两个关键信息:无向图,邻接表。无向图中,边(vi,vj)要在vi的链表中记录一次,再以(vj,vi)的形式在vj的链表中记录一次。 每个边都要记录两次&#xff0c…

Ubuntu安装qemu-guest-agent

系列文章目录 Ubuntu-24.04-live-server-amd64安装界面中文版 Ubuntu-24.04-live-server-amd64启用ssh Ubuntu乌班图安装VIM文本编辑器工具 文章目录 系列文章目录前言一、安装二、启用服务三、效果总结 前言 QEMU Guest Agent(简称QEMU GA或QGA)在虚拟…

基于 Arm 虚拟硬件的心电图(ECG)疾病分类应用开发实战

基于 Arm 虚拟硬件的心电图(ECG)疾病分类应用开发实战 目录 文章目录 [toc] 一、实验背景1. 嵌入式软件开发的基本流程2. Arm 虚拟硬件镜像产品简介 二、实验目标三、实验步骤简介四、实验前准备1. 订阅使用 Arm 虚拟硬件镜像的百度智能云云服务器 BCC 实例1.1 百度智能云账号准…

Ubuntu 安装 CloudCompare

步骤: sudo apt install flatpakflatpak install flathub org.cloudcompare.CloudCompare此时会有报错: error: No remote refs found similar to ‘flathub’执行 flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.fla…

[深度学习]--分类问题的排查错误的流程

原因复现: 原生的.pt 好使, 转化后的 CoreML不好使, 分类有问题。 yolov8 格式的支持情况 Format Argument Suffix CPU GPU 0 PyTorch - .pt True True 1 Tor…

STM32多种开发环境及生成hex及bin文件介绍

一.STM32开发环境 KEIL系列 (1)KEIL公司目前有四款独立的嵌入式软件开发工具,即MDK、KEIL C51、KEIL C166、KEIL C251,它们都是KEIL公司品牌下的产品,都基于uVision集成开发环境,其中MDK是RealView系列中…

初阶 《数组》 3. 数组越界

3. 数组越界 数组的下标是有范围限制的。 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。 所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。 C语言本身…

在Mac上恢复丢失或未保存的Word文档的5种有效方法

“救命!我想在Mac上恢复丢失的Word文档。就在 1 小时前,我错误地删除了它们,并清空了垃圾桶。这些Word文档对我来说非常重要。我不知道如何恢复它们,谁能帮我?提前致谢! 没有什么比忘记保存 Word 文档或在…

openEuler搭建hadoop 伪分布式集群

openEuler搭建hadoop 伪分布式集群 Mode 伪分布式 hadoop101hadoop102hadoop103192.168.10.101192.168.10.102192.168.10.103namenodesecondary namenoderecource managerdatanodedatanodedatanodenodemanagernodemanagernodemanagerjob historyjob logjob logjob log 升级软…

C语言之顺序结构以及程序调试的debug宏

一:C语言中的顺序结构 1:最浅显的顺序结构理解:三种结构之一 (1)代码执行的时候没有遇到判断跳转或者循环,默认是顺序执行的。执行完上一句则开始执行下一句。 (2)顺序结构说明cpu的工作状态&a…

springboot基于Web的社区医院管理服务系统 LW+ PPT+源码+讲解

3系统需求分析 3.1系统功能 通过前面的功能分析可以将社区医院管理服务系统的功能分为管理员、用户和医生三个部分,系统的主要功能包括首页、个人中心、用户管理、医生管理、预约医生管理、就诊信息管理、诊疗方案管理、病历信息管理、健康档案管理、费用信息管理…