Android openGl开发详解(一)——绘制简单图形

1. What? openGl是什么?openGl ES又是什么?
相信很多人从事开发的都或多或少听到过有关OpenGl这个东西,但是平时用的少,只知道有这么个东西,而且学起来不简单,所以大多数人都不能讲出个个所以然来。

官方对OpenGl的描述为:

OpenGL(Open Graphics Library开发图形接口)是一个跨平台的图形API,用于指定3D图形处理硬件中的标准软件接口。

OpenGl的前身是SGI公司为其图形工作站开发的IRIS GL,后来因为IRIS GL的移植性不好,所以在其基础上,开发出了OpenGl。OpenGl一般用于在图形工作站,PC端使用,由于性能各方面原因,在移动端使用OpenGl基本带不动。为此,Khronos公司就为OpenGl提供了一个子集,OpenGl ES(OpenGl for Embedded System)

什么是OpenGl ES呢?

OpenGl ES是免费的跨平台的功能完善的2D/3D图形库接口的API,是OpenGL的一个子集。

移动端使用到的基本上都是OpenGl ES,当然Android开发下还专门为OpenGl提供了android.opengl包,并且提供了GlSurfaceView,GLU,GlUtils等工具类。

2. How? Android中的openGL 如何使用?
在了解OpenGl的使用之前,我们需要了解两个基本类别的Android框架:GlSurfaceView和GlSurfaceView.Renderer

3. GlSurfaceView是什么? GLSurfaceView的作用是什么? GLSurfaceView如何使用?
GlSurfaceView从名字就可以看出,它是一个SurfaceView,看源码可知,GlSurfaceView继承自SurfaceView。并增加了Renderer.它的作用就是专门为OpenGl显示渲染使用的。

GLSurfaceView的使用方法: 
可以通过创建的实例使用这个类,并增加你的Renderer.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GLSurfaceView glSurfaceView = new GLSurfaceView(this);
        glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {

            }

            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {

            }

            @Override
            public void onDrawFrame(GL10 gl) {

            }
        });
        setContentView(glSurfaceView);
    }

4. GlSurfaceView.Renderer是什么?GLSurfaceView.Renderer的作用?GLSurfaceView.Renderer的用法?
该接口定义了用于绘制在图形所需的方法GLSurfaceView。你必须提供这个接口作为一个单独的类的实现,并将其连接到您的GLSurfaceView使用实例 GLSurfaceView.setRenderer()。如上面的代码所示。作用就是提供各种渲染方法,OpenGl的渲染操作均在此接口中实习。下面说下实现该接口的方法含义:

onSurfaceCreated():系统调用这个方法一次创建时GLSurfaceView。使用此方法来执行只需要发生一次的操作,比如设置OpenGL的环境参数或初始化的OpenGL图形对象。
onDrawFrame():系统调用上的每个重绘此方法GLSurfaceView。使用此方法作为主要执行点用于绘制(和重新绘制)的图形对象。
系统调用此方法时的GLSurfaceView几何形状的变化,包括尺寸变化GLSurfaceView或设备屏幕的取向。例如,当设备从纵向变为横向的系统调用这个方法。使用此方法可以在变化做出反应GLSurfaceView容器。
介绍完了GlSurfaceView和GlSurfaceView.renderer之后,接下来说下如何使用GlSurfaceView; 
1. 创建一个GlSurfaceView 
2. 为这个GlSurfaceView设置渲染 
3. 在GlSurfaceView.renderer中绘制处理显示数据

5. OpenGl的简单使用实例(绘制一个三角形)
在使用OpenGl之前,需要在AndroidManifest.xml中设置OpenGl的版本:这里我们使用的是OpenGl ES 2.0,所以需要添加如下说明:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

使用GLSufaceView(上面有介绍)
具体在GlSurfaceView.Renderer中的绘制步骤: 
设置视图展示窗口(viewport) :在onSurfaceChanged中调用GLES20.glViewport(0, 0, width, height);
创建图形类,确定好顶点位置和图形颜色,将顶点和颜色数据转换为OpenGl使用的数据格式
加载顶点找色器和片段着色器用来修改图形的颜色,纹理,坐标等属性
创建投影和相机视图来显示视图的显示状态,并将投影和相机视图的转换传递给着色器。
创建项目(Program),连接顶点着色器片段着色器。
将坐标数据传入到OpenGl ES程序中:
使用OpenGl修改背景颜色
创建一个GlSurfaceView,并为其设置渲染OneGlRenderer;

