OpenGL ES 2.0 是 OpenGL 三維圖形 API 的子集印颤。是針對移動設(shè)備和嵌入式設(shè)備而設(shè)計的事甜。可用來實(shí)現(xiàn)全面可編程的 3D 圖形佳簸。在這篇文章中供屉,我們將會初步了解一些概念,創(chuàng)建第一個關(guān)于 OpenGL ES 2.0 的程序溺蕉,把整個編碼流程走完伶丐,讓你對整體有個了解。
后文中提到的的 OpenGL 一般是 OpenGL ES 2.0 的簡稱疯特。
我們先來看一下demo 效果:
著色器
簡單地說哗魂, OpenGL 程序就是把一個頂點(diǎn)著色器和一個片元著色器連接在一起變成一個 OpenGL 程序。著色器分為兩種漓雅,頂點(diǎn)著色器和片段著色器录别。前者生成每個頂點(diǎn)的最終位置朽色,OpenGL 可以將他們組裝成點(diǎn)、線和三角形组题。后者是為點(diǎn)葫男、線和三角形的每個片段著色。OpenGL 的繪制渲染崔列,通過著色器程序梢褐,把一個物體的頂點(diǎn)和片段都由著色器處理放到管道中進(jìn)行渲染成型。所以我們需要創(chuàng)建這兩個著色器對象赵讯。
OpenGL ES 2.0 中盈咳,繪制的所有物體都是由點(diǎn)、線边翼,三角面組成鱼响。比如四邊形,就是由兩個三角形組成组底。
創(chuàng)建著色器對象
在Android中丈积,著色器語言不能像我們有編譯器一樣處理方便,從編譯鏈接運(yùn)行都由可視化界面完成债鸡,而是要我們進(jìn)行手動工作江滨,以字符串的形式,通過 OpenGL 相關(guān) API 進(jìn)行編譯鏈接運(yùn)行娘锁。
首先,我們在 main 包下創(chuàng)建 assets 文件夾饺鹃,然后創(chuàng)建文件vertex.glsl
莫秆,在里面編寫頂點(diǎn)著色器相關(guān)代碼。創(chuàng)建 frag.glsl
悔详,在里面編寫片元著色器相關(guān)代碼镊屎。
//assets/vertex.glsl
uniform mat4 uMVPMatrix; //總變換矩陣
attribute vec3 aPosition; //頂點(diǎn)位置
attribute vec4 aColor; //頂點(diǎn)顏色
varying vec4 aaColor; //用于傳遞給片元著色器的變量
void main()
{
gl_Position = uMVPMatrix * vec4(aPosition,1); //根據(jù)總變換矩陣計算此次繪制此頂點(diǎn)位置
aaColor = aColor;//將接收的顏色傳遞給片元著色器
}
//assets/frag.glsl
precision mediump float;
varying vec4 aaColor; //接收從頂點(diǎn)著色器傳來的參數(shù)
void main(){
gl_FragColor = aaColor;//給此片元著色
}
上面頂點(diǎn)著色器中變換矩陣是為了我們在進(jìn)行平移旋轉(zhuǎn)縮放等操作時候,通過矩陣計算茄螃,得出變換后頂點(diǎn)的位置缝驳。在這篇文章里面不對著色器語言作較多的闡述,我們在這里主要是把整個繪制流程走完归苍。
創(chuàng)建完成后用狱,再在 java 中讀取著色器程序文本,調(diào)用 OpenGL 的相關(guān)方法對他進(jìn)行編譯拼弃。為了程序的簡潔和耦合性夏伊,我們可以把相關(guān)步驟提取到一個工具類中執(zhí)行。
我們先創(chuàng)建一個 ShaderUtil 工具類吻氧,集中幫助我們處理對著色器程序的處理溺忧。
/**
* 加載著色器咏连,編譯著色器
* @param type 著色器類型 GLES20.GL_VERTEX_SHADER GLES20.GL_FRAGMENT_SHADER
* @param shaderString 著色器程序內(nèi)容文本字符串
* @return 著色器程序id
*/
private static int loadShader(int type, String shaderString) {
//創(chuàng)建著色器對象
int shaderid = GLES20.glCreateShader(type);
if(shaderid != 0){//創(chuàng)建成功
//加載著色器代碼到著色器對象
GLES20.glShaderSource(shaderid, shaderString);
//編譯著色器
GLES20.glCompileShader(shaderid);
//存放編譯成功Shader數(shù)量數(shù)組
int[] compileStatus = new int[1];
//獲取shader編譯情況
GLES20.glGetShaderiv(shaderid, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
if(compileStatus[0] == 0){
//編譯失敗,顯示日志并刪除該對象
Log.e("GLES20", "Could not compile shader " + type + ":");
Log.e("GLES20", GLES20.glGetShaderInfoLog(shaderid));
GLES20.glDeleteShader(shaderid);
return 0;
}
}
return shaderid;
}
/**
* 從 assets 資源文件夾中讀取著色器內(nèi)容
*
* @param fname 著色器文件名稱
* @param r
* @return 著色器內(nèi)容
*/
public static String loadFromAssetsFile(String fname, Resources r) {
String result = null;
try {
InputStream in = r.getAssets().open(fname);
int ch = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((ch = in.read()) != -1) {
baos.write(ch);
}
byte[] buff = baos.toByteArray();
baos.close();
in.close();
result = new String(buff, "UTF-8");
result.replaceAll("\\r\\n", "\n");
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
上面 loadShader()
這個方法就是將著色器程序內(nèi)容文本字符串進(jìn)行編譯鲁森,并返回改著色器對象id祟滴。
另外可以看到我們創(chuàng)建了 compileStatus
數(shù)組。這是 Android 平臺的 OpenGL 的一個通用模式歌溉。為了取出一個值垄懂,我們通常會創(chuàng)建一個長度為 1 的數(shù)組,并把這個數(shù)組傳進(jìn) OpenGL 中調(diào)用研底,OpenGL 會將結(jié)果值賦值到這個數(shù)據(jù)里面埠偿。
鏈接程序和著色器對象
要創(chuàng)建 OpenGL 程序,只要調(diào)用 GLES20.glCreateProgram();
即可創(chuàng)建著色器程序?qū)ο?榜晦,該方法會返回一個 id 冠蒋,我們可以用過這個 id 對 OpenGL 程序進(jìn)行各種操作。
但是這其實(shí)只是相當(dāng)于聲明了一個對象乾胶,還沒初始化抖剿,我們要成功創(chuàng)建 OpenGL 程序,還要把著色器對象鏈接到到程序當(dāng)中识窿。鏈接完成后讀取鏈接狀態(tài)斩郎,成功后才能認(rèn)為創(chuàng)建著色器程序成功。后面就可以拿該程序 id 進(jìn)行操作使用喻频。我們在 ShaderUtil 下繼續(xù)添加一個方法缩宜,完成對 OpenGL 程序的創(chuàng)建。
//ShaderUtil.java
/**
* 創(chuàng)建著色器程序
* @param vertexShader
* @param fragmentShader
* @return
*/
public static int createProgram(String vertexShader, String fragmentShader) {
//加載頂點(diǎn)著色器
int vertexShaderId = loadShader(GLES20.GL_VERTEX_SHADER, vertexShader);
if(vertexShaderId == 0){
return 0;
}
//加載片元著色器
int fragShaderId = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);
if (fragShaderId == 0){
return 0;
}
//創(chuàng)建著色器程序
int program = GLES20.glCreateProgram();
//在程序中加入頂點(diǎn)著色器和片元著色器
if(program !=0 ){
//加入頂點(diǎn)著色器
GLES20.glAttachShader(program, vertexShaderId);
checkGlError("glAttachShader");
//加入片元著色器
GLES20.glAttachShader(program, fragShaderId);
checkGlError("glAttachShader");
//鏈接程序
GLES20.glLinkProgram(program);
//存放成功的程序
int[] linkStatus = new int[1];
//獲取program鏈接情況
GLES20.glGetProgramiv(program, 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(program));
GLES20.glDeleteProgram(program);
return 0;
}
}
return program;
}
以上就完成了對著色器的創(chuàng)建锻煌,并成功返回 OpenGL 程序的 id。
創(chuàng)建物體
既然 OpenGL 程序的邏輯已經(jīng)完成姻蚓,那么就是要實(shí)現(xiàn)繪制一個圖形了宋梧。我們先從簡單的 2D 圖形入手。我們要繪制三個三角形如文章開頭所示狰挡,要怎么繪制呢捂龄?我們可以先繪制一個三角形,后面的三角形只是改變了 z 軸坐標(biāo)加叁,就可以改變他在前在后了倦沧。另外為了直觀演示,我們再將第二個三角形旋轉(zhuǎn)180度它匕,并向 Y 軸平移一定的距離刀脏。這樣基本就能實(shí)現(xiàn) demo 的樣子了。接下來我們說說如何去實(shí)現(xiàn)超凳。
一個物體由多個頂點(diǎn)組成愈污,每個頂點(diǎn)里面帶有多種屬性耀态,包括了坐標(biāo),顏色暂雹,等等首装。在這里我們一般是使用數(shù)組進(jìn)行存儲。
創(chuàng)建數(shù)組
對物體創(chuàng)建頂點(diǎn)坐標(biāo)和顏色的數(shù)組杭跪,里面就包含了物體的屬性仙逻。
要繪制一個平面 2D 三角形,三角形有三個頂點(diǎn)涧尿,每個頂點(diǎn)的屬性里面帶有位置(x,y,z)
和顏色 (r,g,b,a)
系奉,那么這個數(shù)組就有7*3個數(shù)據(jù)。其中每7個浮點(diǎn)數(shù)姑廉,組成的集合代表一個頂點(diǎn)缺亮。
最后,我們會將該數(shù)組傳入到緩沖區(qū)桥言,OpenGL將會讀取緩沖區(qū)的內(nèi)容賦值到著色器中萌踱,進(jìn)行渲染繪制。
我們先創(chuàng)建一個物體類号阿。關(guān)于這個物體的初始化等操作都在里面并鸵。
//Triangle.java
/**
* 初始化頂點(diǎn)數(shù)據(jù)
*/
private void initVertexData(float z) {
float[] vertexArray = {
// x, y, z, r,g,b,a
0f, -0.5f, z, 1f, 0f, 0f, 0f,
0.5f, 0.5f, z, 0f, 1f, 0f, 0f,
-0.5f, 0.5f, z, 0f, 0f, 1f, 0f
};
//
mTriangleBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4)//一個float四個字節(jié)
.order(ByteOrder.nativeOrder())//設(shè)置字節(jié)順序與操作系統(tǒng)一致
.asFloatBuffer()
.put(vertexArray);//放入緩存
mTriangleBuffer.position(0);
}
上面我們在數(shù)組中定義了這個三角形的 (x,y,z,r,g,b,a),由于 OpenGL 對坐標(biāo)進(jìn)行了歸一化扔涧,所以你會看到上面的數(shù)據(jù)都在 [-1,1] 這個范圍园担。這個坐標(biāo)系的圓心是屏幕中央,右x正上y正(下一小節(jié)矩陣變換中有圖例)枯夜。在這里你可以暫時的認(rèn)為歸一化就是把觀察坐標(biāo)范圍弯汰,轉(zhuǎn)為 [-1,1] 這個范圍。
OpenGL 的坐標(biāo)系統(tǒng)比較多卤档,容易產(chǎn)生混淆蝙泼,攤開來說又可以水一篇文章了程剥,所以在這里就不一一累述了劝枣。
然后我們會將這個數(shù)組放入緩存中。mTriangleBuffer 的類型是 FloatBuffer织鲸,在后面繪制舔腾,或者是對 OpenGL 程序里面的數(shù)據(jù)修改,如對物體進(jìn)行矩陣變換等都會用到搂擦。
OpenGL 中推薦頂點(diǎn)按逆時針順序排列稳诚,其內(nèi)部做了優(yōu)化,讓性能更佳瀑踢。
初始化著色器
一個物體扳还,除了基本數(shù)據(jù)才避,如果需要著色繪制,就少不了著色器氨距。所以我們初始化完頂點(diǎn)數(shù)據(jù)后桑逝,就要初始化著色器了。我們在上面編寫的 ShaderUtil 在這里就可以派上用場俏让。
在這里我們初始化著色器楞遏,并把 OpenGL 程序 id 記錄下來。然后我們就可以通過這個 id首昔,獲取到著色器代碼里面的相關(guān)變量引用寡喝。這些變量幫助我們在后面繪制的時候,橋接 java 本地變量和著色器變量勒奇,讓他們關(guān)聯(lián)更新预鬓。
/**
* 初始化著色器
* @param mv
*/
private void initShader(MySurfaceView mv) {
//加載著色器內(nèi)容
mVertexShader = ShaderUtils.loadFromAssetsFile("vertex.glsl", mv.getResources());
mFragmentShader = ShaderUtils.loadFromAssetsFile("frag.glsl", mv.getResources());
//創(chuàng)建OpenGL程序
mProgramId = ShaderUtils.createProgram(mVertexShader, mFragmentShader);
//獲取程序中頂點(diǎn)位置屬性引用id
mVertexPositionHandle = GLES20.glGetAttribLocation(mProgramId, "aPosition");
//獲取程序中頂點(diǎn)顏色屬性引用id
mVertexColorHandle = GLES20.glGetAttribLocation(mProgramId, "aColor");
//獲取程序中總變換矩陣引用id
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramId, "uMVPMatrix");
}
以上兩步,我們可以封裝到物體對象的構(gòu)造方法里面撬陵,這樣每次構(gòu)建一個物體的時候珊皿,就會自動創(chuàng)建數(shù)組和初始化著色器。
public Triangle(MySurfaceView mv, float z) {
//初始化頂點(diǎn)
initVertexData(z);
//初始化著色器
initShader(mv);
}
繪制
在 OpenGL 中巨税,物體的移動蟋定,旋轉(zhuǎn),縮放等都是依賴于矩陣的變換草添。所以我們這里也會維護(hù)一個矩陣驶兜。關(guān)于矩陣我們會創(chuàng)建一個矩陣的工具類。在下一節(jié)說明远寸。
然后我們就會將矩陣抄淑,頂點(diǎn),顏色等數(shù)據(jù)驰后,傳遞到OpenGL程序肆资,并進(jìn)行繪制。這部分代碼可以抽取出來封裝到方法drawSelf()
里面灶芝。
public void drawSelf() {
//使用某套OpenGL程序
GLES20.glUseProgram(mProgramId);
//將最終變換矩陣傳入OpenGl程序
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
//為畫筆指定頂點(diǎn)位置數(shù)據(jù)
mTriangleBuffer.position(0);
GLES20.glVertexAttribPointer(mVertexPositionHandle, 3, GLES20.GL_FLOAT, false, STRIDE, mTriangleBuffer);
//為畫筆指定顏色著色數(shù)據(jù)
mTriangleBuffer.position(3);
GLES20.glVertexAttribPointer(mVertexColorHandle, 3, GLES20.GL_FLOAT, false, STRIDE, mTriangleBuffer);
GLES20.glEnableVertexAttribArray(mVertexPositionHandle);
GLES20.glEnableVertexAttribArray(mVertexColorHandle);
//繪制內(nèi)容
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 1);
}
上面代碼就是通過 glVertexAttribPointer
將 buffer 傳入到 OpenGL 程序中郑原,最后通過 glDrawArrays
繪制出來。
由于我們物體構(gòu)建的時候是一個數(shù)組夜涕,7個數(shù)據(jù)為1個頂點(diǎn)犯犁,其中前面的坐標(biāo)是(x, y, z)
三個數(shù)據(jù)。所以可以看到為畫筆指定顏色著色數(shù)據(jù)的時候女器,使用position將指針移動到了3酸役。不然如果position指針指向0,會把坐標(biāo)位置當(dāng)做顏色來處理。
STRIDE 可以認(rèn)為是 sizeOf(頂點(diǎn))涣澡,我們一個頂點(diǎn)有7個數(shù)據(jù)贱呐。每個數(shù)據(jù)都是一個浮點(diǎn)數(shù)。所以STRIDE = ( position(3) + color(4) ) * 4 = ( 3 + 4 ) * 4
入桂。
矩陣變換
觀察位置
三維世界吼句,僅有物體是不能確定看到物體是哪一面的,你還需要一個觀察的角度事格,距離惕艳,視點(diǎn)。
我們可以把這些需要的東西驹愚,當(dāng)做是一臺相機(jī)远搪。相機(jī)的攝像頭就是視點(diǎn),攝像頭對準(zhǔn)的方向就是觀察方向逢捺,對焦的地方就是觀察的目標(biāo)點(diǎn)谁鳍,相機(jī)還需要一個向上的向量,標(biāo)記你相機(jī)傾斜角度劫瞳,仰視俯視之類的倘潜。OpenGL 中有 Matrix 類的 setLookAtM() 方法。
Matrix.setLookAtM(
mVMatrix,0,
cx,cy,cz,
tx,ty,tz,
upx,upy,upz);
c就是camera志于,標(biāo)記的是相機(jī)的(x,y,z)坐標(biāo)涮因。t就是target,標(biāo)記的是目標(biāo)點(diǎn)的(x,y,z)坐標(biāo)伺绽。up標(biāo)記的就是向上的向量养泡。標(biāo)記的是向上的方向。下圖可以幫助你理解這三個分量奈应。最后就是將這些數(shù)據(jù)存入到 mVMatrix 中澜掩。第二個參數(shù)是偏移量,我們更多情況默認(rèn)為0杖挣。下圖可以幫助你理解肩榕。
正交投影
確定了物體,觀察位置惩妇,然后就要說這個投影空間了株汉。OpenGL 中,根據(jù)應(yīng)用程序提供的投影矩陣屿附,來確定一個可視空間郎逃。投影主要分為正交投影和透視投影兩種哥童。在這里暫且只說正交投影挺份。
正交投影是沒有近大遠(yuǎn)小效果的≈福可以認(rèn)為是有平行光從遠(yuǎn)平面射過來匀泊,物體在可視空間內(nèi)优训,產(chǎn)生投影到近平面的影子痢畜,就是我看在視點(diǎn)看到的部分了抹缕。
同樣地,OpenGL 在 Matrix 類中也有方法 orthoM达址,對著上圖我們可以看出各參數(shù)含義躲因。
Matrix.orthoM(mProjectMatrix, 0, left, right, bottom, top, near, far);
變換
我們熟知的變換方式有 平移早敬,旋轉(zhuǎn),縮放大脉。Matrix 類中都有對應(yīng)的方法進(jìn)行操作搞监。我們可以創(chuàng)建一個4階矩陣 mCurrMatrix,保存每一步的運(yùn)動變換镰矿。
MatrixState 輔助類
以上概念了解完后琐驴,我們可以編寫一個簡單的 Matrix 輔助類,后期可以繼續(xù)完善秤标。
public class MatrixState {
/**
* 投影矩陣
*/
private static float[] mProjectMatrix = new float[16];
/**
* 攝像機(jī)位置朝向9參數(shù)矩陣
*/
private static float[] mVMatrix = new float[16];
/**
* 當(dāng)前變換矩陣
*/
private static float[] mCurrMatrix;
/**
* 最后起作用的總變換矩陣
*/
private static float[] mMVPMatrix;
public static Stack<float[]> mStack = new Stack<float[]>();//保護(hù)變換矩陣的棧
public static void setInitStack()//獲取不變換初始矩陣
{
mCurrMatrix = new float[16];
Matrix.setRotateM(mCurrMatrix, 0, 0, 1, 0, 0);
}
public static void pushMatrix()//保護(hù)變換矩陣
{
mStack.push(mCurrMatrix.clone());
}
public static void popMatrix()//恢復(fù)變換矩陣
{
mCurrMatrix = mStack.pop();
}
/**
* 設(shè)置攝像機(jī)
*
* @param cx 攝像機(jī)位置x
* @param cy 攝像機(jī)位置y
* @param cz 攝像機(jī)位置z
* @param tx 攝像機(jī)目標(biāo)點(diǎn)x
* @param ty 攝像機(jī)目標(biāo)點(diǎn)y
* @param tz 攝像機(jī)目標(biāo)點(diǎn)z
* @param upx 攝像機(jī)UP向量X分量
* @param upy 攝像機(jī)UP向量Y分量
* @param upz 攝像機(jī)UP向量Z分量
*/
public static void setCamera(
float cx,
float cy,
float cz,
float tx,
float ty,
float tz,
float upx,
float upy,
float upz
) {
Matrix.setLookAtM(
mVMatrix,
0,
cx,
cy,
cz,
tx,
ty,
tz,
upx,
upy,
upz
);
}
/**
* 設(shè)置正交投影矩陣
*
* @param left near面的left
* @param right near面的right
* @param bottom near面的bottom
* @param top near面的top
* @param near near面距離
* @param far far面距離
* @return
*/
public static void setProjectOrtho(
float left,
float right,
float bottom,
float top,
float near,
float far
) {
Matrix.orthoM(mProjectMatrix, 0, left, right, bottom, top, near, far);
}
public static void translate(float x, float y, float z)//設(shè)置沿xyz軸移動
{
Matrix.translateM(mCurrMatrix, 0, x, y, z);
}
public static void rotate(float angle, float x, float y, float z)//設(shè)置繞xyz軸移動
{
Matrix.rotateM(mCurrMatrix, 0, angle, x, y, z);
}
/**
* 獲取具體物體的總變換矩陣
*
* @return
*/
public static float[] getFinalMatrix() {
mMVPMatrix = new float[16];
Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mCurrMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjectMatrix, 0, mMVPMatrix, 0);
return mMVPMatrix;
}
}
上面這個矩陣輔助類中绝淡,維護(hù)了一個 mCurrMatrix 矩陣,這個矩陣是用于我們記錄當(dāng)前對坐標(biāo)系做運(yùn)動變換(平移旋轉(zhuǎn))苍姜。mProjectMatrix 則是記錄了我們是進(jìn)行平行投影還是透視投影牢酵。最后使用矩陣相乘,把結(jié)果放入到 mMVPMatrix 這個最終結(jié)果矩陣中衙猪。
此外茁帽,添加了一個棧對矩陣狀態(tài)進(jìn)行了維護(hù),他可以讓我們管理變換的時候更為便捷(可以聯(lián)想到 Canvas.save & Canvas.restore)屈嗤。
最后我們定義了 getFinalMatrix()
方法潘拨,可以得到這個最終矩陣,將其賦值到 OpenGL 程序的總變換矩陣饶号。
GLSurfaceView
GLsurfaceView 作用就是設(shè)置一個窗口铁追,把 OpenGL 渲染繪制的內(nèi)容呈現(xiàn)到這個窗口上。此外我們與用戶的交互茫船,如觸摸事件也往往在這里完成琅束。
定義渲染器Renderer
在 GLSurfaceView 里面定義一個內(nèi)部類實(shí)現(xiàn) Renderer 接口。然后會讓你實(shí)現(xiàn)三個方法算谈。在這里我們就是將上面的著色器對象涩禀,創(chuàng)建物體做初始化,并進(jìn)行渲染然眼。
- onSurfaceCreated(GL10 gl, EGLConfig config) :做初始化操作
- onSurfaceChanged(GL10 gl, int width, int height):設(shè)置視圖窗口大小
- onDrawFrame(GL10 gl) :渲染物體
需要注意的是艾船,參數(shù)里面的GL10是1.0版本的 OpenGL API,我們不要使用。而是使用 GLES20 的相關(guān)API屿岂。
private class SceneRenderer implements Renderer {
Triangle[] triangles = new Triangle[3];
float xAngle = 0;
float yAngle = 0;
@Override
public void onDrawFrame(GL10 gl) {
//清除深度緩沖與顏色緩沖
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
//繪制物體
//記錄初始狀態(tài)
MatrixState.pushMatrix();
//設(shè)置旋轉(zhuǎn)值
MatrixState.rotate(yAngle, 0, 1, 0);
MatrixState.rotate(xAngle, 1, 0, 0);
//開始繪制
triangles[0].drawSelf();
//記錄旋轉(zhuǎn)后的矩陣
MatrixState.pushMatrix();
//對第二個三角形進(jìn)行180度旋轉(zhuǎn)践宴,且往y軸方向平移,讓中間的三角形與其他兩個三角形相錯
MatrixState.rotate(180, 0, 0, 1);
MatrixState.translate(0, -0.5f, 0);
triangles[1].drawSelf();
//恢復(fù)
MatrixState.popMatrix();
triangles[2].drawSelf();
MatrixState.popMatrix();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//設(shè)置窗口大小和位置
GLES20.glViewport(0, 0, width, height);
//計算GLSurfaceView寬高比
float ratio = (float) width / (float) height;
//設(shè)置平行投影
MatrixState.setProjectOrtho(-ratio, ratio, -1, 1, 1, 10);
//調(diào)用此方法產(chǎn)生攝像機(jī)9參矩陣
MatrixState.setCamera(0, 0, 3f,
0, 0, 0f,
0, 1.0f, 0f);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//設(shè)置屏幕背景色
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
//創(chuàng)建各個對象
for (int i = 0; i < triangles.length; i++) {
triangles[i] = new Triangle(MySurfaceView.this, -0.3f * i);
}
//打開深度檢測
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
//打開背面剪裁
GLES20.glEnable(GLES20.GL_CULL_FACE);
//初始化變換矩陣
MatrixState.setInitStack();
}
}
設(shè)置渲染器
定義好渲染器之后爷怀,我們可以在 GLSurfaceView 的構(gòu)造方法里面做初始化阻肩,把渲染器設(shè)置到該View上。
public MySurfaceView(Context context) {
super(context);
//使用OpenGL2.0
this.setEGLContextClientVersion(2);
mRenderer = new SceneRenderer();
setRenderer(mRenderer);
//主動渲染模式运授,不斷地烤惊,連續(xù)地
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
觸摸事件
想要相應(yīng)用戶觸摸事件,還要重寫攔截方法吁朦,在里面對創(chuàng)建物體的旋轉(zhuǎn)角度做改變撕氧。我們創(chuàng)建了 MatrixState 矩陣輔助類,可以對坐標(biāo)系做平移旋轉(zhuǎn)變化喇完,所以我們只需要改變矩陣的旋轉(zhuǎn)值就可以了伦泥。
@Override
public boolean onTouchEvent(MotionEvent event) {
float y = event.getY();
float x = event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
float dy = y - mPreviousY;
float dx = x - mPreviousX;
mRenderer.yAngle += dy * TOUCH_SCALE_FACTOR;//設(shè)置繞y軸旋轉(zhuǎn)角度
mRenderer.xAngle += dx * TOUCH_SCALE_FACTOR;//設(shè)置繞x軸旋轉(zhuǎn)角度
requestRender();
break;
}
mPreviousX = x;
mPreviousY = y;
return true;
}
運(yùn)行程序
最后當(dāng)然就是運(yùn)行程序了。我們只需要在 Activity 上創(chuàng)建該GLSurfaceView
對象锦溪,并在 setContentView(glSurfaceView)
傳入不脯,那么運(yùn)行程序之后就可以看到繪制的物體了。
最后刻诊,附上 demo 地址