opengles(二)画个三角型

首先我们需要知道opengles在屏幕上的坐标,即中间坐标点是x=0,y=0; x轴往右是正,往左是负,y轴往上是正,往下是负,且x, y的范围都是-1到1;所以我们先定义坐标数组,顺序是逆时针方向:

private static final float[] TRIANGLE_VERTEX_COORD = {0, 0.5f,
            -0.5f, -0.5f,
            0.5f, -0.5f};

为了最大限度地提高效率,我们可以将这些坐标写入FloatBuffer,然后将其传递给OpenGL ES图形管道进行处理:

    private FloatBuffer mTriangleCoordBuffer;

    public Triangle() {
        // 数组转化为buffer,提高opengles性能
        mTriangleCoordBuffer = ByteBuffer.allocateDirect(TRIANGLE_VERTEX_COORD.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mTriangleCoordBuffer.put(TRIANGLE_VERTEX_COORD);

        // 设置从第一位开始读
        mTriangleCoordBuffer.position(0);
    }       

现在我们要把这些坐标数据传给opengles图形管道,那么我们怎么传呢,这里需要用到shader,即是着色器,opengles里分为两种shader,一种是vertex shader(我们的顶点坐标就是传到这里), 另一种是fragment shader(我们的颜色数据传到这),并且由opengles特有的语言把这两种shader组织起来,下面我们来看看例子:

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

    private static final String FRAGMENT_SHADER_SOURCE = "uniform vec4 vColor;" +
            "void main() {" +
            "   gl_FragColor = vColor;" +
            "}";

它们的写法类似于C语言,叫做GLES语言,当有一些自己独有的东西;
attribute仅在vertex shader中使用,常用于指出vertex shader的输入数据;
vec4代表这个向量里有四个float型的数据;
vPosition是我们自己起的一个变量名;
gl_Position是opengles的特有名词,指的是顶点的坐标;
uniform用于存储shader需要的各种数据,例如转换矩阵、光线参数和颜色等。可以被vertex shader 和 fragment shader 共享使用;
gl_FragColor也是opengles特有的名词,指的是颜色;

好了,写完GLES代码,我们需要把代码编译后传给opengles,下面看下该怎么做:
我们先写一个加载shader的方法

    private int getShader(int type, String source) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, source);
        GLES20.glCompileShader(shader);

        // 获取编译的状态
        final int[] compileStatus = new int[1];
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,
                compileStatus, 0);

        Log.v(TAG, "代码编译结果:" + "\n" + source
                    + "\n:" + GLES20.glGetShaderInfoLog(shader));

        if (compileStatus[0] == 0) {
            Log.w(TAG, "编译失败!.");
            return 0;
        }

        return shader;
    }

接着把得到的shader和program连接,这样opengles就知道该怎么处理我们的着色逻辑了;

private int mVertexShader, mFragmentShader;

public Triangle() {
        // ...
        // 得到编译后的shader
        mVertexShader = getShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
        mFragmentShader = getShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);

        // 创建并连接program
        int program = GLES20.glCreateProgram();
        GLES20.glAttachShader(program, mVertexShader);
        GLES20.glAttachShader(program, mFragmentShader);
        GLES20.glLinkProgram(program);
        GLES20.glUseProgram(program);
}

我们之前定义好的顶点buffer该怎么传进去给opengles呢,这里我们就需要先找到对应的变量的locaction:

    private static final String POSITION_NAME = "vPosition";
    private static final String COLOR_NAME = "vColor";

    public Triangle() {
        // ...
        // 找到对应变量的index
        mPositionLocation = GLES20.glGetAttribLocation(program, POSITION_NAME);
        mFragColorLocation = GLES20.glGetUniformLocation(program, COLOR_NAME);
    }

