Opengl ES(三):画一个三角形之显示到屏幕

本文详细介绍如何使用OpenGL ES在Android设备上绘制一个三角形,包括顶点数据的准备、着色器的编写与编译、以及最终的图形渲染过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

###

我们已经利用opengl改变了背景颜色  Opengl ES(一):第一个例子

并且创造了一个三角形  Opengl ES(二):画一个三角形之创造一个三角形

接下来我们就将创造的三角形输出显示

代码参考https://developer.android.google.cn/training/graphics/opengl/draw

###

由于需要自己编写和加载着色器,这个过程的复杂程度可能超乎你的想象,如果你没有接触过,最好有心理准备。

###

我们已经创造了一种三角形,它的信息都被封存在了Triangle类中

public class Triangle {
 
    private FloatBuffer vertexBuffer;
 
    // 数组中每个顶点的坐标数(即维数)
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // in counterclockwise order:
             0.0f,  0.622008459f, 0.0f, // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
             0.5f, -0.311004243f, 0.0f  // bottom right
    };
    // 颜色信息,分别是RGB和alpha通道的归一化值
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
 
    public Triangle() {
        // 初始化顶点数据Buffer
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (一个float型4字节)
                triangleCoords.length * 4);
        // 字节序使用native order
        bb.order(ByteOrder.nativeOrder());
 
        // 将ByteBuffer转换为浮点型FloatBuffer
        vertexBuffer = bb.asFloatBuffer();
        // 将顶点数据添加到Buffer
        vertexBuffer.put(triangleCoords);
        // Buffer位置调整到开头
        vertexBuffer.position(0);
    }
}

显然我们需要实例化它,之前说过显示内容的设计基本是在GLSurfaceView.Renderer中,我们之前在修改背景时编写的MyGLRenderer类如下

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

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

public class MyGLRenderer implements GLSurfaceView.Renderer {
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //设置背景颜色,参数是RGB和alpha通道值的归一值,即范围为0-1,我设置的是橙色
        GLES20.glClearColor(1.0f, 0.6f, 0f, 1.0f);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //调整窗口大小
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //把窗口颜色用刚刚设定的值(glClearColor)刷洗
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }
}

再复习一下三个方法函数的作用

显然,我们需要在surface创建的时候就初始化我们的三角形实例,也就是在onSurfaceCreated()中添加

mTriangle = new Triangle();

接着我们来完成顶点着色器和片元着色器,顶点着色器用于渲染顶点形状,片元着色器用于渲染纹理或颜色。

这两个着色器是使用opengl的着色器语言(OpenGL Shading Language (GLSL) )实现,具体着色器语言规则建议单独学习,这里直接给出

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;" +
    "}";

之后只需要将这两个命令交付给opengl即可,也就是编译shader并链接到程序,但这个过程耗费资源十分多,所以要尽量避免多次调用,一般而言就只在创建绘制图形实例时做一次,我们就在实例化三角形时完成这个过程,所以上面的shader信息也可以封装在Triangle类中。

那么怎么交付给opengl来编译这两个shader呢?代码如下

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

    // 创建顶点着色器的type是 GLES20.GL_VERTEX_SHADER
    // 创建片元着色器的type是 GLES20.GL_FRAGMENT_SHADER
    int shader = GLES20.glCreateShader(type);

    // 传递着色器代码并编译着色器
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}

我们将loadShader()这个加载函数放在MyGLRenderer中

接下来我们需要调用loadShader()函数来获得编译好的shader,并链接到程序,就像刚刚说的,在实例化三角形类时完成即可,故可在Triangle类的构造函数中添加代码。

        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                                        vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                                        fragmentShaderCode);

        // 创建空的Opengl ES program
        mProgram = GLES20.glCreateProgram();

        // 将顶点着色器加入program
        GLES20.glAttachShader(mProgram, vertexShader);

        // 将片元着色器加入program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // 创建opengl ES可执行的文件
        GLES20.glLinkProgram(mProgram);

接下来就是调用已经可以执行的mProgram来渲染了。调用的方法函数最好也放在Triangle类中,一是保持区域功能一致性,二是我们创建的mProgram声明在Triangle中,免得还要传参。

调用渲染的方法

private int positionHandle;
private int colorHandle;

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

public void draw() {
    // 将program添加到Opengl ES环境
    GLES20.glUseProgram(mProgram);

    // 获取mProgram中vPosition的句柄(顶点数据)
    positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // 启用顶点属性
    GLES20.glEnableVertexAttribArray(positionHandle);

    // 顶点坐标的处理方式,参数依次为索引值(刚刚获取的句柄),数据维数(顶点即3维)
    // 数据类型(float),当被访问时固定点数值是否需要归一化(false)
    // 步长,即连续顶点偏移量(COORDS_PER_VERTEX * 4),
    // 起始位置在缓冲区的偏移量(vertexBuffer)
    GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // 获取mProgram中vColor的句柄(颜色数据)
    colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // 使颜色生效
    GLES20.glUniform4fv(colorHandle, 1, color, 0);

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

    // 禁用顶点属性
    GLES20.glDisableVertexAttribArray(positionHandle);
}

