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 eslayout(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 开发(二):纹理映射

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

有趣的 Oracle JDBC 驱动包命名问题 - ojdbc6 和 ojdbc14 哪个新?!

有趣的 Oracle JDBC 驱动包命名问题 - ojdbc6 和 ojdbc14 哪个新?! 1 背景概述 最近协助一个小兄弟排查了某作业使用 sqoop 采集 oracle 数据的失败问题,问题现象,问题原因和解决方法都挺直观,但在此过程中发现了一个有趣的 Oracle JDBC 驱…

特氟龙深水采样器FEP贝勒管水质取样器密封性好

贝勒管深水采样器(bailers tube),是一种经济型便携式水质采样器,操作简单,使用方便,性价比高,能大限度的保证样品的真实性。采样管直径很小,能够采取小口径的深水井水样。是一款简单实用,性价比高的水质采样…

C语言入门课程学习笔记8:变量的作用域递归函数宏定义交换变量

C语言入门课程学习笔记8 第36课 - 变量的作用域与生命期(上)第37课 - 变量的作用域与生命期(下)实验—局部变量的作用域实验-变量的生命期 第38课 - 函数专题练习第39课 - 递归函数简介实验-递归小结 第40课 - C 语言中的宏定义实…

C语言 while循环1

在C语言里有3种循环:while循环 do while 循环 for循环 while语句 //while语法结构 while(表达式)循环语句; 比如在屏幕上打印1-10 在while循环中 break用于永久的终止循环 在while循环中,continue的作用是跳过本次循环 …

如何安全进行亚马逊、沃尔玛测评?

在亚马逊、沃尔玛、速卖通、阿里国际站等电商平台上,测评已成为一种高效的推广手段,但伴随的风险也不容忽视。这些风险主要源于平台严格的大数据风控机制,它涵盖了多个方面,以确保评价的真实性和合规性。 首先,硬件参数…

Avalonia 常用控件二 Menu相关

1、Menu 添加代码如下 <Button HorizontalAlignment"Center" Content"Menu/菜单"><Button.Flyout><MenuFlyout><MenuItem Header"打开"/><MenuItem Header"-"/><MenuItem Header"关闭"/&…

Unity贪吃蛇改编【详细版】

Big and small greedy snakes 游戏概述 游戏亮点 通过对称的美感&#xff0c;设置两条贪吃蛇吧&#xff0c;其中一条加倍成长以及加倍减少&#xff0c;另一条正常成长以及减少&#xff0c;最终实现两条蛇对整个界面的霸占效果。 过程中不断记录两条蛇的得分情况&#xff0c…

如何配置taro

文章目录 step1. 全局安装wepacksetp2. 使用npm安装tarostep3. 项目初始化 使用taro时需要在本地配置好nodejs环境&#xff0c;关于如何配置nodejs可参考我的这篇博文 如何配置nodejs环境 step1. 全局安装wepack 使用指令npm install webpack -g即可 安装完成后可看到有wepa…

十大经典排序算法——选择排序和冒泡排序

一、选择排序 1.基本思想 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的数据全部排完。 2.直接选择排序 (1) 在元素集合arr[i] — arr[n - 1]中选择关键妈的最大&#xff08;小…

GaussDB关键技术原理:高性能(二)

GaussDB关键技术原理&#xff1a;高性能&#xff08;一&#xff09;从数据库性能优化系统概述对GaussDB的高性能技术进行了解读&#xff0c;本篇将从查询处理综述方面继续分享GaussDB的高性能技术的精彩内容。 2 查询处理综述 内容概要&#xff1a;本章节介绍查询端到端处理的…

AI大模型企业应用实战(18)-“消灭”LLM幻觉的利器 - RAG介绍

大模型在一定程度上去改变了我们生活生工作的思考的方式&#xff0c;然后也越来越多的个人还有企业在思考如何将大模型去应用到更加实际的呃生产生活中去&#xff0c;希望大语言模型能够呃有一些更多企业级别生产落地的实践&#xff0c;然后去帮助我们解决一些业务上的问题。目…

汉语拼音字母表 (声母表和韵母表)

汉语拼音字母表 [声母表和韵母表] 1. 汉语拼音声母表2. 汉语拼音韵母表References 1. 汉语拼音声母表 声母是韵母前的辅音&#xff0c;与韵母一起构成一个完整的音节。 辅音是发声时&#xff0c;气流在口腔中受到各种阻碍所产生的声音&#xff0c;发音的过程即是气流受阻和克…

玩转Matlab-Simscape(初级)- 10 - 基于COMSOLSimulink 凸轮机构的控制仿真

** 玩转Matlab-Simscape&#xff08;初级&#xff09;- 10 - 基于COMSOL&Simulink 凸轮机构的控制仿真 ** 目录 玩转Matlab-Simscape&#xff08;初级&#xff09;- 10 - 基于COMSOL&Simulink 凸轮机构的控制仿真 前言一、简介二、在Solidworks中创建3D模型&#xff…

Springboot拦截器使用及其底层源码剖析

博主最近看了一下公司刚刚开发的微服务&#xff0c;准备入手从基本的过滤器以及拦截器开始剖析&#xff0c;以及在帮同学们分析一下上次的jetty过滤器源码与本次Springboot中tomcat中过滤器的区别。正题开始&#xff0c;拦截器顾名思义是进行拦截请求的一系列操作。先给大家示例…

H4020 12V24V36V40V1A 同步降压芯片IC Buck-DCDC 低功耗,高效率 100%占空比

H4020是一款12V24V36V40V1A的同步降压&#xff08;Buck&#xff09;DC-DC转换器&#xff0c;专为需要高效率、低功耗和精确电压/电流控制的应用而设计。它内置了高压MOSFET&#xff0c;支持宽范围的输入电压&#xff08;5V-36V&#xff09;&#xff0c;并能提供高达1A的持续输出…

汇编快速入门

一.基础知识 1.数据类型 DB&#xff08;Define Byte&#xff0c;字节类型 占位8位bit 1字节&#xff09; 范围&#xff1a;DB可以用来定义&#xff08;无符号、有符号&#xff09;整数&#xff08;包含二、十、十六进制&#xff09;和字符 语法&#xff1a;a DB 数据个数…

“人工智能+”带来新变化

以生成式人工智能&#xff08;AIGC&#xff09;为代表的新一代人工智能技术创新加速演进&#xff0c;相关商业化应用成果也不断涌现&#xff0c;行业应用范围不断拓展&#xff0c;深度赋能实体经济&#xff0c;为行业提质增效与实现减排提供助力。 自主航运初创公司OrcaAI于6月…

g++制作C++动态库的简洁例程

g制作C动态库的简洁例程 code review! 文章目录 g\制作C动态库的简洁例程1. 创建 C 动态库1.1 动态库源文件1.2 编译动态库 2. 使用动态库2.1 命令行编译链接然后运行2.2 使用 CMake 编译链接然后运行 3.附加笔记&#xff1a;关于运行时是否能找到libmylib.so的问题汇总3.1.g -…

STM32项目分享:智能窗帘系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 1.PCB图 2.PCB板打样焊接图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.c…

浅析缓存技术

缓存技术的原理 缓存技术通过在内存中存储数据副本来加速数据访问。当应用程序需要数据时&#xff0c;首先检查缓存是否存在数据副本&#xff0c;如果有则直接返回&#xff0c;否则再从原始数据源获取。这种机制大大减少了访问时间&#xff0c;提升了系统的响应速度和整体性能。…