public class OneGlSurfaceView extends GLSurfaceView {
    private final OneGlRenderer mRenderer;
    public OneGlSurfaceView(Context context) {
        super(context);
        // Create an OpenGL ES 2.0 context
        setEGLContextClientVersion(2);

        mRenderer = new OneGlRenderer();

        // Set the Renderer for drawing on the GLSurfaceView
        setRenderer(mRenderer);
    }
}

实现渲染接口

public class OneGlRenderer implements GLSurfaceView.Renderer {
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // Set the background frame color
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }

    public void onDrawFrame(GL10 unused) {
        // Redraw background color
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }

    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }
}

展示渲染后的GlSurfaceView

public class OneOpenGlActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        OneGlSurfaceView glSurfaceView = new OneGlSurfaceView(this);
        setContentView(glSurfaceView);
    }
}

效果如下:就是简单给GlSurfaceView渲染一层黑色。

使用OpenGl绘制几何图形
一:图形创建

创建一个几何图形(这里主要列举三角形和正方形),需要注意一点,我们设置图形的顶点坐标后,需要将顶点坐标转为ByteBuffer,这样OpenGl才能进行图形处理。

三角形图形创建:

public class Triangle {

    private FloatBuffer vertexBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // in counterclockwise order:
             0.0f,  0.5f, 0.0f, // top
            -0.5f, -0.5f, 0.0f, // bottom left
             0.5f, -0.5f, 0.0f  // bottom right
    };

    // Set color with red, green, blue and alpha (opacity) values
    float color[] = { 255, 0, 0, 1.0f };

    public Triangle() {
       // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个float占4个字节
        ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * 4);
        // 数组排列用nativeOrder
        bb.order(ByteOrder.nativeOrder());
        // 从ByteBuffer创建一个浮点缓冲区
        vertexBuffer = bb.asFloatBuffer();
        // 将坐标添加到FloatBuffer
        vertexBuffer.put(triangleCoords);
        // 设置缓冲区来读取第一个坐标
        vertexBuffer.position(0);
    }
}

正方型图:

public class Square {

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
             0.5f, -0.5f, 0.0f,   // bottom right
             0.5f,  0.5f, 0.0f }; // top right

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

    public Square() {
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个float占4个字节
        ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // 初始化ByteBuffer,长度为arr数组的长度*2,因为一个short占2个字节
        ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
}

创建图形基本没什么技巧可言,按部就班就行了,为什么数据需要转换格式呢?主要是因为Java的缓冲区数据存储结构为大端字节序(BigEdian),而OpenGl的数据为小端字节序(LittleEdian),因为数据存储结构的差异,所以,在Android中使用OpenGl的时候必须要进行下转换。当然,一般我们在使用的时候都会做个简单的工具类。这里提供几个简单的封装。(占几个字节就初始化ByteBuffer长度的时候*几)

将int[]转成IntBuffer

private IntBuffer intBufferUtil(int[] arr)
    {
        IntBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asIntBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }

将float[]数组转为OpenGl 所需要的FloatBuffer

private FloatBuffer floatBufferUtil(float[] arr)
    {
        FloatBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asFloatBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }

当然,依葫芦画瓢,如何将short[]转ShortBuffer这个就照着写就ok了

 private ShortBuffer shortBufferUtil(short[] arr){
        ShortBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*2,因为一个short占2个字节
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                arr.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        mBuffer = dlb.asShortBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }

创建完形状之后,我们就要进行我们的第二步了,将这些形状渲染到GlSurfaceView中去。主要可分为下面几步: 
1. 首先我们需要在GlSurfaceView.Renderer中初始化需要渲染的几何图形

private Triangle mTriangle;
    private Square   mSquare;
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // 设置背景颜色
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        // 初始化triangle
        mTriangle = new Triangle();
        // 初始化 square
        mSquare = new Square();
    }

二.:绘制图形,因为需要提供很多细节的图形渲染管线,所以绘制图形前至少需要一个顶点着色器来绘制形状和一个片段着色器的颜色,形状。这些着色器必须被编译,然后加入到一个OpenGL ES程序,然后将其用于绘制形状。简单介绍下这几个概念: 
- 顶点着色器(Vertex Shader)顶点着色器是GPU上运行的小程序,由名字可以知道,通过它来处理顶点,他用于渲染图形顶点的OpenGL ES图形代码。顶点着色器可用来修改图形的位置,颜色,纹理坐标,不过不能用来创建新的顶点坐标。 
- 片段着色器(Fragment Shader ) 用于呈现与颜色或纹理的形状的面的OpenGL ES代码。 
- 项目(Program) -包含要用于绘制一个或多个形状着色器的OpenGL ES的对象。

