OpenGLES(二)绘制三角形

使用概述

在Android上运行OpenGL ES程序需要用到GLSurfaceView控件,GLSurfaceView继承自SurfaceView并实现了GLThread,通过OpenGL ES进行绘制。

所有OpenGL相关的操作必须在GLThread线程执行,无法在主线程(UI线程)中执行。Render的onSurfaceCreated、onDrawFrame、onSurfaceChanged方法都是运行在GLThread线程。

OpenGL ES的使用步骤如下:

  • 创建GLSurfaceView
    • 设置GLES版本
    • 设置Render
    • 设置GlSurfaceView渲染模式
  • 定义Vertex Shader和Fragment Shader
  • 编译Shader(着色器)
    • 创建Shander
    • 加载Shader源码
    • 编译Shader
    • 查看编译结果
  • 链接Program(着色器程序对象)
    • 创建Program
    • attach顶点着色器
    • attach片段着色器
    • 链接Program
  • 渲染
    • 设置窗口尺寸
    • 激活着色器程序
    • 启用顶点属性
    • 设置顶点数据
    • 绘制

声明APK需要OpenGL ES版本

//在设备上不存在指定的功能时,则该应用不能够正常运行
<uses-feature android:glEsVersion="0x00030000" android:required="true" />

创建GLSurfaceView

public class OneActivity extends Activity {
    private GLSurfaceView glSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_one);
        glSurfaceView = findViewById(R.id.root_surface);
        initGlSurface();
    }

    private void initGlSurface(){
        //设置GLES版本
        glSurfaceView.setEGLContextClientVersion(3);

        //设置Render
        glSurfaceView.setRenderer(new OneRenderer(this));

        //设置GlSurfaceView渲染模式,一定要在setRenderer之后调用
        //RENDERMODE_WHEN_DIRTY 只有在调用requestRender或者onResume等方法时才渲染
        //RENDERMODE_CONTINUOUSLY 表示一直渲染
        glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    @Override
    protected void onResume() {
        super.onResume();
        glSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        glSurfaceView.onPause();
    }
}

定义Shader

在assets下创建glsl文件夹,用于存放glsl(着色器语言)文件,创建one_vertex.glsl文件,保存 Vertex Shader(顶点着色器) 代码

#version 300 es
//顶点着色器的输入
in vec4 vPosition;
//设置 vPosition 的位置属性为 0,设置后不需要查询顶点属性
//layout (location=0) in vec4 vPosition;
void main() {
  //gl_Position是内部变量,用来表示输入的顶点坐标
  gl_Position = vPosition;
}

创建one_fragment.glsl文件,保存 Fragment Shader(片段着色器)代码:

#version 300 es
precision mediump float;
//片段着色器的输出
out vec4 FragColor;
void main(){
    FragColor = vec4(1,0,0,1);
}

Java实现方案

编译Shader、链接Program

public class ShaderUtils {
    private static final String TAG = "WHF_"+ShaderUtils.class.getSimpleName();

    /**
     * 编译着色器
     * @param shaderType {@link GLES30#GL_VERTEX_SHADER} {@link GLES30#GL_FRAGMENT_SHADER}
     * @param shaderSource glsl源码
     */
    private static int compileShader(int shaderType,String shaderSource){
        //创建空Shader,返回值为Shader句柄
        int shader = GLES30.glCreateShader(shaderType);
        if (shader == 0){
            return 0;
        }

        //加载 Shader 源码
        GLES30.glShaderSource(shader,shaderSource);

        //编译 Shader
        GLES30.glCompileShader(shader);

        //获取 Shader 编译结果
        int[] compileStatus = new int[1];
        GLES30.glGetShaderiv(shader,GLES30.GL_COMPILE_STATUS,compileStatus,0);

        //编译失败
        if (compileStatus[0] == 0){
            Log.e(TAG,"compileShader error = "+GLES30.glGetShaderInfoLog(shader));
            GLES30.glDeleteShader(shader);
            shader = 0;
        }

        return shader;
    }

