一、基本概念
- OpenGL 绘制的都是图形,包括形状和填充,基本形状是三角形。
- 每个形状都有顶点,Vertex,顶点的序列就是一个图形。
- 图形有所谓的正反面,如果我们看向一个图形,它的顶点序列是逆时针方向,那我们看到的就是正面。
- Shader(着色器)用来描述如何绘制(渲染),GLSL 是 OpenGL 的编程语言,全称就叫 OpenGL Shader Language。OpenGL 渲染需要两种 shader,vertex 和 fragment。
- Vertex shader,控制顶点的绘制,指定坐标、变换等。
- Fragment shader,控制形状内区域渲染,纹理填充内容。
二、坐标系
2.1、android手机坐标系
二维坐标系,原点在左上角,x 轴向右,y 轴向下,x y 取值范围为屏幕分辨率
2.2 OpenGL坐标系
三维坐标系,原点在中间,x 轴向右,y 轴向上,z 轴朝向我们,x y z 取值范围都是 [-1, 1]:
2.3、OpenGL纹理坐标系(texture)
二维坐标系,原点在左下角,s(x)轴向右,t(y)轴向上,x y 取值范围都是 [0, 1]:
3、HelloWord
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GLSurfaceView glSurfaceView =
(android.opengl.GLSurfaceView) findViewById(R.id.mGLSurfaceView);
glSurfaceView.setEGLContextClientVersion(2);
glSurfaceView.setRenderer(new MyRenderer());
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
static class MyRenderer implements GLSurfaceView.Renderer {
private static final String VERTEX_SHADER = "attribute vec4 vPosition;\n"
+ "void main() {\n"
+ " gl_Position = vPosition;\n"
+ "}";
private static final String FRAGMENT_SHADER = "precision mediump float;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(0.5,0,0,1);\n"
+ "}";
private static final float[] VERTEX = { // in counterclockwise order:
0, 1, 0, // top
-0.5, -1, 0, // bottom left
1, -1, 0, // bottom right
};
private final FloatBuffer mVertexBuffer;
private int mProgram;
private int mPositionHandle;
MyRenderer() {
mVertexBuffer = ByteBuffer.allocateDirect(VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(VERTEX);
mVertexBuffer.position(0);
}
@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
}
@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
mProgram = GLES20.glCreateProgram();
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
GLES20.glLinkProgram(mProgram);
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
}
@Override
public void onDrawFrame(GL10 unused) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(mProgram);
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false,
12, mVertexBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
}
}
3.1、set up
首先我们需要一个 GLSurfaceView,它是让我们渲染的“画布”,更准确来说,是一个 surface。然后我们需要一个 GLSurfaceView.Renderer,它将实现我们的渲染逻辑。此外我们还将设置 GL ES 版本,并将 surface view 和 renderer 连接起来:
glSurfaceView.setEGLContextClientVersion(2);
glSurfaceView.setRenderer(new MyRenderer());
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
RenderMode 有两种,RENDERMODE_WHEN_DIRTY 和 RENDERMODE_CONTINUOUSLY,前者是懒惰渲染,需要手动调用 glSurfaceView.requestRender() 才会进行更新,而后者则是不停渲染。
3.2 GLSurfaceView.Renderer
Renderer 包含三个接口:
public interface Renderer {
void onSurfaceCreated(GL10 gl, EGLConfig config);
void onSurfaceChanged(GL10 gl, int width, int height);
void onDrawFrame(GL10 gl);
}
onSurfaceCreated 在 surface 创建时被回调,通常用于进行初始化工作,只会被回调一次;onSurfaceChanged 在每次 surface 尺寸变化时被回调,注意,第一次得知 surface 的尺寸时也会回调;onDrawFrame 则在绘制每一帧的时候回调。
有的时候,我们的初始化工作可能需要依赖 surface 的尺寸,所以这里我们把初始化工作放到了 onSurfaceChanged 方法中。
3.3、GLSL 程序
和普通的 view 利用 canvas 来绘制不一样,OpenGL 需要加载 GLSL 程序,让 GPU 进行绘制。所以我们需要定义 shader 代码,并在 onSurfaceChanged 回调中加载:
@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
mProgram = GLES20.glCreateProgram();
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
GLES20.glLinkProgram(mProgram);
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
}
static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
我们的 Java 代码需要获取 shader 代码中定义的变量索引,并在稍后的绘制代码中进行赋值。
- 创建 GLSL 程序:mProgram = GLES20.glCreateProgram()
- 加载 shader 代码:vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER)
- attatch shader 代码:GLES20.glAttachShader(mProgram, vertexShader)
- 链接 shader 代码:GLES20.glLinkProgram(mProgram)
- 获取 shader 代码中的变量索引:mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition")
需要指出的是,shader 代码中的变量索引,在 GLSL 程序的生命周期内(两次链接之间),都是固定的,只需要获取一次。
3.4 绘制
@Override
public void onDrawFrame(GL10 unused) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(mProgram);
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false,
12, mVertexBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
首先需要应用 GLSL 程序,
对于 attribute 类型的变量,我们需要先 enable,再赋值,绘制完毕之后再 disable。
我们可以通过 GLES20.glDrawArrays 或者 GLES20.glDrawElements 开始绘制。
注意,执行完毕之后,GPU 就在显存中处理好帧数据了,但此时并没有更新到 surface 上,是 GLSurfaceView 会在调用 renderer.onDrawFrame 之后,调用 mEglHelper.swap(),来把显存的帧数据更新到 surface 上的。
转载至:https://blog.piasy.com/2016/06/07/Open-gl-es-android-2-part-1/