下面给Triangle类定义一个基本的着色器代码:

public class Triangle {

    private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
        "void main() {" +
        "  gl_Position = vPosition;" +
        "}";

    private final String fragmentShaderCode =
        "precision mediump float;" +
        "uniform vec4 vColor;" +
        "void main() {" +
        "  gl_FragColor = vColor;" +
        "}";

    ...
}

当然,上面我们创建了着色器的编译代码,代码编写完成,需要写个方法来执行这段代码,这里我们在渲染器中写一个如下方法来执行着色器代码:

public static int loadShader(int type, String shaderCode){

        // 创造顶点着色器类型(GLES20.GL_VERTEX_SHADER)
        // 或者是片段着色器类型 (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);
        // 添加上面编写的着色器代码并编译它
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }

这里有一点需要注意,因为着色器的代码执行是很昂贵滴,所以避免多次执行,需要我们一般将执行代码的逻辑写带图形类的构造方法中。比如上面的Triangle,我们就这么写:

private final int mProgram;
public Triangle() {
        ... ...//数据转换
        int vertexShader = OneGlRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = OneGlRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // 创建空的OpenGL ES程序
        mProgram = GLES20.glCreateProgram();

        // 添加顶点着色器到程序中
        GLES20.glAttachShader(mProgram, vertexShader);

        // 添加片段着色器到程序中
        GLES20.glAttachShader(mProgram, fragmentShader);

        // 创建OpenGL ES程序可执行文件
        GLES20.glLinkProgram(mProgram);
    }

最后,所有绘制的所有基本配置都配置完成之后,我们来写绘制图形的方法,我们在图形类(Triangle)中创建一个绘制的方法onDraw(),可以在onDraw()方法中设置绘制逻辑。

private int mPositionHandle;
    private int mColorHandle;

    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    public void draw() {
        // 将程序添加到OpenGL ES环境
        GLES20.glUseProgram(mProgram);

        // 获取顶点着色器的位置的句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // 启用三角形顶点位置的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        //准备三角形坐标数据
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // 获取片段着色器的颜色的句柄
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // 设置绘制三角形的颜色
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        // 绘制三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // 禁用顶点数组
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }


完成上面所有步骤,只需要在GlSurfaceView.Renderer的onDrawFrame()方法中调用图形类的绘制方法即可(上面的onDraw()):

 public void onDrawFrame(GL10 unused) {
     mTriangle.draw();
 }

最后的呈现效果如下图所示: 


运用投影和相机视图
通常情况下,OpenGl中展示的视图和在Android上显示的图形会有偏差。借用官方图片:


当然我们可以通过矩阵转换来解决这种问题,让OpenGl上的视图在任何android设备上显示的比例都是一样的,这里说下什么是投影和相机视图:

投影的定义
使用OpenGl绘制的3D图形,需要展示在移动端2D设备上,这就是投影。Android OpenGl ES中有两种投影方式:一种是正交投影,一种是透视投影:

正交投影投影物体的带下不会随观察点的远近而发生变化,我们可以使用下面方法来执行正交投影:

Matrix.orthoM (float[] m,           //接收正交投影的变换矩阵
                int mOffset,        //变换矩阵的起始位置(偏移量)
                float left,         //相对观察点近面的左边距
                float right,        //相对观察点近面的右边距
                float bottom,       //相对观察点近面的下边距
                float top,          //相对观察点近面的上边距
                float near,         //相对观察点近面距离
                float far)          //相对观察点远面距离

透视投影:随观察点的距离变化而变化,观察点越远,视图越小,反之越大,我们可以通过如下方法来设置透视投影:

Matrix.frustumM (float[] m,         //接收透视投影的变换矩阵
                int mOffset,        //变换矩阵的起始位置(偏移量)
                float left,         //相对观察点近面的左边距
                float right,        //相对观察点近面的右边距
                float bottom,       //相对观察点近面的下边距
                float top,          //相对观察点近面的上边距
                float near,         //相对观察点近面距离
                float far)          //相对观察点远面距离

相机视图
什么是相机视图?简单来说生活中我们拍照,你站的高度,拿相机的位置,姿势不同,拍出来的照片也就不一样,相机视图就是来修改相机位置,观察方式以及相机的倾斜角度等属性。我们可以通过下面方法来修改相机视图属性:

