Android OpenGL ES(八)----纹理编程框架(一)

简介: Android OpenGL ES(八)----纹理编程框架(一)

1.把纹理加载进OpenGL中


我们的第一个任务就是把一个图像文件的数据加载到一个OpenGL的纹理中。


作为开始,让我们重新舍弃第二篇的框架,重新创建一个程序,新建一个util工具包,在该包下创建一个新类TextureHelper,我们将以下面的方法签名开始:


public static int loadTexture(Context context,int resourceId){}


这个方法会把Android上下文,和资源ID作为输入参数,并返回加载图像的OpenGL纹理的ID。开始时,我们会使用创建其他OpenGL对象时一样的模式生成一个新的纹理ID。

final int[] textureObjectIds=new int[1];
GLES20.glGenTextures(1,textureObjectIds,0);
if(textureObjectId[0]==0){
Log.w(TAG,"创建纹理失败!");
}


通过传递1作为第一个参数调用glGenTextures(),我们就创建了一个纹理对象。OpenGL会把那个生成的ID存储在textureObjectIds中。我们也检查了glGenTextures()调用是否成功,如果结果不等于0就继续,否则记录那个错误并返回0。因为TAG还没有定义,让我们在类的顶部为它加入如下定义:


private static final String TAG="TextureHelper";


加载位图数据并与纹理绑定


下一步是使用Android的API读入图像文件的数据。OpenGL不能直接读取PNG或者JPEG文件的数据,因为这些文件被编码为特定的压缩格式。OpenGL需要非压缩形式的原始数据,因此,我们需要用Android内置的位图解码器把图像文件解压缩为OpenGL能理解的形式。


让我们继续实现loadTexture(),把那个图像解压缩为一个Android位图:

final BitmapFactory.Options options=new BitmapFactory.Options();
options.inScaled=false;
final Bitmap bitmap=BitmapFactory.decodeResource(context.getResource(),resourceId,options);
if(bitmap==null){
Log.w(TAG,"加载位图失败");
GLES20.glDeleteTexture(1,textureObjectIds,0);
return 0;
}


首先创建一个新的BitmapFactory.Options的实例,命名为“options”,并且设置inScaled为"false"。这告诉Android我们想要原始的图像数据,而不是这个图像的压缩版本。



接下来调用BitmapFactory.decodeResource()做实际的解码工作,把我们刚刚定义的Android上下文,资源ID和解码的options传递进去。这个调用会把解码后的图像存入bitmap,如果失败就会返回空值。我们检查了那个失败,如果位图是空值,那个OpenGL纹理对象会被删除。如果解码成功,就继续处理那个纹理。


在可以使用这个新生成的纹理对象做任何其他事之前,我们需要告诉OpenGL后面纹理的调用应该应用于这个纹理对象。我们为此使用一个glBindTexture()调用:


GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectIds[0]);


第一个参数GL_TEXTURE_2D告诉OpenGL这应该被作为一个二位纹理对待,第二个参数告诉OpenGL要绑定到哪个纹理对象的ID。



既然上一篇博文已经了解了纹理过滤,我们直接编写loadTexture()后面的代码:


GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR_MIPMAP_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);


我们用一个glTexParameteri()调用设置每个过滤器:GL_TEXTURE_MIN_FILTER是指缩小的情况,而GL_TEXTURE_MAG_FILTER是指放大的情况。对于缩小的情况,我们选择GL_LINEAR_MIPMAP_LINEAR,它告诉OpenGL使用三线性过滤;我们设置放大过滤器为GL_LINEAR,它告诉OpenGL使用双线性过滤。


加载纹理到OpenGL并返回其ID


我们现在可以用一个简单的GLUtil_texImage2D()调用加载位图数据到OpenGL里了:


GLUtil_texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0);

这个调用告诉OpenGL读入bitmap定义的位图数据,并把它复制到当前绑定的纹理对象。


既然这些数据已经被加载进OpenGL了,我们就不需要持有Android的位图了。正常情况下,释放这个位图数据也会花费Dalvik的几个垃圾回收周期,因此我们应该调用bitmap对象的recycle()方法立即释放这些数据:


bitmap.recycle();


生成MIP贴图也是一件容易的事情。我们用一个快速的glGenerateMipmap()调用告诉OpenGL生成所有必要的级别:


GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);


既然我们完成了纹理对象的加载,一个很好的实践就是解除与这个纹理的绑定,这样我们就不会用其他纹理方法调用意外地改变这个纹理:


GLES20.gl_BindTexture(GLES20.GL_TEXTURE_2D,0);


传递0给glBindTexture()就与当前的纹理接触绑定了。最后一步是返回纹理对象ID:


return textureObjectIds[0];


我们现在有一个方法了,它可以从资源文件夹读入图像文件,并把图形数据加载进OpenGL。我们也取回一个纹理ID,它可被用做这个纹理的引用,如果加载失败,我们会得到0。以上所有方法都是TextureHelper类下loadTexture()方法里面的代码。


2.创建新的着色器集合


