Android OpenGL ES入门 (一):Hello World 绘制三角形

前言

这是入门篇,让大家简单了解OpenGL ES,并且保证你能绘制出三角形。

能动手就不哔哔

大家都是小学生,手把手教学吧!下面直接上代码。
MainAcitivity

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //创建一个GLSurfaceView
        GLSurfaceView glSurfaceView = new GLSurfaceView(this);
        glSurfaceView.setEGLContextClientVersion(2);
        //设置自己的Render.Render 内进行图形的绘制
        glSurfaceView.setRenderer(new GLRenderer());
        setContentView(glSurfaceView);
    }

GLRenderer

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;

import com.example.openglcampaign.deep.Constant;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class GLRenderer implements GLSurfaceView.Renderer {
    public static final int BYTES_PER_FLOAT = 4;//每个浮点数:坐标个数* 4字节
    private FloatBuffer vertexBuffer;//顶点缓冲
    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;" +
                    "}";


    //顶点的坐标系
    private static float TRIANGLE_COORDS[] = {
            //Order of coordinates: X, Y, Z
            0.5f, 0.5f, 0.0f, // top
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f   // bottom right
    };
    private int mProgramObjectId;


    //在数组中,一个顶点需要3个来描述其位置,需要3个偏移量
    private static final int COORDS_PER_VERTEX = 3;
    private static final int COORDS_PER_COLOR = 0;

    //在数组中,描述一个顶点,总共的顶点需要的偏移量。这里因为只有位置顶点,所以和上面的值一样
    private static final int TOTAL_COMPONENT_COUNT = COORDS_PER_VERTEX + COORDS_PER_COLOR;
    //一个点需要的byte偏移量。
    private static final int STRIDE = TOTAL_COMPONENT_COUNT * BYTES_PER_FLOAT;

    // 颜色,rgba  更换颜色
    float TRIANGLE_COLOR[] = {0.5176471f, 0.77254903f, 0.9411765f, 1.0f};

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        //初始化顶点字节缓冲区
        ByteBuffer bb = ByteBuffer.allocateDirect(TRIANGLE_COORDS.length * 4);//每个浮点数:坐标个数* 4字节
        bb.order(ByteOrder.nativeOrder());//使用本机硬件设备的字节顺序
        vertexBuffer = bb.asFloatBuffer();// 从字节缓冲区创建浮点缓冲区
        vertexBuffer.put(TRIANGLE_COORDS);// 将坐标添加到FloatBuffer
        vertexBuffer.position(0);//设置缓冲区以读取第一个坐标


        //0.简单的给窗口填充一种颜色
        GLES20.glClearColor(1.0f, 0f, 0f, 0f);//rgba

        //在创建的时候,去创建这些着色器
        //1.根据String进行编译。得到着色器id
        int vertexShaderObjectId = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShaderObjectId = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

        //3.继续套路。取得到program
        mProgramObjectId = GLES20.glCreateProgram();
        //将shaderId绑定到program当中
        GLES20.glAttachShader(mProgramObjectId, vertexShaderObjectId);
        GLES20.glAttachShader(mProgramObjectId, fragmentShaderObjectId);

        //4.最后,启动GL link program
        GLES20.glLinkProgram(mProgramObjectId);


    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //在窗口改变的时候调用
        GLES20.glViewport(0, 0, width, height);//GL视口

    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //0.glClear()的唯一参数表示需要被清除的缓冲区。当前可写的颜色缓冲
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        GLES20.glUseProgram(mProgramObjectId);

        //1.根据我们定义的取出定义的位置
        int vPosition = GLES20.glGetAttribLocation(mProgramObjectId, "vPosition");
        //2.开始启用我们的position
        GLES20.glEnableVertexAttribArray(vPosition);
        //3.将坐标数据放入
        GLES20.glVertexAttribPointer(
                vPosition,  //上面得到的id
                COORDS_PER_VERTEX, //告诉他用几个偏移量来描述一个顶点
                GLES20.GL_FLOAT, false,
                STRIDE, //一个顶点需要多少个字节的偏移量
                vertexBuffer);

        //取出颜色
        int uColor = GLES20.glGetUniformLocation(mProgramObjectId, "vColor");

        //开始绘制
        //设置绘制三角形的颜色
        GLES20.glUniform4fv(
                uColor,
                1,
                TRIANGLE_COLOR,
                0
        );

        //绘制三角形.
        //draw arrays的几种方式
        //GL_TRIANGLES三角形
        //GL_TRIANGLE_STRIP三角形带的方式(开始的3个点描述一个三角形,后面每多一个点,多一个三角形)
        //GL_TRIANGLE_FAN扇形(可以描述圆形)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, TRIANGLE_COORDS.length / 3);
        //禁止顶点数组的句柄
        GLES20.glDisableVertexAttribArray(vPosition);


    }


    /**
     * 加载作色器
     *
     * @param type       着色器类型
     *                   顶点着色 {@link GLES20.GL_VERTEX_SHADER}
     *                   片元着色 {@link GLES20.GL_FRAGMENT_SHADER}
     * @param shaderCode 着色代码
     * @return 作色器
     */
    private static int loadShader(int type, String shaderCode) {
        int shader = GLES20.glCreateShader(type);//创建着色器
        if (shader == 0) {//加载失败直接返回
            return 0;
        }
        GLES20.glShaderSource(shader, shaderCode);//加载着色器源代码
        GLES20.glCompileShader(shader);//编译
        return shader;
    }

}

到目前位置,你的代码就可以绘制出来三角形了。