Matrix.setLookAtM (float[] rm,      //接收相机变换矩阵
                int rmOffset,       //变换矩阵的起始位置(偏移量)
                float eyeX,float eyeY, float eyeZ,   //相机位置
                float centerX,float centerY,float centerZ,  //观察点位置
                float upX,float upY,float upZ)  //up向量在xyz上的分量

转换矩阵(变换矩阵)
转换矩阵用来做什么的呢?是否记得上面我们绘制的图形坐标需要转换为OpenGl中能处理的小端字节序(LittleEdian),没错,转换矩阵就是用来将数据转为OpenGl ES可用的数据字节,我们将相机视图和投影设置的数据相乘,便得到一个转换矩阵,然后我们再讲此矩阵传给顶点着色器,具体使用方法及参数说明如下:

Matrix.multiplyMM (float[] result, //接收相乘结果
                int resultOffset,  //接收矩阵的起始位置(偏移量)
                float[] lhs,       //左矩阵
                int lhsOffset,     //左矩阵的起始位置(偏移量)
                float[] rhs,       //右矩阵
                int rhsOffset)     //右矩阵的起始位置(偏移量)

下面简单讲解下如何使用投影和相机视图来实现矩阵变换并传递给顶点着色器;定义一个投影:

    // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float[] mMVPMatrix = new float[16];
    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES20.glViewport(0, 0, width, height);

        float ratio = (float) width / height;        // 这个投影矩阵被应用于对象坐标在onDrawFrame()方法中
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }

定义一个相机视图

@Override
public void onDrawFrame(GL10 unused) {
    ...
    // Set the camera position (View matrix)
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    // Calculate the projection and view transformation
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);    // Draw shape
    mTriangle.draw(mMVPMatrix);
}

修改图形类执行代码

public class Triangle {

    private final String vertexShaderCode =
        // This matrix member variable provides a hook to manipulate
        // the coordinates of the objects that use this vertex shader
        "uniform mat4 uMVPMatrix;" +
        "attribute vec4 vPosition;" +
        "void main() {" +
        // the matrix must be included as a modifier of gl_Position
        // Note that the uMVPMatrix factor *must be first* in order
        // for the matrix multiplication product to be correct.
        "  gl_Position = uMVPMatrix * vPosition;" +
        "}";

    // Use to access and set the view transformation
    private int mMVPMatrixHandle;    ...
}

投影和相机视图代码到图形类的绘制方法中去onDraw()

 public void draw(float[] mvpMatrix){
       ... ...
        // 得到形状的变换矩阵的句柄
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

        // 将投影和视图转换传递给着色器
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

        // 画三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // 禁用顶点数组
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }

做完这些,我们就能得到如下图:

没错,这才没有变形的视图。到这里,基本的通过OpenGl绘制简单图形就over了,下面我们讲解下如何添加一些交互动作。

添加动作
前面都是简单的动作介绍,使用OpenGl在屏幕上绘制对象是使用openGl的基本功。下面我来说下如何添加旋转形状。使用OpenGl的描绘对象是相对简单的,首先需要在渲染器中创建一组旋转矩阵,然后使用之前提到过的投影和相机视图变换矩阵结合起来使用:

private float[] mRotationMatrix = new float[16];
public void onDrawFrame(GL10 gl) {
    float[] scratch = new float[16];

    ...

    // 创建一个旋转矩阵
    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);
    Matrix.setRotateM(mRotationMatrix, 0, angle, 0, 0, -1.0f);

    // 将旋转矩阵与投影和相机视图组合在一起
    // Note that the mMVPMatrix factor *must be first* in order
    // for the matrix multiplication product to be correct.
    Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);

    // Draw triangle
    mTriangle.draw(scratch);
}

运行效果图如下:

修改顶点颜色
一个颜色是不是太单调了?如何让做成多彩的呢?接下来我们来做一个多彩三角形,如何来做一个多彩三角形?我们通过顶点着色器来做。基于上面的代码,我们只需要做一点点改动,下面是基本步骤: 
1. 修改着色器代码 
2. 将颜色值修改为float数组并转为floatBuffer 
3. 将获取的floatBuffer传递给顶点着色器。

修改着色器代码:

 private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "uniform mat4 uMVPMatrix;"+
                    "varying  vec4 vColor;"+
                    "attribute vec4 aColor;"+
                    "void main() {" +
                    "  gl_Position = uMVPMatrix*vPosition;" +
                    "  vColor=aColor;"+
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "varying vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";

