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

阅读本文前如果是初次接触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

发布了46 篇原创文章 · 获赞 21 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/pangrui201/article/details/75119662