拆解

  1. loadShader 方法。
 /**
     * 加载作色器
     *
     * @param type       着色器类型
     *                   顶点着色 {@link GLES20.GL_VERTEX_SHADER}
     *                   片元着色 {@link GLES20.GL_FRAGMENT_SHADER}
     * @param shaderCode 着色代码
     * @return 作色器
     */
    private static int loadShader(int type, String shaderCode) {
        //1.根据类型,创建着色器
        int shader = GLES20.glCreateShader(type);
        if (shader == 0) {
            //加载失败直接返回
            return 0;
        }
        //2.加载着色器源代码
        GLES20.glShaderSource(shader, shaderCode);
        //3.编译(往下还有一个验证,我们先不处理)
        GLES20.glCompileShader(shader);
        return shader;
    }
  1. onSurfaceCreated 方法
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        //初始化顶点字节缓冲区
        ByteBuffer bb = ByteBuffer.allocateDirect(TRIANGLE_COORDS.length * 4);//每个浮点数:坐标个数* 4字节
        bb.order(ByteOrder.nativeOrder());//使用本机硬件设备的字节顺序
        vertexBuffer = bb.asFloatBuffer();// 从字节缓冲区创建浮点缓冲区
        vertexBuffer.put(TRIANGLE_COORDS);// 将坐标添加到FloatBuffer
        vertexBuffer.position(0);//设置缓冲区以读取第一个坐标


        //0.简单的给窗口填充一种颜色
        GLES20.glClearColor(1.0f, 0f, 0f, 0f);//rgba

        //在创建的时候,去创建这些着色器
        //1.根据String进行编译。得到着色器id
        int vertexShaderObjectId = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShaderObjectId = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

        //3.继续套路。取得到program
        mProgramObjectId = GLES20.glCreateProgram();
        //将shaderId绑定到program当中
        GLES20.glAttachShader(mProgramObjectId, vertexShaderObjectId);
        GLES20.glAttachShader(mProgramObjectId, fragmentShaderObjectId);

        //4.最后,启动GL link program
        GLES20.glLinkProgram(mProgramObjectId);
    }

总结:初始化相应的顶点数据,创建一个程序对象并链接着色器。

  1. onSurfaceChanged 方法
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //在窗口改变的时候调用
        GLES20.glViewport(0, 0, width, height);//GL视口
    }
  1. onDrawFrame 方法
     @Override
    public void onDrawFrame(GL10 gl) {
        //0.glClear()的唯一参数表示需要被清除的缓冲区。当前可写的颜色缓冲
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        GLES20.glUseProgram(mProgramObjectId);


        //1.根据我们定义的取出定义的位置
        int vPosition = GLES20.glGetAttribLocation(mProgramObjectId, "vPosition");
        //2.开始启用我们的position
        GLES20.glEnableVertexAttribArray(vPosition);
        //3.将坐标数据放入
        GLES20.glVertexAttribPointer(
                vPosition,  //上面得到的id
                COORDS_PER_VERTEX, //告诉他用几个偏移量来描述一个顶点
                GLES20.GL_FLOAT, false,
                STRIDE, //一个顶点需要多少个字节的偏移量
                vertexBuffer);

        //取出颜色
        int uColor = GLES20.glGetUniformLocation(mProgramObjectId, "vColor");

        //开始绘制
        //设置绘制三角形的颜色
        GLES20.glUniform4fv(
                uColor,
                1,
                TRIANGLE_COLOR,
                0
        );

        //绘制三角形.
        //draw arrays的几种方式
        //GL_TRIANGLES三角形
        //GL_TRIANGLE_STRIP三角形带的方式(开始的3个点描述一个三角形,后面每多一个点,多一个三角形)
        //GL_TRIANGLE_FAN扇形(可以描述圆形)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, TRIANGLE_COORDS.length / 3);
        //禁止顶点数组的句柄
        GLES20.glDisableVertexAttribArray(vPosition);

    }

总结:设置窗口和清除颜色缓冲区,加载几何形状和绘制图元。

扩展

openGL使用右手坐标。Z轴朝向屏幕外。我们看一下X和Y轴。

坐标轴

//顶点的坐标系
private static float TRIANGLE_COORDS[] = {
//Order of coordinates: X, Y, Z
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
原本认为是等腰的三角形,最后发现不是,就是因为坐标系的问题。

我们看一下归一化坐标系:


归一化坐标系

我们下一步借助矩阵也可以在手机上绘制出这样的三角形。

知识延伸

    private final String vertexShaderCode =//顶点着色代码
            "attribute vec4 vPosition;" +
                    "void main() {" +
                    "  gl_Position = vPosition;" +
                    "}";

为什么 vPosition是使用vec4 ,这是一个比较有意思的问题,一下是百度的答案。

由于3d图形用到了 4x4的矩阵(4行4列),矩阵乘法要求 nxm * mxp(n行m列 乘 m行p列)才能相乘,注意m是相同的,所以 1x4 * 4x4 才能相乘。
所以是vec4而不是vec3。

至于为什么 4x4 看下那些 投影矩阵的演算过程就知道了。
至于你说的多出来的那一位,
如果是点坐标的话是 1.0
position 是位置所以应该是 (x,y,z,1.0f)

如果是 方向向量 ,也就是 代表的不是点 而是一个方向 则是 0.0 ,也就是 (x,y,z,0.0f)。
这也是 要与矩阵进行乘法所决定的。
建议学一些矩阵的运算,既然要用到 3d ,学矩阵是很有必要的,很多计算用到了矩阵
而且现在的cpu 也提供了 mmx来做矩阵的运算,可见矩阵对3d的作用很大。

参考:https://zhidao.baidu.com/question/1817876033322344268.html

你可能感兴趣的:(Android OpenGL ES入门 (一):Hello World 绘制三角形)