shader的变量类型(uniform,attribute和varying)的区别
关于shader的变量类型(uniform,attribute和varying)的区别及使用,下面做下说明: 
1. uniform:uniform变量在vertex和fragment两者之间声明方式完全一样,则它可以在vertex和fragment共享使用。(相当于一个被vertex和fragment shader共享的全局变量)uniform变量一般用来表示:变换矩阵,材质,光照参数和颜色等信息。在代码中通过GLES20.glGetUniformLocation(int program, String name)来获取属性值。并通过 GLES20.glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset);方法将数据传递给着色器。 
2. attribute:这个变量只能在顶点着色器中使用(vertex Shader),用来表示顶点的数据,比如顶点坐标,顶点颜色,法线,纹理坐标等。在绘制的时候通过GLES20.glGetAttribLocation(int program, String name)来获取变量值,通过 GLES20.glEnableVertexAttribArray(int index)来启动句柄,最后通过 GLES20.glVertexAttribPointer(int indx,int size,int type,boolean normalized,int stride,java.nio.Buffer ptr)来设置图形数据。 
3. varying变量:这个变量只能用来在vertex和fragment shader之间传递数据时使用,不可以通过代码获取其变量值。

接来下我们进行数据转换:

    float color[] = {
            1.0f, 0f, 0f, 1.0f ,
            0f, 1.0f, 0f, 1.0f ,
            0f, 0f, 1.0f, 1.0f
    };
        public Triangle() {
           ... ...
          ByteBuffer dd = ByteBuffer.allocateDirect(
                    color.length * 4);
            dd.order(ByteOrder.nativeOrder());
            colorBuffer = dd.asFloatBuffer();
            colorBuffer.put(color);
            colorBuffer.position(0);
        }

最后我们需要获取着色器的句柄并设置着色器的颜色:

public void draw(float[] mvpMatrix){
        ... ... 
         /* // 获取片段着色器的vColor成员的句柄
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // 设置绘制三角形的颜色
        GLES20.glUniform4fv(mColorHandle, 1, colorBuffer, 0);*/

        //获取片元着色器的vColor成员的句柄
        mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
        //设置绘制三角形的颜色
        GLES20.glEnableVertexAttribArray(mColorHandle);
        GLES20.glVertexAttribPointer(mColorHandle,4,
                GLES20.GL_FLOAT,false,
                0,colorBuffer);
                ... ...
}

 


6. 参考链接:
opengl官网
opengl的环境搭建及基本教程
7. 项目地址:
AserbaosAndroid此项目为博主所有的系列学习的代码汇总项目,该文章的代码位于:opengl/OneOpenGl/OneOpenGlActivity

