Android OpenGL ES(三)----编程框架(一)

简介: Android OpenGL ES(三)----编程框架(一)

首先当然是创建Android项目,你可以选择最新的Android Studio也可以选择eclipse都是一样的。我们重点讲解开发OpenGL ES的流程


1.定义顶点着色器和片段着色器


第一节我们讲解的已经很细致了,为了便于理解在这里在详细的说明一下。并且换一种方式定义着色器。


我们知道第一篇定义的顶点的坐标和颜色是分开的,这样可以但如果把它们放在一起会方便许多。


假设我们要绘制一个长方形和两条直线,二个定点,我们就需要这样来定义这个数组。

float[] tableVerticesWithTriangles = {
//两个三角形和三角形的颜色分量
                0f, 0f, 1f, 1f, 1f,
                -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
                0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
                0.5f, 0.5f, 0.7f, 0.7f, 0.7f,
                -0.5f, 0.5f, 0.7f, 0.7f, 0.7f,
                -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
//两条直线和直线的颜色分量
                -0.5f, 0f, 1f, 0f, 0f,
                0.5f, 0f, 1f, 0f, 0f,
//两个顶点和顶点的颜色分量
                0f, -0.25f, 0f, 0f, 1f,
                0f, 0.25f, 1f, 0f, 0f
        };


前面的两个顶点代表坐标,后面三个顶点代表颜色分别为:红色,绿色和蓝色。


接着必须对应的建立对应的顶点着色器,假设raw文件夹下的顶点着色器的文件名是

simple_vertex_shader.glsl:
attribute vec4 a_Position;    
 attribute vec4 a_Color;
 varying vec4 v_Color;
 void main()                    
 {
     v_Color=a_Color;
     gl_Position = a_Position;
     gl_PointSize = 10.0;
 }


我们加入了一个新的属性a_Color,也加入了一个叫做v_Color的新varying。上一篇已经讲过varying是一个特殊的变量类型,它把给它的值进行混合并把这些混合的值发送给片段着色器。


我们把varying也加入片段着色器,在raw文件夹下创建simple_fragment_shader.glsl:

precision mediump float; 
 varying vec4 v_Color;
 void main()                     
 {                               
     gl_FragColor = v_Color;
 }


我们用varying变量v_Color替换了原来代码中的uniform。如果这个片段属于一条直线,那个OpenGL就会用构成那条直线的两个顶点计算其混合后的颜色。


2.加载着色器


在项目中创建一个新的Java源代码包,命名为util把,至于前缀得看你项目的名称或者你自己的爱好。


在这个包下创建一个名为“TextResourceReader”的新类

public class TextResourceReader {
     public static String readTextFileFromResource(Context context, int resourceId) {
         StringBuilder body = new StringBuilder();
         try {
             InputStream is = context.getResources().openRawResource(resourceId);
             InputStreamReader reader = new InputStreamReader(is);
             BufferedReader bufferedReader = new BufferedReader(reader);
             String nextLine;
             while ((nextLine = bufferedReader.readLine()) != null) {
                 body.append(nextLine);
                 body.append("\n");
             }
         } catch (IOException e) {
             throw new RuntimeException(
                     "Could not open resource: " + resourceId, e);
         } catch (Resources.NotFoundException nfe) {
             throw new RuntimeException("Resource not found: " + resourceId, nfe);
         }
         return body.toString();
     }
 }


至于这段代码我就不过多的解释了这属于JAVA基础也可以说是Android基础,本文重点讲解OpenGL,这段代码的作用是加载着色器。



3.初始化OpenGL


定义两个成员变量:

private GLSurfaceView glSurfaceView;
private boolean rendererSet=false;


在Activity的OnCreate()方法里面初始化glSurfaceView:

this.glSurfaceView = new GLSurfaceView(this);

检查系统是否支持OpenGL ES 2.0:

final ActivityManager activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
         final ConfigurationInfo configurationInfo=activityManager.getDeviceConfigurationInfo();
         final boolean supportsEs2=configurationInfo.reqGlEsVersion>=0x20000;

首先我们需要Android ActivityManager的一个引用,用它来获取设备的配置信息,然后,取出reqGlEsVersion变量检查OpenGL ES版本号。如果版本号为0*20000或后续版本,我们就可以使用OpenGL ES2.0的API了。


为OpenGL ES2.0配置渲染表面