传入数据,就可以基本完成我们的绘图了:

    public void draw() {
        // 由于性能方面的考虑,opengles默认读取不到缓冲区里的数据,需要先enable后再读
        GLES20.glEnableVertexAttribArray(mPositionLocation);
        GLES20.glVertexAttribPointer(mPositionLocation, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false, 0, mTriangleCoordBuffer);

        GLES20.glUniform4f(mFragColorLocation, 0, 0.5f, 0, 1.0f);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_SIZE);

        // 调用完glDrawArrays后才能disable;
        GLES20.glDisableVertexAttribArray(mPositionLocation);
    }

这里给出较完整的代码:

public class MyRenderer implements GLSurfaceView.Renderer {

    private Triangle mTriangle;

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);

        mTriangle = new Triangle();
    }

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

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        mTriangle.draw();
    }
}
public class Triangle {
    private static final String TAG = "Triangle";

    // 三角形的三个顶点
    private static final float[] TRIANGLE_VERTEX_COORD = {0, 0.5f,
            -0.5f, -0.5f,
            0.5f, -0.5f};

    private static final int VERTEX_SIZE = 3;
    private static final int COORDS_PER_VERTEX = TRIANGLE_VERTEX_COORD.length / VERTEX_SIZE;

    private FloatBuffer mTriangleCoordBuffer;

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

    private static final String FRAGMENT_SHADER_SOURCE = "uniform vec4 vColor;" +
            "void main() {" +
            "   gl_FragColor = vColor;" +
            "}";

    private int mVertexShader, mFragmentShader;

    private static final String POSITION_NAME = "vPosition";
    private static final String COLOR_NAME = "vColor";

    private int mPositionLocation, mFragColorLocation;

    public Triangle() {
        // 数组转化为buffer,提高opengles性能
        mTriangleCoordBuffer = ByteBuffer.allocateDirect(TRIANGLE_VERTEX_COORD.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mTriangleCoordBuffer.put(TRIANGLE_VERTEX_COORD);
        // 设置从第一位开始读
        mTriangleCoordBuffer.position(0);

        // 得到编译后的shader
        mVertexShader = getShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
        mFragmentShader = getShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);

        // 创建并连接program
        int program = GLES20.glCreateProgram();
        GLES20.glAttachShader(program, mVertexShader);
        GLES20.glAttachShader(program, mFragmentShader);
        GLES20.glLinkProgram(program);
        GLES20.glUseProgram(program);

        // 找到对应变量的index
        mPositionLocation = GLES20.glGetAttribLocation(program, POSITION_NAME);
        mFragColorLocation = GLES20.glGetUniformLocation(program, COLOR_NAME);
    }

    private int getShader(int type, String source) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, source);
        GLES20.glCompileShader(shader);

        // 获取编译的状态
        final int[] compileStatus = new int[1];
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,
                compileStatus, 0);

        Log.v(TAG, "代码编译结果:" + "\n" + source
                    + "\n:" + GLES20.glGetShaderInfoLog(shader));

        if (compileStatus[0] == 0) {
            Log.w(TAG, "编译失败!.");
            return 0;
        }

        return shader;
    }

    public void draw() {
        // 由于性能方面的考虑,opengles默认读取不到缓冲区里的数据,需要先enable后再读
        GLES20.glEnableVertexAttribArray(mPositionLocation);
        GLES20.glVertexAttribPointer(mPositionLocation, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false, 0, mTriangleCoordBuffer);

        GLES20.glUniform4f(mFragColorLocation, 0, 0.5f, 0, 1.0f);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_SIZE);

        GLES20.glDisableVertexAttribArray(mPositionLocation);
    }

}

运行后的结果:
opengles(二)画个三角型_第1张图片

不过我们的三角型有点变形,我们定义的明明是等边三角型,但实际画出来的不是,这里是因为opengles的坐标和实际屏幕的坐标是不对应的,opengles的长和宽都是一样的,就是-1到1,实际屏幕的长和宽肯定不一样,没有正方形的手机屏吧,该如何解决呢?这里我们需要用到矩阵,下节再说。

你可能感兴趣的:(opengles)