Android上Java程序和Opengl通信方式和opengl es着色器

来源:互联网 发布:矩阵lu分解例题3*3 编辑:程序博客网 时间:2024/06/11 15:27

阅读本文前如果是初次接触opengl可以先阅读前文:
openGL 3D图形和openGL简介和 android studio上第一个opengl es程序
           在OpenGL中只能绘制点,直线,三角形。每个物体都是通过顶点聚合形成的点,直线,三角形组成基本图形,并且通过着色器着色绘制而成。所以,在OpenGL中顶点和着色器是非常重要的概念。
顶点:
顶点就是指几何物体拐角的点,最重要的属性就是位置,来标识顶点在空间的定位,通常用float[] 来存放顶点的坐标。

           上图为一个正方形的基本顶点坐标,可以看出是由简单三角形构成,在OpenGL中(0,0)坐标位于左下角,采用逆时针笛卡尔坐标系, float[] tableVerticesWithTriangles = {
// Triangle 1
0f, 0f,
9f, 14f,
0f, 14f,
// Triangle 2
0f, 0f,
9f, 0f,
9f, 14f
};
总是以逆时针方向排列顶点,叫卷曲顺序。
Android上Java程序和OpenGL通信方式
           我们知道Android上的java程序是运行在虚拟机上的,而OpenGL程序是操作硬件的程序直接运行在硬件上,这就涉及Java程序和OpenGL通信问题,在Android中有两种方式实现该通信。
1.通过Java接口jni方式,这种方式Android软件包已经实现,例如当我们调用android.opengl.GLES20包里面方法时,实际使用Android通过jni方式来调用本地opengl库函数。
2.把内存从Java堆复制拷贝到本地堆。Java有一个特殊的类集合用来分配本地内存块,并且把Java数据拷贝到本地内存,本地内存可以被本地环境存取而不受java虚拟机垃圾回收管理,如下图。
这里写图片描述

代码层面可以参考本文末尾给的demo里面AirHockeyRenderer 代码如下:

/*** * Excerpted from "OpenGL ES for Android", * published by The Pragmatic Bookshelf. * Copyrights apply to this code. It may not be used to create training material,  * courses, books, articles, and the like. Contact us if you are in doubt. * We make no guarantees that this code is fit for any purpose.  * Visit http://www.pragmaticprogrammer.com/titles/kbogla for more book information.***/package opengl.timothy.net.openglesproject_lesson2;import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;import static android.opengl.GLES20.GL_FLOAT;import static android.opengl.GLES20.GL_LINES;import static android.opengl.GLES20.GL_POINTS;import static android.opengl.GLES20.GL_TRIANGLES;import static android.opengl.GLES20.glClear;import static android.opengl.GLES20.glClearColor;import static android.opengl.GLES20.glDrawArrays;import static android.opengl.GLES20.glEnableVertexAttribArray;import static android.opengl.GLES20.glGetAttribLocation;import static android.opengl.GLES20.glGetUniformLocation;import static android.opengl.GLES20.glUniform4f;import static android.opengl.GLES20.glUseProgram;import static android.opengl.GLES20.glVertexAttribPointer;import static android.opengl.GLES20.glViewport;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.FloatBuffer;import javax.microedition.khronos.egl.EGLConfig;import javax.microedition.khronos.opengles.GL10;import android.content.Context;import android.opengl.GLSurfaceView.Renderer;import opengl.timothy.net.openglesproject_lesson2.util.LoggerConfig;import opengl.timothy.net.openglesproject_lesson2.util.ShaderHelper;import opengl.timothy.net.openglesproject_lesson2.util.TextResourceReader;public class AirHockeyRenderer implements Renderer {    private static final String U_COLOR = "u_Color";    private static final String A_POSITION = "a_Position";        private static final int POSITION_COMPONENT_COUNT = 2;    private static final int BYTES_PER_FLOAT = 4;    private final FloatBuffer vertexData;    private final Context context;    private int program;    private int uColorLocation;    private int aPositionLocation;    public AirHockeyRenderer() {        // This constructor shouldn't be called -- only kept for showing        // evolution of the code in the chapter.        context = null;        vertexData = null;    }    public AirHockeyRenderer(Context context) {        this.context = context;        /*        float[] tableVertices = {             0f,  0f,             0f, 14f,             9f, 14f,             9f,  0f         };         */        /*        float[] tableVerticesWithTriangles = {            // Triangle 1            0f,  0f,             9f, 14f,            0f, 14f,            // Triangle 2            0f,  0f,             9f,  0f,                                        9f, 14f                     // Next block for formatting purposes            9f, 14f,            , // Comma here for formatting purposes                     // Line 1            0f,  7f,             9f,  7f,            // Mallets            4.5f,  2f,             4.5f, 12f        };         */        float[] tableVerticesWithTriangles = {            // Triangle 1            -0.5f, -0.5f,              0.5f,  0.5f,            -0.5f,  0.5f,            // Triangle 2            -0.5f, -0.5f,              0.5f, -0.5f,              0.5f,  0.5f,            // Line 1            -0.5f, 0f,              0.5f, 0f,            // Mallets            0f, -0.25f,             0f,  0.25f        };        vertexData = ByteBuffer            .allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT)            .order(ByteOrder.nativeOrder())            .asFloatBuffer();        vertexData.put(tableVerticesWithTriangles);    }    @Override    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {        /*        // Set the background clear color to red. The first component is red,        // the second is green, the third is blue, and the last component is        // alpha, which we don't use in this lesson.        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);         */        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);        String vertexShaderSource = TextResourceReader            .readTextFileFromResource(context, R.raw.simple_vertex_shader);        String fragmentShaderSource = TextResourceReader            .readTextFileFromResource(context, R.raw.simple_fragment_shader);        int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource);        int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderSource);        program = ShaderHelper.linkProgram(vertexShader, fragmentShader);        if (LoggerConfig.ON) {            ShaderHelper.validateProgram(program);        }        glUseProgram(program);        uColorLocation = glGetUniformLocation(program, U_COLOR);        aPositionLocation = glGetAttribLocation(program, A_POSITION);        // Bind our data, specified by the variable vertexData, to the vertex        // attribute at location A_POSITION_LOCATION.        vertexData.position(0);        glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT,             false, 0, vertexData);        glEnableVertexAttribArray(aPositionLocation);    }    /**     * onSurfaceChanged is called whenever the surface has changed. This is     * called at least once when the surface is initialized. Keep in mind that     * Android normally restarts an Activity on rotation, and in that case, the     * renderer will be destroyed and a new one created.     *      * @param width     *            The new width, in pixels.     * @param height     *            The new height, in pixels.     */    @Override    public void onSurfaceChanged(GL10 glUnused, int width, int height) {        // Set the OpenGL viewport to fill the entire surface.        glViewport(0, 0, width, height);            }    /**     * OnDrawFrame is called whenever a new frame needs to be drawn. Normally,     * this is done at the refresh rate of the screen.     */    @Override    public void onDrawFrame(GL10 glUnused) {        // Clear the rendering surface.        glClear(GL_COLOR_BUFFER_BIT);        // Draw the table.        glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);                glDrawArrays(GL_TRIANGLES, 0, 6);        // Draw the center dividing line.        glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);                glDrawArrays(GL_LINES, 6, 2);         // Draw the first mallet blue.                glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);                glDrawArrays(GL_POINTS, 8, 1);        // Draw the second mallet red.        glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);                glDrawArrays(GL_POINTS, 9, 1);    }}

代码中BYTES_PER_FLOAT = 4表示的是一个float占4个字节,FloatBuffer用来在本地底层内存存储数据。AirHockeyRenderer的构造函数中有如下代码

        vertexData = ByteBuffer            .allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT)            .order(ByteOrder.nativeOrder())            .asFloatBuffer();        vertexData.put(tableVerticesWithTriangles);

        用ByteBuffer .allocateDirect分配了一块本地内存,这块内存不会被垃圾回收器管理,里面参数是需要申请内存字节大小,tableVerticesWithTriangles.length * BYTES_PER_FLOAT为本文需要申请大小。order(ByteOrder.nativeOrder())是告诉字节缓冲区(ByteBuffer)按照本地字节序(nativeOrder)组织他的内容。asFloatBuffer()是指我们不愿意用字节来操作内存还是float。 vertexData.put(tableVerticesWithTriangles)方法就把数据从dalvik/art虚拟机复制到本地内存。

字节序:

           是描述一种硬件架构是如何来组织位(bit)和字节的.这个和具体硬件架构有关,有的硬件是按照大头序排列(即最重要的位在前面),有的按小头序(最重要的位在后面)。比如十进制10000转换为二进制数为10011100010000,在按大头序排列的硬件上是00100111 00010000,在按小头序排列的硬件上是 00010000 00100111 。

着色器:

           在数据从Java层拷贝到底层内存后,最终是要在硬件GPU上渲染绘制。而着色器正是来告诉GPU这些数据如何组织渲染绘制。有两种着色器,需要在使用之前先定义:
1.顶点着色器(vetex shader),生产每个顶点的最终位置,针对每个顶点都会执行一次,一旦最终位置确定了,OpenGL就会把这些可见的顶点组装成点,直线,三角形。
2.片段着色器(fragment shader),为组成点,直线,三角形的每个片段生成最终的颜色,针对每个片段他都会执行一次.一个片段是一个小的,颜色单一的长方形区域,类似于计算器屏幕上的一个像素。一旦最后一块颜色生成,opengl会把他们写到一块称为帧缓冲区(frame buffer)的内存,然后Android会把这块帧缓冲器显示在屏幕上。

为什么要用着色器?

           在着色器出现之前,OpenGL只能用一个固定的方式集合控制很少而有限的事情,比如场景里有多少雾,加多少光线,这些固定api好用不好扩展。故在opengles 2.0加入了使用着色器加入可编程api,而把旧的api完成删除,所以用户必须使用着色器。我们使用顶点着色器控制点,直线,三角形的空间位置,用片段着色器来控制绘制内容。
创建一个顶点着色器:
这里写图片描述
如本文的例子中在res目录下创建了顶点着色器和片段着色器。
其中顶点着色器simple_vertex_shader.glsl里代码如下:

attribute vec4 a_Position;          void main()                    {                                  gl_Position = a_Position;    gl_PointSize = 10.0;}   

           这些代码是按照着色器语言(GLSL)编写的,具体可参考相关资料。对于我们定义的每一个顶点顶点着色器都会调用一次,当他被调用时会在a_Position的属性里接收顶点位置信息,而这个属性是vec4 类型。一个vec4 包含4个分量,在位置上下文中指x,y,z,w坐标,默认情况下x,y,z值为0,w为1。attribute 就是把位置坐标放进着色器的手段。main方法是着色器入口函数,他会把前面定义的位置复制到输出变量gl_Position 中,必须顶点着色器一定要给gl_Position 赋值的,这样OpenGL才会把gl_Position 的值当做当前顶点最终的位置,组装成点,直线,三角形。
创建一个片段着色器(fragment shader):
           首先需要了解“光栅化技术”,就是OpenGL会把点,直线,三角形分解成大量的小片段,类似像素,每个片段上有红,绿,蓝,透明度的颜色分量,每一个小片段根据不同的红,绿,蓝,透明度组合最终产生不同的颜色。这些小片段映射到屏幕上就是一个个像素,最终构成一幅美丽的图案。而片段着色器的作用就是告诉GPU每一个片段(像素)显示颜色的最终值。基于基本图元的每个片段,片段着色器都会被调用一次,如一个三角形被映射成1000个片段,则片段着色器会被调用1000次。
片段着色器simple_fragment_shader.glsl里代码如下:

precision mediump float; uniform vec4 u_Color;                                           void main()                         {                                   gl_FragColor = u_Color;                                         }

           precision mediump float; 是用来描述float精度的,就像Java里面双精度,单精度一样。OpenGL里面精度有三种是lowp,mediump ,highp分别是低,中,高精度。只有在某些硬件上才指定为highp。其实在顶点着色器中也有精度,只是没有指定使用默认的highp,因为顶点着色器描述的是位置坐标所以默认使用的精度为最高级别,而片段着色器处于性能和质量权衡同时考虑到不同厂家兼容性使用mediump。
           uniform和顶点着色器的attribute 不同。顶点着色器的每个顶点都需要设置用attribute 来描述坐标位置。而片段着色器的uniform设置一次,如果后面不赋新值在新的片段里面依旧是之前的值,这是一种状态机。u_Color也是4个分量,分别是红,绿,蓝,透明度。main里,把我们在uniform里面定义的颜色值赋值到OpenGL的变量gl_FragColor作为当前片段最终颜色值。下文中我们将继续深入opengl es着色器原理和具体过程,参考:

本文代码下载:
https://github.com/pangrui201/OpenGlesProject/tree/master/OpenGlesProject_lesson2

原创粉丝点击