在把纹理绘制到屏幕之前,我们不得不创建一套新的着色器,它们可以接收纹理,并把它们应用在要绘制的片段上。这些新的着色器与我们目前为止使用过的着色器相似,只是为了支持纹理做了一些轻微的改动。


创建新的顶点着色器


在项目中res/raw/目录下新建一个文件,命名为“texture_vertex_shader.glsl”,并加入如下内容:

uniform mat4 u_Matrix;
attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
void main(){
v_TextureCoordinates=a_TextureCoordinates
gl_Position=u_Matrix*a_Position;
}


这个着色器的大多数代码看上去应该都比较熟悉:我们已经为矩阵定义了一个uniform,并且也为位置定义了一个属性。我们使用这些去设置最后的gl_Position。而对于这些新的东西,我们同样给纹理坐标家了一个新的属性,它叫“a_TextureCoordinates”。因为它有两个分量:S坐标和T坐标,所以被定义为vec2。我们把这些坐标传递给顶点着色器被插值的varying,称为v_TextureCoordinates。


创建新的片段着色器


在同样的目录,创建一个叫做“texture_fragment_shader.glsl”的新文件,并加入如下代码:

precision mediump float;
uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;
void main(){
gl_FragColor=texture2D(u_TextureUnit,v_TextureCoordinates);
}


为了把纹理绘制到一个物体上,OpenGL会为每个片段都调用片段着色器,并且每个调用都接受v_TextureCoordinates的纹理坐标。片段着色器也通过uniform------u_TextureUnit接受实际的纹理数据,u_TextureUnit被定义为一个sampler2D, 这个变量类型指的是一个二维纹理数据的数组。


被插值的纹理坐标和纹理数据被传递给着色器函数texture2D(),它会读入纹理中那个特定的坐标处的颜色值。接着通过把结果赋值给gl_FragColor设置片段的颜色。


3.为顶点数据创建新的类结构


首先,我们将把顶点数据分离到不同的类中,每个类代表一个物理对象的类型。我们将为桌子创建一个新类,并为木槌创建一个新类。因为纹理上已经有一条直线了,所以我们不需要给那个分割线创建新类。


为了减少重复,我们会创建独立的类,用于封装实际的顶点数组。新的类结构看上去如下图所示:

39.png

我们会创建Mallet类管理木槌的数据,以及Table管理桌子的数据;并且每个类都会有一个VertexArray类的实例,它用来封装存储顶点矩阵的FloatBuffer。


我们将从VertexArray类开始。在你的项目中创建一个新的包,命名为data,并在那个包中创建一个新类,命名为VertexArray,代码如下:

private final FloatBuffer floatBuffer;
public VertexArray(float[] vertexData){
this.floatBuffer=ByteBuffer.allocateDirect(VertexData.length*BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);
}
public void setVertexAttribPointer(int dataOffset,int attributeLocation,int compontCount,int stride){
this.floatBuffer.position(dataOffset);
GLES20.glVertexAttribPointer(attributeLocation,compontCount,GLES20.GL_FLOAT,false,stride,this.floatBuffer);
GLES20.glEnableVertexAttribArray(attributeLocation);
this.floatBuffer.position(0);
}


这段代码包含一个FloatBuffer,如第二篇博文解释的,它是用来在本地代码中存储顶点矩阵数据的。这个构建器取用一个Java的浮点数组,并把它写进这个缓冲区。


我们也创建一个通用的方法把着色器中的属性与这些数据关联起来。它遵循我们在第三篇博文中解释过的同样的模式。


因为我们最终要在几个类中都使用BYTES_PER_FLOAT,我们需要给它找个新的地方。要做到这点,我们要在data包中创建一个名为Constans的新类,并加入如下代码:

public Class Constants{
public static final int BYTES_PER_FLOAT=4;
}


加入桌子数据


现在我们将定义一个存储桌子数据的类,这个类会存储桌子的位置数据;我们还会加入纹理坐标,并把这个纹理应用于这个桌子。


添加类常量,创建一个包,名为object;在这个包中,创建名为Table的新类,并在类的内部加入如下代码:

private static final int POSITION_COMPONENT_COUNT=2;
private static final int TEXTURE_COORDINATES_COMPONENT_COUNT=2;
private static final int STRIDE=(POSITION_COMPONENT_COUNT+TEXTURE_COORDINATES_COMPONENT_COUNT)*Constans.BYTES_PER_FLOAT;


如你所见,我们定义了位置分量计数,纹理坐标分量计数以及跨距。


添加顶点数据,如下代码定义顶点数据:

private static final float[] VERTEX_DATA={
//X,Y,S,T
0f,0f,0.5f,0.5f,
-0.5f,-0.8f,0f,0.9f,
0.5f,-0.8f,1f,0.9f,
0.5f,0.8f,1f,0.1f,
-0.5f,0.8f,0f,0.1f,
-0.5f,-0.8f,0f,0.9f
}