    /**
     * 链接 着色器 为 着色器程序
     * @param vertexSource 顶点着色器源码
     * @param fragmentSource 片元着色器源码
     */
    public static int createAndLinkProgram(String vertexSource,String fragmentSource){
        int program = GLES30.glCreateProgram();
        if (program == 0){
            return program;
        }

        //编译定点着色器
        int vertexShader = compileShader(GLES30.GL_VERTEX_SHADER,vertexSource);
        //编译片元着色器
        int fragmentShader = compileShader(GLES30.GL_FRAGMENT_SHADER,fragmentSource);

        //将顶点着色器附加到着色器程序
        GLES30.glAttachShader(program,vertexShader);
		//将片段着色器附加到着色器程序
        GLES30.glAttachShader(program,fragmentShader);

        //链接所有着色器为一个着色器程序
        GLES30.glLinkProgram(program);

        //获取链接状态
        int[] linkStatus = new int[1];
        GLES30.glGetProgramiv(program,GLES30.GL_LINK_STATUS,linkStatus,0);

        if (linkStatus[0] == 0){
            Log.e(TAG,"createAndLinkProgram error = "+GLES30.glGetProgramInfoLog(program));
            GLES30.glDeleteProgram(program);
            program = 0;
        }

        return program;
    }
}

渲染

自定义Renderer类实现GLSurfaceView.Renderer接口,其有三个接口:

  • onSurfaceCreated:GLSurfaceView创建完成,也代表OpenGL ES环境创建完成,通常情况下在此方法中创建Program及初始化参数。
  • onSurfaceChanged:当Surface发生变化的时候回调,比如竖屏转横屏导致GLSurfaceView大小发生变化,通常情况下在此方法中设置绘制窗口及和GLSurfaceView大小有关系的参数。
  • onDrawFrame:执行OpenGL ES渲染工作,由系统以一定的频率来调用重绘View,当设置GLSurfaceView的渲染模式为GLSurfaceView.RENDERMODE_CONTINUOUSLY或不设置时,系统就会主动回调onDrawFrame()方法, 如果设置为 RENDERMODE_WHEN_DIRTY ,手动调用requestRender(),才会渲染。
public class OneRenderer implements GLSurfaceView.Renderer {
    private static final String TAG = MainActivity.COMMON_TAG + OneRenderer.class.getSimpleName();
    private Context mContext;
    private int program;
    private int vPosition;

    public OneRenderer(Context context){
        this.mContext = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        Log.i(TAG, "onSurfaceCreated");
        String vertexSource = AssetUtil.getGlslSource(mContext,"one_vertex");
        String fragmentSource = AssetUtil.getGlslSource(mContext,"one_fragment");

        program = ShaderUtils.createAndLinkProgram(vertexSource,fragmentSource);
        //查询着色器的顶点属性位置(vPosition为顶点着色器的输入属性)
		//如果使用 layout(position = x) 在 glsl 中限定,则不用查询
        vPosition = GLES30.glGetAttribLocation(program,"vPosition");
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        Log.i(TAG, "onSurfaceChanged");
        //创建OpenGL ES绘制窗口
        GLES30.glViewport(0,0,width,height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        Log.i(TAG, "onDrawFrame");

        //激活着色器程序
        GLES30.glUseProgram(program);
		//启用顶点属性,顶点属性默认是禁用的
        GLES30.glEnableVertexAttribArray(vPosition);
        //将顶点数据设置给Program
		//参数一:顶点属性的位置
        //参数二:为每个顶点的个数
        //参数三:为数据类型
        //参数四:为是否否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间
        //参数五:步长,连续的顶点属性组之间的间隔(当数值是紧密排列时,可以设置0来让OpenGL决定具体步长是多少)
        GLES30.glVertexAttribPointer(vPosition, 3, GLES30.GL_FLOAT, false, 0, createBuffer());
		
        //参数一为绘制方式;参数二为从数组缓存中哪一位开始画;参数三为绘制顶点数量
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);
    }

