之前文章:
《OpenGL從入門(mén)到放棄01 》一些基本概念
《OpenGL從入門(mén)到放棄02 》GLSurfaceView和Renderer
上一篇文章我們看到三角形形狀跟預(yù)期有點(diǎn)不一樣
這是OpenGL坐標(biāo)系的問(wèn)題,對(duì)坐標(biāo)系不明白可以看第一節(jié)蛛碌,這一節(jié)我們通過(guò)投影和相機(jī)視圖來(lái)解決這個(gè)問(wèn)題。
先來(lái)個(gè)概念,之后再上代碼
相機(jī)
生活中我們拍照没炒,你站的高度猾昆,拿相機(jī)的位置匆骗,姿勢(shì)不同,拍出來(lái)的照片也就不一樣
在Android OpenGLES程序中配紫,我們可以通過(guò)以下方法來(lái)進(jìn)行相機(jī)設(shè)置:
Matrix.setLookAtM (float[] rm, //接收相機(jī)變換矩陣
int rmOffset, //變換矩陣的起始位置(偏移量)
float eyeX,float eyeY, float eyeZ, //相機(jī)位置
float centerX,float centerY,float centerZ, //觀測(cè)點(diǎn)位置
float upX,float upY,float upZ) //up向量在xyz上的分量
相機(jī)位置:相機(jī)的位置是比較好理解的,就是相機(jī)在3D空間里面的坐標(biāo)點(diǎn)午阵,
相機(jī)觀察方向:相機(jī)的觀察方向躺孝,對(duì)應(yīng)center向量,表示的是相機(jī)鏡頭的朝向底桂,你可以朝前拍括细、朝后拍、也可以朝左朝右戚啥,或者其他的方向奋单。
相機(jī)UP方向:相機(jī)的UP方向,可以理解為相機(jī)頂端指向的方向猫十。比如你把相機(jī)斜著拿著览濒,拍出來(lái)的照片就是斜著的呆盖,你倒著拿著,拍出來(lái)的就是倒著的贷笛。
如圖:center的X,Y,Z表示相機(jī)的正方向的方向向量应又,up的X,Y,Z表示相機(jī)的上方向的方向向量。因?yàn)榭臻g中想要確定一個(gè)具體朝向乏苦,必須要有兩組向量株扛,所以此函數(shù)需要一個(gè)點(diǎn)和兩組向量來(lái)確定相機(jī)的位置和具體朝向。
投影
使用OpenGl繪制的3D圖形汇荐,需要展示在移動(dòng)端2D設(shè)備上洞就,這就需要投影。
OpenGl ES中有兩種投影方式:一種是正交投影掀淘,一種是透視投影:
正交投影
投影物體的大小不會(huì)隨觀察點(diǎn)的遠(yuǎn)近而發(fā)生變化旬蟋,我們可以使用下面方法來(lái)執(zhí)行正交投影
Matrix.orthoM (float[] m, //接收正交投影的變換矩陣
int mOffset, //變換矩陣的起始位置(偏移量)
float left, //相對(duì)觀察點(diǎn)近面的左邊距
float right, //相對(duì)觀察點(diǎn)近面的右邊距
float bottom, //相對(duì)觀察點(diǎn)近面的下邊距
float top, //相對(duì)觀察點(diǎn)近面的上邊距
float near, //相對(duì)觀察點(diǎn)近面距離
float far) //相對(duì)觀察點(diǎn)遠(yuǎn)面距離
透視投影
隨觀察點(diǎn)的距離變化而變化,觀察點(diǎn)越遠(yuǎn)革娄,視圖越小倾贰,反之越大,我們可以通過(guò)如下方法來(lái)設(shè)置透視投影:
Matrix.frustumM (float[] m, //接收透視投影的變換矩陣
int mOffset, //變換矩陣的起始位置(偏移量)
float left, //相對(duì)觀察點(diǎn)近面的左邊距
float right, //相對(duì)觀察點(diǎn)近面的右邊距
float bottom, //相對(duì)觀察點(diǎn)近面的下邊距
float top, //相對(duì)觀察點(diǎn)近面的上邊距
float near, //相對(duì)觀察點(diǎn)近面距離
float far) //相對(duì)觀察點(diǎn)遠(yuǎn)面距離
只有繪制在近平面和遠(yuǎn)平面之間的內(nèi)容拦惋,才能被用戶看到
轉(zhuǎn)換矩陣(變換矩陣)
轉(zhuǎn)換矩陣用來(lái)做什么的呢匆浙?
是否記得上面我們繪制的圖形坐標(biāo)需要轉(zhuǎn)換為OpenGl中能處理的小端字節(jié)序(LittleEdian)
沒(méi)錯(cuò),轉(zhuǎn)換矩陣就是用來(lái)將數(shù)據(jù)轉(zhuǎn)為OpenGl ES可用的數(shù)據(jù)字節(jié)厕妖,我們將相機(jī)視圖矩陣和投影矩陣相乘吞彤,便得到一個(gè)轉(zhuǎn)換矩陣,然后我們?cè)賹⒋司仃噦鹘o頂點(diǎn)著色器叹放,具體使用方法及參數(shù)說(shuō)明如下:
Matrix.multiplyMM (float[] result, //接收相乘結(jié)果
int resultOffset, //接收矩陣的起始位置(偏移量)
float[] lhs, //左矩陣
int lhsOffset, //左矩陣的起始位置(偏移量)
float[] rhs, //右矩陣
int rhsOffset) //右矩陣的起始位置(偏移量)
下面簡(jiǎn)單講解下如何使用投影和相機(jī)視圖來(lái)實(shí)現(xiàn)矩陣變換并傳遞給頂點(diǎn)著色器
實(shí)戰(zhàn)
1饰恕、定義一個(gè)投影矩陣、相機(jī)矩陣井仰、變換矩陣
private final float[] mMVPMatrix = new float[16]; //變換矩陣(投影矩陣*相機(jī)矩陣的結(jié)果埋嵌,最終要傳遞給頂點(diǎn)著色器)
private final float[] mProjectionMatrix = new float[16]; //投影矩陣
private final float[] mViewMatrix = new float[16]; //相機(jī)位置矩陣
2、onSurfaceChanged 中進(jìn)行設(shè)置
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 設(shè)置繪圖的窗口(可以理解成在畫(huà)布上劃出一塊區(qū)域來(lái)畫(huà)圖)
GLES20.glViewport(0,0,width,height);
/**投影和相機(jī)視圖相關(guān)**/
float ratio = (float) width / height;
//設(shè)置正交投影
// Matrix.orthoM (mProjectionMatrix, 0, -ratio, ratio, -1, 2, 3, 7);
Matrix.orthoM (mProjectionMatrix, 0,0,width, height, 0, -width, width);//這個(gè)投影會(huì)跟屏幕坐標(biāo)關(guān)聯(lián)上
//設(shè)置透視投影(觀察點(diǎn)越遠(yuǎn)俱恶,視圖越斜⑧隆),這個(gè)投影矩陣被應(yīng)用于對(duì)象坐標(biāo)在onDrawFrame()方法中
// Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 2, 3, 7);
//設(shè)置相機(jī)位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7f, 0f, 0f, 0f, 0f, 1.0f, 1.0f);
//計(jì)算變換矩陣,最終mMVPMatrix要傳遞給頂點(diǎn)著色器
Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,mViewMatrix,0);
}
至此就得到了一個(gè)變換矩陣 mMVPMatrix合是,最終需要傳遞給頂點(diǎn)著色器
3了罪、修改三角形代碼
在上一節(jié)代碼的基礎(chǔ)上修改
1、修改頂點(diǎn)著色器
// 頂點(diǎn)著色器的腳本
String vertexShaderCode =
"uniform mat4 uMVPMatrix;" + //接收傳入的轉(zhuǎn)換矩陣
" attribute vec4 vPosition;" + //接收傳入的頂點(diǎn)
" void main() {" +
" gl_Position = uMVPMatrix * vPosition;" + //矩陣變換計(jì)算之后的位置
" }";
2聪全、增加變量
//變換矩陣句柄
private int mMVPMatrixHandle;
//變換矩陣泊藕,提供set方法
private float[] mvpMatrix = new float[16];
public void setMvpMatrix(float[] mvpMatrix) {
this.mvpMatrix = mvpMatrix;
}
3、修改draw方法
draw方法主要是將我們的數(shù)據(jù)傳遞給著色器难礼,既然頂點(diǎn)著色器增加了矩陣變量娃圆,那么我們需要把變換矩陣傳遞給頂點(diǎn)著色器
// 獲取變換矩陣的句柄
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// 將投影和視圖轉(zhuǎn)換傳遞給著色器玫锋,可以理解為給頂點(diǎn)著色器中uMVPMatrix這個(gè)變量賦值為mvpMatrix
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
4、在onDrawFrame中繪制
mGlTriangle02.setMvpMatrix(mMVPMatrix);
mGlTriangle02.draw();
mGlTriangle02在onSurfaceCreated中new出來(lái)讼呢,然后就可以看到效果了
看起來(lái)差不多了撩鹿,可以看到頂點(diǎn)位置不變的情況下,使用投影跟相機(jī)視圖確實(shí)能改變2d效果悦屏。
最后貼下三角形的全部代碼
public class GLTriangle02{
// 頂點(diǎn)著色器的腳本
String vertexShaderCode =
"uniform mat4 uMVPMatrix;" + //接收傳入的轉(zhuǎn)換矩陣
" attribute vec4 vPosition;" + //接收傳入的頂點(diǎn)
" void main() {" +
" gl_Position = uMVPMatrix * vPosition;" + //矩陣變換計(jì)算之后的位置
" }";
// 片元著色器的腳本
String fragmentShaderCode =
" precision mediump float;" + // 聲明float類(lèi)型的精度為中等(精度越高越耗資源)
" uniform vec4 vColor;" + // 接收傳入的顏色
" void main() {" +
" gl_FragColor = vColor;" + // 給此片元的填充色
" }";
private FloatBuffer vertexBuffer; //頂點(diǎn)坐標(biāo)數(shù)據(jù)要轉(zhuǎn)化成FloatBuffer格式
// 數(shù)組中每3個(gè)值作為一個(gè)坐標(biāo)點(diǎn)
static final int COORDS_PER_VERTEX = 3;
//三角形的坐標(biāo)數(shù)組
static float triangleCoords[] = {
0.0f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
//頂點(diǎn)個(gè)數(shù)节沦,計(jì)算得出
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
//一個(gè)頂點(diǎn)有3個(gè)float,一個(gè)float是4個(gè)字節(jié)础爬,所以一個(gè)頂點(diǎn)要12字節(jié)
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per mVertex
//三角形的顏色數(shù)組甫贯,rgba
private float[] mColor = {
0.0f, 1.0f, 0.0f, 1.0f,
};
//當(dāng)前繪制的頂點(diǎn)位置句柄
private int vPositionHandle;
//片元著色器顏色句柄
private int vColorHandle;
//變換矩陣句柄
private int mMVPMatrixHandle;
//這個(gè)可以理解為一個(gè)OpenGL程序句柄
private final int mProgram;
//變換矩陣,提供set方法
private float[] mvpMatrix = new float[16];
public void setMvpMatrix(float[] mvpMatrix) {
this.mvpMatrix = mvpMatrix;
}
public GLTriangle02() {
/** 1幕帆、數(shù)據(jù)轉(zhuǎn)換获搏,頂點(diǎn)坐標(biāo)數(shù)據(jù)float類(lèi)型轉(zhuǎn)換成OpenGL格式FloatBuffer赖条,int和short同理*/
vertexBuffer = GLUtil.floatArray2FloatBuffer(triangleCoords);
/** 2失乾、加載編譯頂點(diǎn)著色器和片元著色器*/
int vertexShader = GLUtil.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = GLUtil.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
/** 3、創(chuàng)建空的OpenGL ES程序纬乍,并把著色器添加進(jìn)去*/
mProgram = GLES20.glCreateProgram();
// 添加頂點(diǎn)著色器到程序中
GLES20.glAttachShader(mProgram, vertexShader);
// 添加片段著色器到程序中
GLES20.glAttachShader(mProgram, fragmentShader);
/** 4碱茁、鏈接程序*/
GLES20.glLinkProgram(mProgram);
}
public void draw() {
// 將程序添加到OpenGL ES環(huán)境
GLES20.glUseProgram(mProgram);
/***1.獲取句柄*/
// 獲取頂點(diǎn)著色器的位置的句柄(這里可以理解為當(dāng)前繪制的頂點(diǎn)位置)
vPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 獲取片段著色器的vColor句柄
vColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 獲取變換矩陣的句柄
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
/**2.設(shè)置數(shù)據(jù)*/
// 啟用頂點(diǎn)屬性,最后對(duì)應(yīng)禁用
GLES20.glEnableVertexAttribArray(vPositionHandle);
//準(zhǔn)備三角形坐標(biāo)數(shù)據(jù)
GLES20.glVertexAttribPointer(vPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 設(shè)置繪制三角形的顏色仿贬,給vColor 這個(gè)變量賦值
GLES20.glUniform4fv(vColorHandle, 1, mColor, 0);
// 將投影和視圖轉(zhuǎn)換傳遞給著色器纽竣,可以理解為給uMVPMatrix這個(gè)變量賦值為mvpMatrix
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
/** 3.繪制三角形,三個(gè)頂點(diǎn)*/
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用頂點(diǎn)數(shù)組(好像不禁用也沒(méi)啥問(wèn)題)
GLES20.glDisableVertexAttribArray(vPositionHandle);
}
}
到這里我們對(duì)繪制三角形的步驟已經(jīng)有一個(gè)基本的了解茧泪,那么如何畫(huà)一個(gè)長(zhǎng)方形呢蜓氨?其實(shí)跟畫(huà)三角形差不多,下一節(jié)我們就學(xué)習(xí)一下如何繪制一個(gè)長(zhǎng)方形队伟,敬請(qǐng)期待穴吹。