樣例代碼來(lái)源: android-openGL-canvas
OpenGL文檔參考:
OpenGL文檔
本文只對(duì)流程做排序以及一些簡(jiǎn)單的說(shuō)明,想了解原理的請(qǐng)查看OpenGL相關(guān)文檔肮蛹,有對(duì)相應(yīng)函數(shù)有疑問(wèn)的可查閱上述文檔刻帚。
一潦嘶、創(chuàng)建 eglContext。EGLContext 是一個(gè)比較重的對(duì)象崇众,所以一般只創(chuàng)建一次掂僵。
例子代碼代碼主要在 EglHelper
- egl = EGLContext.getEGL()
- 獲取 eglDisplay = eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
- 獲取 EGLConfig eglChooseConfig(..)
- eglInitialize(mEgl, mEglDisplay, mEglConfig, eglContext)
- 創(chuàng)建 eglContext = eglCreateContext(display, config, eglContext, attrib_list))
- 大致的相關(guān)代碼
public EGLContext start(EGLContext eglContext) {
if (GLThread.LOG_EGL) {
Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
}
/*
* Get an EGL instance
*/
mEgl = (EGL10) EGLContext.getEGL();
/*
* Get to the default display.
*/
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
/*
* We can now initialize EGL for that display
*/
int[] version = new int[2];
if (!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
mEglConfig = eglConfigChooser.chooseConfig(mEgl, mEglDisplay);
/*
* Create an EGL context. We want to do this as rarely as we can, because an
* EGL context is a somewhat heavy object.
*/
mEglContext = eglContextFactory.createContext(mEgl, mEglDisplay, mEglConfig, eglContext);
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
mEglContext = null;
throwEglException("createContext");
}
if (GLThread.LOG_EGL) {
Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
}
mEglSurface = null;
return mEglContext;
}
二、創(chuàng)建 surface
eglCreateWindowSurface(display, config, nativeWindow, surfaceAttribs)
將 context 和 surface 聯(lián)系起來(lái)顷歌。
eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)如果需要更換surface锰蓬,就先destroy現(xiàn)在的
eglDestroySurface大致的相關(guān)代碼
public boolean createSurface(Object surface) {
if (GLThread.LOG_EGL) {
Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId());
}
/*
* Check preconditions.
*/
if (mEgl == null) {
throw new RuntimeException("egl not initialized");
}
if (mEglDisplay == null) {
throw new RuntimeException("eglDisplay not initialized");
}
if (mEglConfig == null) {
throw new RuntimeException("mEglConfig not initialized");
}
/*
* The window size has changed, so we need to create a new
* surface.
*/
destroySurfaceImp();
/*
* Create an EGL surface we can render into.
*/
mEglSurface = eglWindowSurfaceFactory.createWindowSurface(mEgl,
mEglDisplay, mEglConfig, surface);
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
int error = mEgl.eglGetError();
if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
}
return false;
}
/*
* Before we can issue GL commands, we need to make sure
* the context is current and bound to a surface.
*/
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
/*
* Could not make the context current, probably because the underlying
* SurfaceView surface has been destroyed.
*/
logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
return false;
}
return true;
}
三、接下來(lái)就是使用vertex數(shù)據(jù)眯漩,texture數(shù)據(jù)芹扭,vertex shader, texture shader進(jìn)行繪制了,這一部分是可以隨時(shí)替換數(shù)據(jù)繪制新的東西的,其中比較耗性能的部分是編譯鏈接shader的部分冯勉,所以要對(duì)編譯鏈接部分進(jìn)行緩存以保證性能。
代碼主要在GLES20Canvas
vertex 數(shù)據(jù)
glGenBuffers(...)
glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId)
glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer, GLES20.GL_STATIC_DRAW)
- 大致的相關(guān)代碼
private int uploadBuffer(Buffer buffer, int elementSize) {
mGLId.glGenBuffers(1, mTempIntArray, 0);
checkError();
int bufferId = mTempIntArray[0];
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId);
checkError();
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer,
GLES20.GL_STATIC_DRAW);
checkError();
return bufferId;
}
編譯摹芙,鏈接shader等
- 以兩個(gè) String 對(duì)象作為參數(shù)灼狰,分別是 vertex shader 和 fragment shader
- 編譯
glCreateShader
glShaderSource
glCompileShader
private static int loadShader(int type, String shaderCode) {
// create a vertex shader type (GLES20.GL_VERTEX_SHADER)
// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
// add the source code to the shader and compile it
GLES20.glShaderSource(shader, shaderCode);
checkError();
GLES20.glCompileShader(shader);
checkError();
return shader;
}
- 鏈接
glCreateProgram
glAttachShader vertex
glAttachShader fragment
glLinkProgram
glGetProgramiv
private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) {
int program = GLES20.glCreateProgram();
checkError();
if (program == 0) {
throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError());
}
GLES20.glAttachShader(program, vertexShader);
checkError();
GLES20.glAttachShader(program, fragmentShader);
checkError();
GLES20.glLinkProgram(program);
checkError();
int[] mLinkStatus = mTempIntArray;
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0);
if (mLinkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Could not link program: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
loadHandles(params, program);
return program;
}
設(shè)定繪制區(qū)域
- glViewport(width, height)
此時(shí)就可以利用 width 和 height 來(lái)對(duì) model view projection 矩陣進(jìn)行一些初始設(shè)置了
@Override
public void setSize(int width, int height) {
mWidth = width;
mHeight = height;
GLES20.glViewport(0, 0, mWidth, mHeight);
checkError();
Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1);
if (getTargetTexture() == null) {
mScreenWidth = width;
mScreenHeight = height;
Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);
Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);
}
}
對(duì)model-view矩陣進(jìn)行處理(也可以不處理,看需求)
可以是平移浮禾,旋轉(zhuǎn)交胚,放縮之類的。
傳入 texture, 一個(gè)帶有寬高和圖像數(shù)據(jù)的對(duì)象盈电。
- glUseProgram(programId) 使用之前編譯鏈接好的的id
- 將texture數(shù)據(jù)傳入到OpenGL里的gl操作
glActiveTexture(GLES20.GL_TEXTURE0)
private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
GLES20.glUseProgram(program);
checkError();
enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
checkError();
texture.onBind(this);
GLES20.glBindTexture(texture.getTarget(), texture.getId());
checkError();
GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
checkError();
GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha());
checkError();
}
glGenTextures
glBindTexture
glTexParameteri 設(shè)定相關(guān)的texture params
@Override
public void setTextureParameters(BasicTexture texture) {
int target = texture.getTarget();
GLES20.glBindTexture(target, texture.getId());
checkError();
GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
}
- 如果bitmap的寬高是2的倍數(shù)
glBindTexture
glTexImage2D
@Override
public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
int target = texture.getTarget();
GLES20.glBindTexture(target, texture.getId());
checkError();
GLUtils.texImage2D(target, 0, bitmap, 0);
}
- 如果不是2的倍數(shù)
glBindTexture
GLUtils.texSubImage2D
@Override
public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
int format, int type) {
int target = texture.getTarget();
GLES20.glBindTexture(target, texture.getId());
checkError();
GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
}
glUniform1i(...) // 對(duì)shader里面的textureSampler傳值蝴簇,因?yàn)锳ctiveTexture的是Texture0,所以傳0就可以了匆帚。
GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
將vertex的值傳入OpenGL
glBindBuffer
glVertexAttribPointer
glBindBuffer 0 清理
private void setPosition(ShaderParameter[] params, int offset) {
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates);
checkError();
GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE);
checkError();
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
checkError();
}
texture Matrix
- 利用texture的寬高設(shè)定 textureMatrix熬词。 textureMatrix 是shader 里的一個(gè)uniform對(duì)象,用來(lái)映射從texture對(duì)應(yīng)位置獲取的顏色到繪制區(qū)域吸重。例如在不進(jìn)行平移旋轉(zhuǎn)等情況下互拾,第一個(gè)像素點(diǎn)就繪制在第一個(gè)位置。
TextureMatrixTransformer.copyTextureCoordinates(...);
TextureMatrixTransformer.convertCoordinate(...);
TextureMatrixTransformer.setTextureMatrix(...);
model view projection matrix 將這個(gè)matrix傳入到OpenGL里
translateM
scaleM
multiplyMM
glUniformMatrix4fv(...)
private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) {
Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0);
GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE);
checkError();
}
最后就是繪制了
glEnableVertexAttribArray positionHandle
glDrawArrays
glDisableVertexAttribArray positionHandle
private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width,
float height) {
setMatrix(params, x, y, width, height);
int positionHandle = params[INDEX_POSITION].handle;
GLES20.glEnableVertexAttribArray(positionHandle);
checkError();
GLES20.glDrawArrays(type, 0, count);
checkError();
GLES20.glDisableVertexAttribArray(positionHandle);
checkError();
}
四嚎幸、總結(jié)
以上颜矿,說(shuō)明了OpenGL繪制的三個(gè)流程。由于OpenGL是獨(dú)立于語(yǔ)言的嫉晶,這些流程不僅適用于Android骑疆,使用其它語(yǔ)言編寫也可借鑒。從上面冗長(zhǎng)的流程可以看到替废,每次要進(jìn)行繪制都寫那么多代碼實(shí)在不經(jīng)濟(jì)箍铭,那么接下來(lái)的文章就會(huì)討論如何封裝以上的流程了。其實(shí)開(kāi)頭的github倉(cāng)庫(kù)里面的代碼已經(jīng)有封裝好的類了椎镣,有需要的話可以先去看源代碼坡疼。感謝閱讀!
接下來(lái)可以看:
如何封裝 opengl 流程