if(supportsEs2){
             this.glSurfaceView.setEGLContextClientVersion(2);
             this.glSurfaceView.setRenderer(new LYJRenderer(this));
             this.rendererSet=true;
         }else{
             Toast.makeText(this,"bu zhi chi gai banben ",Toast.LENGTH_SHORT).show();
             return;
         }

如果设备支持OpenGL ES2.0,我们就通过调用setEGLContextClientVersion(2)配置这个surface视图,然后调用setReader()传进自定义的Renderer类的一个新实例,其实如果设备不支持OpenGL ES2.0,公开发布的应用在这个设备的应用程序市场中被隐藏起来,至于隐藏,后续讲到,当然这也是清单文件的基础知识。rendererSet记住GLSurfaceView是否处于有效状态。


setContentView(this.glSurfaceView);

相信大家都知道上面的作用,就是把GLSurfaceView加入到Activity中。并把它显示到屏幕上。


当然我们还需要利用Activity生命周期释放资源,如果没有下面的代码,应用程序可能会崩溃。

@Override
     protected void onResume() {
         super.onResume();
         if(this.rendererSet){
             this.glSurfaceView.onResume();
         }
     }
     @Override
     protected void onPause() {
         super.onPause();
         if(this.rendererSet){
             this.glSurfaceView.onPause();
         }
     }


创建Renderer类


让我们看一下这个接口的方法:


onSurfaceCreated(GL10 glUnused,EGLConfig config)


当Surface被创建的时候,GLSurfaceView会调用这个方法;这发生在应用程序第一次运行的时候,并且,当设备被唤醒或者用户从其他Activity切换回来时,这个方法可能会被调用。在实践中,这意味着,当应用程序运行时,本方法可能会被调用多次。

onSurfaceChanged(GL10 glUnused,int width,int height)


在Surface被创建后,每次Surface尺寸变化时,这个方法都会被GLSurfaceView调用到,在横屏,竖屏来回切换的时候,Surface尺寸会发生变化。

onDrawFrame(GL10 glUnused)

当绘制一帧时,这个方法会被GLSurfaceView调用。在这个方法中,我们一定要绘制一些东西,即使只是清空屏幕;因为,在这个方法返回后,渲染缓冲区会被交换并显示在屏幕上,如果什么都没画,可以会看到糟糕的闪烁效果。


观察这些方法,可能细心的会发现都有一个GL10参数,这是OpenGL1.0遗留下来的,如果在1.0的设备上就会用到,当时对于OpenGL ES2.0,GLES20类提供了静态方法存取。


新建渲染器LYJRenderer:

public class LYJRenderer implements GLSurfaceView.Renderer {
public LYJRenderer(Context context) {
         this.context = context;
this.vertexData = ByteBuffer.allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();
         this.vertexData.put(tableVerticesWithTriangles);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES20.glViewport(0, 0, width, height);
}
    public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }

首先调用在onSurfaceCreated调用glClearColor设置清空屏幕,前三个参数对应颜色,后一个参数是透明度。这里设置为黑色。


下一步就是设置视口的尺寸了也就是glViewport,这就是告诉OpenGL可以用来渲染的surface的大小。


在onDrawFrame中调用glClear清空屏幕,这会擦除屏幕上所有颜色,并调用之前的glClearColor()调用定义的颜色填充整个屏幕。


在Renderer类中读入着色器,在onSurfaceCreated()的结尾除加入如下代码:

String vertexShaderSource = TextResourceReader.readTextFileFromResource(this.context, R.raw.simple_vertex_shader);
        String fragmentShaderSource = TextResourceReader.readTextFileFromResource(this.context, R.raw.simple_fragment_shader);


4.编译着色器


我们把着色器源码从文件中读取出来,下一步就是编译每个着色器了。我们要创建一个新的辅助类,它可以创建新的OpenGL着色器对象,编译着色器并且返回代表那段着色器的对象。一旦写出这个样板代码,在未来的项目中可以重用了。


创建一个名为ShaderHelper类,并添加如下代码:

public class ShaderHelper {
     public static final String TAG = "ShaderHelper";
     public static int compileVertexShader(String shaderCode) {
         return compileShader(GLES20.GL_VERTEX_SHADER, shaderCode);
     }
     public static int compileFragmentShader(String shaderCode) {
         return compileShader(GLES20.GL_FRAGMENT_SHADER, shaderCode);
     }
public static int compileShader(int type, String shaderCode) {


这些代码作为辅助类的基础。下面就要创建新的着色器对象并检查创建是否成功,下面都是在构建compileShader方法。

final int shaderObjectId = GLES20.glCreateShader(type);
         if (shaderObjectId == 0) {
             if (LoggerConfig.ON) {
                 Log.w(TAG, "Counld not create new shader");
             }
             return 0;
         }

这里,用glCreateShader()调用创建了一个新的着色器对象,并把这个对象的ID存入变量shaderObjectId。这个type可以代表定点着色器的GL_VERTEX_SHADER,或者是代表片段着色器的GL_FRAGMENT_SHADER。剩下的代码也用同样的方式。


记住我们是如何创建对象并检查它是否有效的;这个模式将在OpenGL里广泛使用:


1.首先使用一个如glCreateShader()一样的调用创建一个对象,这个调用会返回一个整型值。


2.这个整型值就是OpenGL对象的一个引用。无论后面什么时候要引用这个对象,就要把这个整型值传回 OpenGL。


3.返回值0表示这个对象创建失败,它类似于Java代码返回的null值。


上传和编译着色器源代码

GLES20.glShaderSource(shaderObjectId, shaderCode);

这个调用是告诉OpenGL读入字符串shaderCode定义的源代码,并把它与shaderObjectId所引用的着色器对象关联起来。然后调用下面方法编译着色器:

GLES20.glCompileShader(shaderObjectId);

这个调用告诉OpenGL编译上传到shaderObjectId的源代码。


取出编译状态,加入如下代码:

final int[] compileStatus = new int[1];
         GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

为了检查编译失败还是成功,首先要创建新的长度为1的int数组,称为compileStatus;然后调用glGetShaderiv。这就告诉OpenGL读取与shaderObjectId关联的编译状态,并把它写入compileStatus的第0个元素。


这是Android平台上的OpenGL的另一个通用模式。 为了取出一个值,我们通常会使用一个长度为1的数组,并把这个数组传进OpenGL调用。在一个调用中,我们告诉OpenGL把结果存进数组的第一个元素。


验证编译状态并返回着色器对象ID,代码如下:

if (compileStatus[0] == 0) {
             GLES20.glDeleteShader(shaderObjectId);
             if (LoggerConfig.ON) {
                 Log.w(TAG, "Compilation of shader failed");
             }
             return 0;
         }

如果编译成功返回shaderObjectId:

return shaderObjectId;


在Renderer类中编译着色器,在OnSurfaceView()结尾处加入如下代码:

int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource);
         int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderSource);


相关文章
|
4月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
5月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台框架解析
在移动应用开发的广阔舞台上,安卓和iOS一直是两大主角。随着技术的进步,开发者们渴望能有一种方式,让他们的应用能同时在这两大平台上运行,而不必为每一个平台单独编写代码。这就是跨平台框架诞生的背景。本文将探讨几种流行的跨平台框架,包括它们的优势、局限性,以及如何根据项目需求选择合适的框架。我们将从技术的深度和广度两个维度,对这些框架进行比较分析,旨在为开发者提供一个清晰的指南,帮助他们在安卓和iOS的开发旅程中,做出明智的选择。
|
1月前
|
算法 JavaScript Android开发
|
2月前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
111 1
|
3月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
393 3
|
3月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
88 8
|
4月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!
|
4月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
如何使用Amlogic T972安卓9.0系统上的misc框架来简化驱动程序开发,通过misc框架自动分配设备号并创建设备文件,从而减少代码量并避免设备号冲突。
53 0
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
|
4月前
|
存储 前端开发 Java
Android MVVM框架详解与应用
在Android开发中,随着应用复杂度的增加,如何有效地组织和管理代码成为了一个重要的问题。MVVM(Model-View-ViewModel)架构模式因其清晰的结构和高效的开发效率,逐渐成为Android开发者们青睐的架构模式之一。本文将详细介绍Android MVVM框架的基本概念、优势、实现流程以及一个实际案例。
128 0
|
5月前
|
移动开发 开发工具 Android开发
探索安卓与iOS开发的差异:平台特性与编程实践
【7月更文挑战第8天】在移动开发的广阔天地中,安卓和iOS这两大操作系统各自占据着半壁江山。它们在用户界面设计、系统架构及开发工具上展现出截然不同的特色。本文将深入探讨这两个平台在技术实现和开发生态上的关键差异,并分享一些实用的开发技巧,旨在为跨平台开发者提供有价值的见解和建议。