这个数组包含了空气曲棍球桌子的顶点数据。我们也定义了X和Y的位置,以及S和T纹理坐标。你可能注意到了那个T分量正是按那个Y分量相反的方向定义的。之所以会这样,如我们上篇博文解释的,图像的朝向是右边向上的。当我们使用一个对称的纹理坐标时,这一点实际上没有关系,但是在其他情况下,这就有问题了,因此一定要记住这个原则。


剪裁纹理


我们还使用了0.1f和0.9f作为T坐标。为什么?这个桌子是1个单位宽,1.6个单位高,而纹理图像是512*1024像素,因此,如果它的宽度对应1个单位,那纹理的高实际就是2个单位。为了避免把纹理压扁,我们使用乏味0.1到0.9剪裁它的边缘,而不是用0.0到1.0,并且只画它的中间部分。


即使不使用剪裁,我们还可以坚持使用从0.0到1.0的纹理坐标,把这个纹理预拉伸,这样被压扁到空气曲棍球桌子之后,它看去就是正确的了。采用这种方法,那些无法显示的纹理部分就不会占用任何内存了。


初始化和绘制数据


现在为 Table类创建一个构造函数。这个构造函数会使用VertexArray把数据复制到本地内存中的一个FloatBuffer。

private final VertexArray vertexArray;
public Table(){
this.vertexArray=new VertexArray(VERTEX_DATA);
}


添加一个方法把顶点数组绑定到一个着色器程序上:


public void bindData(TextureShaderProgram textureProgram){
this.vertexArray.setVertexAttribPointer(
0,
textureProgram.getPositionLocation(),
POSITION_COMPONENT_COUNT,
STRIDE);
this.vertexArray.setVertexAttribPointer(
POSITION_COMPONENT_COUNT,
textureProgram.getTextureLocation(),
TEXTURE_COORDINATES_COMPONENT_COUNT,
STRIDE);
}


这个方法为每个顶点调用了setVertexAttribPointer(),并从着色器程序获取每个属性的位置。它通过调用getPositionLocation()把位置绑定到被引用的着色器属性上,并通过getTextureLocation()把纹理坐标绑定到被引用的着色器属性上。当我们创建着色器的类时,会定义这些方法。


我们只需加入最后一个方法就可以画出这张桌子了:

public void draw(){
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN,0,6);
}


加入木槌数据


在同一个包中创建另一个类,命名为“Mallet”。在这个类中加入如下代码:

private static final int POSITION_COMPONENT_COUNT=2;
private static final int COLOR_COMPONENT_COUNT=3;
private static final int STRIDE=(POSITION_COMPONENT_COUNT+COLOR_COMPONENT_COUNT)*Constans.BYTES_PER_FLOAT;
private static final float[] VERTEX_DATA={
0f,-0.4f,0f,0f,1f,
0f,0.4f,1f,0f,0f
}
private final VertexArray vertexArray;
public Mallet(){
this.vertexArray=new VertexArray(VERTEX_DATA);
}
public void bindData(ColorShaderProgram colorProgram){
this.vertexArray.setVertexAttribPointer(
0,
colorProgram.getPositionLocation(),
POSITION_COMPONENT_COUNT,
STRIDE);
this.vertexArray.setVertexAttribPointer(
POSITION_COMPONENT_COUNT,
colorProgram.getColorLocation(),
COLOR_COMPONENT_COUNT,
STRIDE);
}
public void draw(){
GLES20.glDrawArrays(GLES20.GL_POINTS,0,2);
}


它遵循与Table类一样的模式,与之前一样,我们还是把木槌画为点。


顶点数据现在被定义好了:我们有一个类表示桌子数据,另一个类表示木槌数据,第三个类使得更容易管理顶点数据本身。下一步是为着色器程序定义类。

目录
打赏
0
0
0
0
20
分享
相关文章
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
78 36
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
安卓与iOS开发中的跨平台框架解析
在移动应用开发的广阔舞台上,安卓和iOS一直是两大主角。随着技术的进步,开发者们渴望能有一种方式,让他们的应用能同时在这两大平台上运行,而不必为每一个平台单独编写代码。这就是跨平台框架诞生的背景。本文将探讨几种流行的跨平台框架,包括它们的优势、局限性,以及如何根据项目需求选择合适的框架。我们将从技术的深度和广度两个维度,对这些框架进行比较分析,旨在为开发者提供一个清晰的指南,帮助他们在安卓和iOS的开发旅程中,做出明智的选择。
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。
|
5月前
|
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
209 1
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
555 3
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
150 8
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!

热门文章

最新文章

  • 1
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    16
  • 2
    Android历史版本与APK文件结构
    11
  • 3
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    28
  • 4
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    2
  • 5
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    11
  • 6
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    3
  • 7
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    3
  • 8
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    2
  • 9
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    4
  • 10
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    1
  • 1
    android FragmentManager 删除所有Fragment 重建
    17
  • 2
    Android实战经验之Kotlin中快速实现MVI架构
    25
  • 3
    即时通讯安全篇(一):正确地理解和使用Android端加密算法
    33
  • 4
    escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
    42
  • 5
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    135
  • 6
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    41
  • 7
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    57
  • 8
    Android历史版本与APK文件结构
    156
  • 9
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    48
  • 10
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    41