    //顶点数据
    private FloatBuffer createBuffer(){
        //三个顶点数据,每个顶点数据包含x、y、z三个值
        float[] vertexArray = {
				0.0f, 0.5f, 0.0f,    // top
                -0.5f, -0.5f, 0.0f,  // bottom left
                0.5f, -0.5f, 0.0f	 // bottom right
		};
		
		//每个值占用4个字节(32位)
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4);
        byteBuffer.order(ByteOrder.nativeOrder());
        FloatBuffer buffer = byteBuffer.asFloatBuffer();
        buffer.put(vertexArray);
        buffer.position(0);
        return buffer;
    }
}

Native实现方式

CMake配置

target_link_libraries(
        lib_part2
        android
        GLESv3
        ${log-lib})

AssetManager资源读取

#include "log.h"
#include "gl_utils.h"
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <cstdlib>

char *readAssetFile(JNIEnv *env, jobject javaAssetManager, const char *file_name) {
    AAssetManager *assetManager = AAssetManager_fromJava(env, javaAssetManager);
    AAsset *asset = AAssetManager_open(assetManager, file_name, AASSET_MODE_UNKNOWN);
    if (asset == nullptr) {
        LOGE("open asset error");
        return nullptr;
    }

    off_t fileByteLength = AAsset_getLength(asset);
    LOGD("file byte length = %ld", fileByteLength);

    char *buffer = (char *) malloc(fileByteLength);
    int readLength = AAsset_read(asset, buffer, fileByteLength);
    LOGD("file readLength = %d, content = %s", readLength, buffer);

    AAsset_close(asset);

    return buffer;
}

编译Shader、链接Program

#include "log.h"
#include "gl_utils.h"
#include <GLES3/gl3.h>

GLuint compileShader(GLenum shaderType, const char *shaderSource) {
    if (shaderSource == nullptr) {
        LOGE("read shader source error");
        return 0;
    }

    GLuint shader = glCreateShader(shaderType);
    if (shader == 0) {
        LOGE("create shader error");
        return 0;
    }

    //参数2:着色器源码个数
    glShaderSource(shader, 1, &shaderSource, NULL);
    glCompileShader(shader);

    GLint result;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
    if (!result) {
        GLchar infoLog[512];
        glGetShaderInfoLog(shader, 512, NULL, infoLog);
        LOGE("compile error = %s", infoLog);
        glDeleteShader(shader);
        return 0;
    }

    return shader;
}

GLuint linkShaderProgram(const char *vertexShaderSource,
                         const char *fragmentShaderSource) {
    GLuint program = glCreateProgram();
    if (program == 0) {
        LOGE("create program error");
        return 0;
    }

    GLuint verticalShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
    GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);

    glAttachShader(program, verticalShader);
    glAttachShader(program, fragmentShader);

    glLinkProgram(program);

    GLint result;
    glGetProgramiv(program, GL_LINK_STATUS, &result);
    if (!result) {
        GLchar infoLog[512];
        glGetProgramInfoLog(program, 512, NULL, infoLog);
        LOGE("compile error = %s", infoLog);
        glDeleteProgram(program);
        return 0;
    }

    return program;
}

渲染

void Part2NativeRender::OnSurfaceCreate(JNIEnv *env, jobject assetManager) {
    LOGD("Part2NativeRender OnSurfaceCreate");

    //编译链接着色器
    char *vertexSource = readAssetFile(env, assetManager, "glsl/one_vertex.glsl");
    char *fragmentSource = readAssetFile(env, assetManager, "glsl/one_fragment.glsl");
    program = linkShaderProgram(vertexSource, fragmentSource);
    glUseProgram(program);
    glEnableVertexAttribArray(0);
}

void Part2NativeRender::OnSurfaceChange(int width, int height) {
    LOGD("Part2NativeRender OnSurfaceChange width = %d height = %d", width, height);
    glViewport(0, 0, width, height);
}

void Part2NativeRender::OnDrawFrame() {
    float vertexArray[] = {0.0f, 0.5f, 0.0f,   // top
                           -0.5f, -0.5f, 0.0f, // bottom left
                           0.5f, -0.5f, 0.0f}; // bottom right
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertexArray);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值