主要有三个类:
- MainActivity:创建GLSurfaceView对象
- HelloTriangleRenderer:实现GLSurfaceView.Renderer接口(渲染程序类)
- MyGLSurfaceView:创建GLSurfaceView的拓展类,用以实现诸如触摸事件处理的操作(具体解释:
https://developer.android.com/training/graphics/opengl/environment?hl=zh-cn#glsurfaceview)
MainActivity
package com.example.opengl_triangle;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private final int CONTEXT_CLIENT_VERSION = 3;
@Override
protected void onCreate ( Bundle savedInstanceState )
{
super.onCreate ( savedInstanceState );
//创建MyGLSurfaceView对象
mGLSurfaceView = new MyGLSurfaceView( this );
if ( detectOpenGLES30() )
{
// Tell the surface view we want to create an OpenGL ES 3.0-compatible
// context, and set an OpenGL ES 3.0-compatible renderer.
mGLSurfaceView.setEGLContextClientVersion ( CONTEXT_CLIENT_VERSION );
mGLSurfaceView.setRenderer ( new HelloTriangleRenderer ( this ) );
}
else
{
Log.e ( "HelloTriangle", "OpenGL ES 3.0 not supported on device. Exiting..." );
finish();
}
setContentView ( mGLSurfaceView );
}
private boolean detectOpenGLES30()
{
ActivityManager am =
( ActivityManager ) getSystemService ( Context.ACTIVITY_SERVICE );
ConfigurationInfo info = am.getDeviceConfigurationInfo();
return ( info.reqGlEsVersion >= 0x30000 );
}
@Override
protected void onResume()
{
// Ideally a game should implement onResume() and onPause()
// to take appropriate action when the activity looses focus
super.onResume();
mGLSurfaceView.onResume();
}
@Override
protected void onPause()
{
// Ideally a game should implement onResume() and onPause()
// to take appropriate action when the activity looses focus
super.onPause();
mGLSurfaceView.onPause();
}
private GLSurfaceView mGLSurfaceView;
}
MyGLSurfaceView
package com.example.opengl_triangle;
import android.content.Context;
import android.opengl.GLSurfaceView;
//创建GLSurfaceView的拓展类MyGLSurfaceView,用以拓展例如触摸事件的处理
//https://developer.android.com/training/graphics/opengl/environment?hl=zh-cn#glsurfaceview
class MyGLSurfaceView extends GLSurfaceView {
public MyGLSurfaceView(Context context){
//引用父类构造函数
super(context);
}
}
重点介绍HelloTriangleRenderer类:
package com.example.opengl_triangle;
import android.content.Context;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
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 HelloTriangleRenderer implements GLSurfaceView.Renderer
{
///
// Constructor
//
public HelloTriangleRenderer ( Context context )
{
//获取Activity传入的Context上下文
mContext = context;
mVertices = ByteBuffer
.allocateDirect ( mVerticesData.length * 4 ) //直接分配 native 内存,不会被gc
.order ( ByteOrder.nativeOrder() )//和本地平台保持一致的字节序(大/小端)
.asFloatBuffer();//将底层字节映射到FloatBuffer实例,方便使用
mVertices
.put ( mVerticesData )//将顶点拷贝到 native 内存中
.position ( 0 );//每次 put position 都会 + 1,需要在绘制前重置为0
}
private int compileShader(int shader_type, String shaderString){
// This compiles the shader from the string
// 编译保存在 string 中的 shader 内容
int shader = GLES30.glCreateShader(shader_type);
GLES30.glShaderSource(shader, shaderString);
GLES30.glCompileShader(shader);
// This checks for for compilation errors
// 检查是否出现编译错误
int[] compiled = new int[1];
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
String log = GLES30.glGetShaderInfoLog(shader);
Log.e(TAG, "Shader compilation error: ");
Log.e(TAG, log);
}
return shader;
}
//从 AssetFile 中读取已经编写好的着色器文件(vertexShader and fragmentShader)到 string
public String loadStringFromAssetFile(Context context, String filePath) {
StringBuilder shaderSource = new StringBuilder();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(context.getAssets().open(filePath)));
String line;
while((line = reader.readLine()) != null){
shaderSource.append(line).append("\n");
}
reader.close();
return shaderSource.toString();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Could not load shader file");
return null;
}
}
//调用一次以设置视图的 OpenGL ES 环境
@Override
public void onSurfaceCreated ( GL10 glUnused, EGLConfig config )
{
// Load shaders from file
String vertexShaderString = loadStringFromAssetFile(mContext, "triangle_vertex_shader.glsl");
String fragmentShaderString = loadStringFromAssetFile(mContext, "triangle_fragment_shader.glsl");
// Compile shaders
int vertexShader = compileShader(GLES30.GL_VERTEX_SHADER, vertexShaderString);
int fragmentShader = compileShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderString);
int programObject;
int[] linked = new int[1];
// Create the program object
// 创建渲染程序对象
programObject = GLES30.glCreateProgram();
if ( programObject == 0 )
{
return;
}
GLES30.glAttachShader ( programObject, vertexShader );
GLES30.glAttachShader ( programObject, fragmentShader );
// Bind vPosition to attribute 0
//绑定属性位置 vPosition :0 着色器中没有设定属性位置时使用
GLES30.glBindAttribLocation ( programObject, 0, "vPosition" );
// Link the program
GLES30.glLinkProgram ( programObject );
// Check the link status
GLES30.glGetProgramiv ( programObject, GLES30.GL_LINK_STATUS, linked, 0 );
if ( linked[0] == 0 )
{
Log.e ( TAG, "Error linking program:" );
Log.e ( TAG, GLES30.glGetProgramInfoLog ( programObject ) );
GLES30.glDeleteProgram ( programObject );
return;
}
// Store the program object
mProgramObject = programObject;
GLES30.glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
}
// /
// Draw a triangle using the shader pair created in onSurfaceCreated()
//
@Override
public void onDrawFrame ( GL10 glUnused )
{
// Set the viewport
GLES30.glViewport ( 0, 0, mWidth, mHeight );
// Clear the color buffer
GLES30.glClear ( GLES30.GL_COLOR_BUFFER_BIT );
// Use the program object
GLES30.glUseProgram ( mProgramObject );
// Load the vertex data
GLES30.glVertexAttribPointer ( 0, 3, GLES30.GL_FLOAT, false, 0, mVertices );
GLES30.glEnableVertexAttribArray ( 0 );
GLES30.glDrawArrays ( GLES30.GL_TRIANGLES, 0, 3 );
}
// /
// Handle surface changes
//
@Override
public void onSurfaceChanged ( GL10 glUnused, int width, int height )
{
mWidth = width;
mHeight = height;
}
// Member variables
private Context mContext;
private int mProgramObject;
private int mWidth;
private int mHeight;
private FloatBuffer mVertices;
private static String TAG = "HelloTriangleRenderer";
private final float[] mVerticesData =
{ 0.0f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f };
}
主要作用:
载入着色器文件(从assets文件夹中读取已经写好了的着色器文件,一般默认工程没有此文件夹,需自行创建):
顶点着色器(triangle_vertex_shader.glsl)、片段着色器(triangle_fragment_shader.glsl)
triangle_fragment_shader.glsl
#version 300 es
precision mediump float;
out vec4 fragColor;
void main()
{
fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );
}
triangle_vertex_shader.glsl
#version 300 es
in vec4 vPosition;
void main()
{
gl_Position = vPosition;
}
这两个着色器文件在使用时,可以在HelloTriangleRenderer中读取文件内容到string,然后再进行compile等处理,具体代码参考 loadStringFromAssetFile,需要注意的是读取文件时需要传入Activity的Context,否则无法使用 getAssets() 方法
效果:
参考资料:
- 构建 OpenGL ES 环境【官网】
- Compiling and Linking GLSL-ES Shaders from asset file
- https://github.com/danginsburg/opengles3-book/tree/master/Android_Java/Chapter_2/Hello_Triangle
- https://juejin.cn/post/6844904045316947981#heading-13