最近由於項目需要,所以開始學習OpenGL,網絡上的東西零零散散,所以就想寫一系列博客來記錄學習OpenGL。
一、簡介
首先我們要對其有一個簡單的認識
什麼是OpenGL?
官方描述:OpenGL是一個跨平臺的圖形API,用於指定3D圖形處理硬件中的標準軟件接口。
OpenGL的優勢?
OpenGl是用來做圖像處理的,那我們爲什麼不使用Canvas呢?費勁學這個幹嘛?
答案很簡單,爲了效率,使用Canvas畫圖使用的是CPU,那我們都知道手機上還有一個專門的圖形處理單元叫GPU,它可以並行的做浮點運算,OpenGL使用的就是GPU,這樣的話可以用GPU分擔CPU的工作量,提高圖形渲染效率。
概念
頂點着色器:它的作用就是爲每一個頂點生成座標,因此每個頂點都要運行一遍頂點着色器程序,一旦頂點座標計算出來之後,OpenGL就能夠使用這些頂點來組成點,線,面。
片段着色器:它的作用就是爲每一個頂點的片段渲染顏色。
座標系:原點在中心,範圍爲[-1,1],2d環境只有(x,y),3d環境有(x,y,z)
二、畫個三角形
1.創建GLSurfaceView
瞭解了基礎概念之後我們就開始上代碼,要使用OpenGL進行繪製就必須要有一個地方來供它繪製,它既然是使用GPU進行計算的,那這塊地方就肯定與Canvas有所不同,這個地方就是GLSurfaceView,它是android爲我們提供的一個類,繼承於SurfaceView,也就是說它管理着一塊Surface。
要創建SurfaceView可以使用new來創建,也可以在xml中書寫
GLSurfaceView glSurfaceView=new GLSurfaceView(context);
setContentView(glSurfaceView);
或者
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.opengl.GLSurfaceView
android:id="@+id/glSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
GLSurfaceView glSurfaceView = (GLSurfaceView) findViewById(R.id.glSurfaceView);
創建完成後還要爲其設定要使用的OpenGL版本,一般來說我們爲其設置版本好爲2
glSurfaceView.setEGLContextClientVersion(2);
爲其設置Render,這個纔是控制渲染的地方
glSurfaceView.setRenderer(renderer);
2.書寫Render
Render是一個接口,存在於GLSurfaceView類中,你需要將它裏面的三個重要的方法實現
public interface Renderer {
//當GlSurfaceView創建時被調用
void onSurfaceCreated(GL10 var1, EGLConfig var2);
//當GlSurfaceView尺寸發生變化時被調用
void onSurfaceChanged(GL10 var1, int var2, int var3);
//當畫每一幀的時候都會被調用
void onDrawFrame(GL10 var1);
}
一般來說我們在onSurfaceCreated中繪製背景,在onSurfaceChanged中設置視圖大小,在onDrawFrame中進行真正的繪畫,不過要在繪畫前清空視圖,下面給一個簡單的示例:
public class MyRender implements GLSurfaceView.Renderer {
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);
}
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
GLES20.glViewport(0,0,width,height);
}
@Override
public void onDrawFrame(GL10 gl10) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
}
}
該Render繪製出來就是一個灰色的背景,上面沒有任何圖形,要繪製圖形的話還要使用Shader,比如說我們要繪製三角形,那麼它的ShaderCode就應該這麼寫
private final String vertexShaderCode=
"attribute vec4 vPosition;"+
"void main(){"+
" gl_Position=vPosition;"+
"}";
當然還要給圖形上色了,塗顏色也需要使用ShaderCode
private final String fragmentShaderCode=
"precision mediump float;"+
"uniform vec4 vColor;"+
"void main(){"+
" gl_FragColor=vColor;"+
"}";
現在你先不用着急去研究Shader該怎麼寫,先熟悉整個流程
現在有了畫圖的程序和上色的程序還缺什麼呢,缺少座標,你不給座標程序怎麼知道你要畫在哪,怎麼畫
//用於裝載座標點
private FloatBuffer vertexBuffer;
//編譯ShaderCode後程序的指針
private int mProgram;
//一個座標要用三個數表示,x,y,z
private final int COORDS_PER_VERTEX=3;
//座標數組
private float triangleCoords[]={
0.5f,0.5f,0.0f,
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f
};
//頂點句柄
private int mPositionHandle;
//顏色句柄
private int mColorHandle;
//座標點數量
private final int vertexCount=triangleCoords.length/COORDS_PER_VERTEX;
//一個float佔四位,一共佔座標點數量*4位的空間
private final int vertexStride=COORDS_PER_VERTEX*4;
private float[] color={1.0f,1.0f,1.0f,1.0f};
接下來就是真正的編譯程序和繪畫了
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
//設置背景
GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);
//初始化ByteBuffer,爲其分配空間
ByteBuffer byteBuffer=ByteBuffer.allocateDirect(triangleCoords.length*4);
//設置排列順序爲nativeOrder
byteBuffer.order(ByteOrder.nativeOrder());
//將ByteBuffer轉換爲FloatBuffer
vertexBuffer=byteBuffer.asFloatBuffer();
//將座標點放入FloatBuffer
vertexBuffer.put(triangleCoords);
//設置起點
vertexBuffer.position(0);
//獲得編譯後的頂點程序句柄
int vertexShader=loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);
//獲得編譯後的顏色程序句柄
int fragmentShader=loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);
//創建一個OpenGLES程序
mProgram=GLES20.glCreateProgram();
//將編譯後的頂點程序加入其中
GLES20.glAttachShader(mProgram,vertexShader);
//將編譯後的顏色程序加入其中
GLES20.glAttachShader(mProgram,fragmentShader);
//連接程序
GLES20.glLinkProgram(mProgram);
//聲明一個數組,用於存放連接程序結果
int[] linkStatus = new int[1];
//獲取連接程序結果,將其存入linkStatus的第0個位置
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
//對連接程序結果的處理
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e("ES20_ERROR", "Could not link program: ");
Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(mProgram));
GLES20.glDeleteProgram(mProgram);
mProgram = 0;
}
}
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
//設置視圖大小
GLES20.glViewport(0,0,width,height);
}
@Override
public void onDrawFrame(GL10 gl10) {
//清空視圖
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
//將程序加入到OpenGLES環境
GLES20.glUseProgram(mProgram);
//獲取頂點着色器的句柄
mPositionHandle=GLES20.glGetAttribLocation(mProgram,"vPosition");
//啓用句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);
//填入數據
GLES20.glVertexAttribPointer(mPositionHandle,COORDS_PER_VERTEX,
GLES20.GL_FLOAT,false,vertexStride,vertexBuffer);
//獲取片元着色器的句柄
mColorHandle=GLES20.glGetUniformLocation(mProgram,"vColor");
//填入數據
GLES20.glUniform4fv(mColorHandle,1,color,0);
//開始繪製
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,vertexCount);
//禁用頂點着色器的句柄
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
//根據類型加載ShaderCode程序
protected int loadShader(int type, String shaderCode){
//根據類型創建着色器
int shader= GLES20.glCreateShader(type);
//加入代碼
GLES20.glShaderSource(shader,shaderCode);
//開始編譯
GLES20.glCompileShader(shader);
//聲明一個數組,用於存放編譯結果
int[] compiled = new int[1];
//獲取編譯結果,將其存入到compiled的第0個位置中
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
//根據編譯結果做處理
if (compiled[0] == 0) {
Log.e("ES20_ERROR", "Could not compile shader " + type + " : "+shaderCode);
Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
return shader;
}
這樣一個三角就畫好了,但是你會發現畫出來的三角形和填入的座標並不相符,明明應該是一個等腰直角三角形,爲什麼被拉長了呢?因爲你的屏幕不是方的,你的屏幕要是方的它就是一個等腰直角三角形了,所以怎麼才能把它轉換一下呢?這就涉及到了投影和相機視圖了,下篇我會講一下。