本书共分两篇,第一篇介绍了Android 3D游戏开发的基础知识,主要对OpenGL ES的相关内容进行了介绍。   章 名主 要 内 容   第1章 英雄还看今朝—Android简介本章介绍了市场上主流的手机平台,同时也分析了未来手机平台的发展趋势及Android平台的前景   第2章 数风流人物—当前流行游戏类型简介本章以分类的方式简要地介绍了当前流行的游戏的玩法,游戏的视觉效果,游戏的设计及《仙剑》等著名游戏的历史   第3章 不积跬步,无以至千里—游戏开发基础知识本章初步介绍了游戏开发的基础知识   第4章 千里之行,始于足下—3D开发基础知识本章介绍了3D开发中的基础知识,包括OpenGL ES的介绍及OpenGL ES中绘制模型的原理,并通过点、线和三角形的绘制介绍了OpenGL ES中模型的几种绘制方式。最后介绍了3D场景中常用的两种投影方式,并通过例子比较了这两种投影的区别   第5章 愿君多采撷,此物最相思—光照效果的开发本章介绍了光照的基础知识,包括环境光、散射光及镜面光   第6章 为伊消得人憔悴——纹理映射本章主要介绍了纹理的基础知识,以及纹理的不同拉伸方式和纹理过滤高级技术,从绘制三角形开始到绘制地月系,可能会经历很长时间,但是这对以后的学习是有帮助的   第7章 海阔凭鱼跃,天高任鸟飞—3D基本形状的构建在本章中介绍了圆柱体、圆锥体、圆环、抛物面、双曲面和螺旋面在OpenGL ES中的渲染方法。这些基本形状在3D世界中应用广泛,在构造一些复杂物体时,经常会运用这些基本形状来进行拼装组合   第8章 执子之手,与子偕老—坐标变换本章介绍了坐标变换的应用。绘制3D场景的过程,主要是旋转和平移操作的组合,通过合理的堆栈操作,就比较容易绘制出所需的3D场景   第9章 孤帆远影碧空尽—摄像机与雾特效在本章中,首先对摄像机及其配置做了介绍。摄像机在3D编程中至关重要,没有正确的配置,摄像机可能不能获得想要的场景效果。然后对雾特效做了具体介绍,应用雾特效可以使场景更加逼真,并且可以减少场景渲染量来提高性能   第10章 假作真时真亦假—混合本章主要为读者介绍了混合,从混合的背景知识到如何配置源因子和目标因子。在介绍源因子和目标因子的时候,向读者介绍了一些预定义常量和一些常用的组合方式,以及如何启用混合   第11章 蓦然回首,那人却在灯火阑珊处—3D高级技术本章主要为读者介绍了3D的一部分高级技术。每一项技术通过讲解其原理和案例,使读者对3D高级技术有一定的了解   第12章 心有灵犀一点通—传感器在本章中,向读者介绍了Android中传感器的相关知识。包括传感器的种类、配置,并且着重介绍了姿态传感器的应用   第13章 千锤万凿出深山—游戏中的数学与物理在本章中对3D游戏中可能会用到的数学及物理知识进行了简单的介绍,这在3D游戏开发中是相当重要的。游戏中的核心算法,基本上都要用到数学和物理知识。一款游戏的性能很大程度上取决于游戏设计的算法   第14章 山舞银蛇,原驰蜡象—AI基本理念本章主要介绍了AI、AI引擎的基本组成与设计,以及游戏AI中图的搜索和模糊逻辑,其中游戏AI中图的搜索为本章的重点。在本章中详细介绍了5种算法的原理与实现   第15章 独上高楼,望尽天涯路—开发小秘籍本章介绍了地图设计器、多键技术、虚拟键盘、查找表技术、状态机、AABB边界框、穿透效应、拾取技术,以及天空盒和天空穹在OpenGL ES中的应用 第二篇以7个比较大的案例来说明Android平台下3D游戏的开发流程,通过这7个案例的讲解,读者对3D游戏的开发将会有更深层次的理解。   章 名主 要 内 容   第16章 体育类游戏——《疯狂投篮》本章介绍了Android 3D游戏《疯狂投篮》的开发。通过该案例向读者介绍了在Android平台下进行3D游戏开发的相关知识和基本流程,并对游戏开发中的编程技巧进行了介绍,并主要介绍了篮球与地面、墙面及篮框的碰撞检测及运动动画的实现方法   第17章 益智类游戏——《旋转积木》本章介绍了Android 3D游戏《旋转积木》的开发。主要介绍了积木旋转的不同状态的实现方法和地图设计器的应用   第18章 休闲类游戏——《摩天大楼》本章介绍了Android 3D游戏《摩天大楼》的开发。主要介绍了楼层与楼层之间的衔接与碰撞及掉落后翻转动画的实现   第19章 动作类游戏——《3D空战》本章介绍了Android 3D游戏《3D空战》的开发。主要介绍了飞机的构造方法和我方战机与敌方战机的操控及动画实现   第20章 桌面类游戏——《激情台球》本章介绍了Android 3D游戏《激情台球》的开发。主要介绍了台球与台球的碰撞检测实现、台球与球桌的碰撞检测实现和进球的判定实现   第21章 射击类游戏——《抢滩登陆》本章介绍了Android 3D游戏《抢滩登陆》的开发。主要运用了灰度图生成技术并且主要介绍了坦克运动的实现方法及炮弹碰撞检测的实现   第22章 竞技类游戏——《乡村飙车》本章介绍了Android 3D游戏《乡村飙车》的开发。主要介绍了运用分层绘制和拼接绘制的策略进行场景的优化绘制,并且对场景部件进行了分类控制   本书面向的读者   本书的内容详细,且几乎涵盖了Android 3D游戏开发所有相关的技术,并向读者介绍了真实项目的开发流程,主要面向以下读者。   Android的初学者   本书详细介绍了OpenGL ES的基础知识,并对Android 3D游戏程序的开发进行了介绍。作为一名Android的初学者,通过本书的学习可以快速全面地掌握Android 3D游戏开发的相关知识,稳健地步入Android 3D游戏开发人员的行列。   有一定Android基础且希望学习Android 3D游戏开发的读者   有一定Android基础的读者通过阅读本书的前半部分便可快速掌握OpenGL ES的基础知识,然后通过7个真实案例的学习迅速掌握Android平台下应用程序的开发。   在职的开发人员
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值