使用概述
在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);
}