最后,只需要在Renderer中的onDrawFrame中调用draw()来绘制,三角形就输出到屏幕上了。

###完整代码

Triangle类,记录了三角形图形的信息(顶点,颜色)和shader(顶点着色器和片元着色器)的代码;并且在初始化时将顶点数据输出到缓冲区;编译shader并链接到程序;最后包含了一个绘制图像的接口draw()以供调用。

import android.opengl.GLES20;

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

public class Triangle {

    private FloatBuffer vertexBuffer;
    private final int mProgram;
    private int positionHandle;
    private int colorHandle;
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    //shader代码​
    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;" +
                    "}";

    // 数组中每个顶点的坐标数(即维数)
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // in counterclockwise order:
            0.0f,  0.622008459f, 0.0f, // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
            0.5f, -0.311004243f, 0.0f  // bottom right
    };
    // 颜色信息,分别是RGB和alpha通道的归一化值
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle() {
        /**
         * 将顶点数据传入Buffer
         */
        // 初始化顶点数据Buffer
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (一个float型4字节)
                triangleCoords.length * 4);
        // 字节序使用native order
        bb.order(ByteOrder.nativeOrder());

        // 将ByteBuffer转换为浮点型FloatBuffer
        vertexBuffer = bb.asFloatBuffer();
        // 将顶点数据添加到Buffer
        vertexBuffer.put(triangleCoords);
        // Buffer位置调整到开头
        vertexBuffer.position(0);
        /**
         * 创建shader并链接到程序
         */
        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // 创建空的Opengl ES program
        mProgram = GLES20.glCreateProgram();

        // 将顶点着色器加入program
        GLES20.glAttachShader(mProgram, vertexShader);

        // 将片元着色器加入program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // 创建opengl ES可执行的文件
        GLES20.glLinkProgram(mProgram);
    }
    public void draw() {
        // 将program添加到Opengl ES环境
        GLES20.glUseProgram(mProgram);

        // 获取mProgram中vPosition的句柄(顶点数据)
        positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // 启用顶点属性
        GLES20.glEnableVertexAttribArray(positionHandle);

        // 顶点坐标的处理方式,参数依次为索引值(刚刚获取的句柄),数据维数(顶点即3维)
        // 数据类型(float),当被访问时固定点数值是否需要归一化(false)
        // 步长,即连续顶点偏移量(COORDS_PER_VERTEX * 4),
        // 起始位置在缓冲区的偏移量(vertexBuffer)
        GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // 获取mProgram中vColor的句柄(颜色数据)
        colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // 使颜色生效
        GLES20.glUniform4fv(colorHandle, 1, color, 0);

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

        // 禁用顶点属性
        GLES20.glDisableVertexAttribArray(positionHandle);
    }
}

Renderer中保留了之前绘制背景颜色的代码。

在此之上增加了shader的加载方法loadShader,调用这个方法来编译shader;在创建sruface时(onSurfaceCreated)实例化一个Triangle类;在显示帧刷新时(onDrawFrame)调用Triangle类中绘制图形的接口draw()。

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

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

public class MyGLRenderer implements GLSurfaceView.Renderer {
    private Triangle mTriangle;
    public static int loadShader(int type, String shaderCode){
        // 创建顶点着色器的type是 GLES20.GL_VERTEX_SHADER
        // 创建片元着色器的type是 GLES20.GL_FRAGMENT_SHADER
        int shader = GLES20.glCreateShader(type);

        // 传递着色器代码并编译着色器
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //设置背景颜色,参数是RGB和alpha通道值的归一值,即范围为0-1,我设置的是橙色
        GLES20.glClearColor(1.0f, 0.6f, 0f, 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) {
        //把窗口颜色用刚刚设定的值(glClearColor)刷洗
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        mTriangle.draw();
    }
}

GLSurfaceView不改变,仍然是设置好renderer即可。 

import android.content.Context;
import android.opengl.GLSurfaceView;

public class MyGLSurfaceView extends GLSurfaceView {
    private final MyGLRenderer renderer;
    public MyGLSurfaceView(Context context) {
        super(context);
        //设置版本
        setEGLContextClientVersion(2);
        //创建Renderer实例
        renderer = new MyGLRenderer();
        //将GLRenderer和GLSurfaceView连接
        setRenderer(renderer);
    }
}

MainActivity也不变,设置好GLSurfaceView即可。

import androidx.appcompat.app.AppCompatActivity;

import android.opengl.GLSurfaceView;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    private GLSurfaceView gLView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建GLSurfaceView实例
        gLView = new MyGLSurfaceView(this);
        //连接GLSurfaceView
        setContentView(gLView);
    }
}

###结果

 

###目录导航

Opengl ES(一):第一个例子

Opengl ES(二):画一个三角形之创造一个三角形

Opengl ES(三):画一个三角形之显示到屏幕

Opengl ES(四):设置projection和camera views

Opengl ES(五):响应触控事件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值