之前文章:
《OpenGL從入門到放棄01 》一些基本概念
《OpenGL從入門到放棄02 》GLSurfaceView和Renderer
上一篇文章我們看到三角形形狀跟預期有點不一樣
這是OpenGL坐標系的問題,對坐標系不明白可以看第一節(jié),這一節(jié)我們通過投影和相機視圖來解決這個問題。
先來個概念,之后再上代碼
相機
生活中我們拍照,你站的高度,拿相機的位置,姿勢不同,拍出來的照片也就不一樣
在Android OpenGLES程序中,我們可以通過以下方法來進行相機設置:
Matrix.setLookAtM (float[] rm, //接收相機變換矩陣
int rmOffset, //變換矩陣的起始位置(偏移量)
float eyeX,float eyeY, float eyeZ, //相機位置
float centerX,float centerY,float centerZ, //觀測點位置
float upX,float upY,float upZ) //up向量在xyz上的分量
相機位置:相機的位置是比較好理解的,就是相機在3D空間里面的坐標點,
相機觀察方向:相機的觀察方向,對應center向量,表示的是相機鏡頭的朝向,你可以朝前拍、朝后拍、也可以朝左朝右,或者其他的方向。
相機UP方向:相機的UP方向,可以理解為相機頂端指向的方向。比如你把相機斜著拿著,拍出來的照片就是斜著的,你倒著拿著,拍出來的就是倒著的。
如圖:center的X,Y,Z表示相機的正方向的方向向量,up的X,Y,Z表示相機的上方向的方向向量。因為空間中想要確定一個具體朝向,必須要有兩組向量,所以此函數(shù)需要一個點和兩組向量來確定相機的位置和具體朝向。
投影
使用OpenGl繪制的3D圖形,需要展示在移動端2D設備上,這就需要投影。
OpenGl ES中有兩種投影方式:一種是正交投影,一種是透視投影:
正交投影
投影物體的大小不會隨觀察點的遠近而發(fā)生變化,我們可以使用下面方法來執(zhí)行正交投影
Matrix.orthoM (float[] m, //接收正交投影的變換矩陣
int mOffset, //變換矩陣的起始位置(偏移量)
float left, //相對觀察點近面的左邊距
float right, //相對觀察點近面的右邊距
float bottom, //相對觀察點近面的下邊距
float top, //相對觀察點近面的上邊距
float near, //相對觀察點近面距離
float far) //相對觀察點遠面距離
透視投影
隨觀察點的距離變化而變化,觀察點越遠,視圖越小,反之越大,我們可以通過如下方法來設置透視投影:
Matrix.frustumM (float[] m, //接收透視投影的變換矩陣
int mOffset, //變換矩陣的起始位置(偏移量)
float left, //相對觀察點近面的左邊距
float right, //相對觀察點近面的右邊距
float bottom, //相對觀察點近面的下邊距
float top, //相對觀察點近面的上邊距
float near, //相對觀察點近面距離
float far) //相對觀察點遠面距離
只有繪制在近平面和遠平面之間的內容,才能被用戶看到
轉換矩陣(變換矩陣)
轉換矩陣用來做什么的呢?
是否記得上面我們繪制的圖形坐標需要轉換為OpenGl中能處理的小端字節(jié)序(LittleEdian)
沒錯,轉換矩陣就是用來將數(shù)據(jù)轉為OpenGl ES可用的數(shù)據(jù)字節(jié),我們將相機視圖矩陣和投影矩陣相乘,便得到一個轉換矩陣,然后我們再將此矩陣傳給頂點著色器,具體使用方法及參數(shù)說明如下:
Matrix.multiplyMM (float[] result, //接收相乘結果
int resultOffset, //接收矩陣的起始位置(偏移量)
float[] lhs, //左矩陣
int lhsOffset, //左矩陣的起始位置(偏移量)
float[] rhs, //右矩陣
int rhsOffset) //右矩陣的起始位置(偏移量)
下面簡單講解下如何使用投影和相機視圖來實現(xiàn)矩陣變換并傳遞給頂點著色器
實戰(zhàn)
1、定義一個投影矩陣、相機矩陣、變換矩陣
private final float[] mMVPMatrix = new float[16]; //變換矩陣(投影矩陣*相機矩陣的結果,最終要傳遞給頂點著色器)
private final float[] mProjectionMatrix = new float[16]; //投影矩陣
private final float[] mViewMatrix = new float[16]; //相機位置矩陣
2、onSurfaceChanged 中進行設置
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 設置繪圖的窗口(可以理解成在畫布上劃出一塊區(qū)域來畫圖)
GLES20.glViewport(0,0,width,height);
/**投影和相機視圖相關**/
float ratio = (float) width / height;
//設置正交投影
// Matrix.orthoM (mProjectionMatrix, 0, -ratio, ratio, -1, 2, 3, 7);
Matrix.orthoM (mProjectionMatrix, 0,0,width, height, 0, -width, width);//這個投影會跟屏幕坐標關聯(lián)上
//設置透視投影(觀察點越遠,視圖越小),這個投影矩陣被應用于對象坐標在onDrawFrame()方法中
// Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 2, 3, 7);
//設置相機位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7f, 0f, 0f, 0f, 0f, 1.0f, 1.0f);
//計算變換矩陣,最終mMVPMatrix要傳遞給頂點著色器
Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,mViewMatrix,0);
}
至此就得到了一個變換矩陣 mMVPMatrix,最終需要傳遞給頂點著色器
3、修改三角形代碼
在上一節(jié)代碼的基礎上修改
1、修改頂點著色器
// 頂點著色器的腳本
String vertexShaderCode =
"uniform mat4 uMVPMatrix;" + //接收傳入的轉換矩陣
" attribute vec4 vPosition;" + //接收傳入的頂點
" void main() {" +
" gl_Position = uMVPMatrix * vPosition;" + //矩陣變換計算之后的位置
" }";
2、增加變量
//變換矩陣句柄
private int mMVPMatrixHandle;
//變換矩陣,提供set方法
private float[] mvpMatrix = new float[16];
public void setMvpMatrix(float[] mvpMatrix) {
this.mvpMatrix = mvpMatrix;
}
3、修改draw方法
draw方法主要是將我們的數(shù)據(jù)傳遞給著色器,既然頂點著色器增加了矩陣變量,那么我們需要把變換矩陣傳遞給頂點著色器
// 獲取變換矩陣的句柄
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// 將投影和視圖轉換傳遞給著色器,可以理解為給頂點著色器中uMVPMatrix這個變量賦值為mvpMatrix
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
4、在onDrawFrame中繪制
mGlTriangle02.setMvpMatrix(mMVPMatrix);
mGlTriangle02.draw();
mGlTriangle02在onSurfaceCreated中new出來,然后就可以看到效果了
看起來差不多了,可以看到頂點位置不變的情況下,使用投影跟相機視圖確實能改變2d效果。
最后貼下三角形的全部代碼
public class GLTriangle02{
// 頂點著色器的腳本
String vertexShaderCode =
"uniform mat4 uMVPMatrix;" + //接收傳入的轉換矩陣
" attribute vec4 vPosition;" + //接收傳入的頂點
" void main() {" +
" gl_Position = uMVPMatrix * vPosition;" + //矩陣變換計算之后的位置
" }";
// 片元著色器的腳本
String fragmentShaderCode =
" precision mediump float;" + // 聲明float類型的精度為中等(精度越高越耗資源)
" uniform vec4 vColor;" + // 接收傳入的顏色
" void main() {" +
" gl_FragColor = vColor;" + // 給此片元的填充色
" }";
private FloatBuffer vertexBuffer; //頂點坐標數(shù)據(jù)要轉化成FloatBuffer格式
// 數(shù)組中每3個值作為一個坐標點
static final int COORDS_PER_VERTEX = 3;
//三角形的坐標數(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
};
//頂點個數(shù),計算得出
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
//一個頂點有3個float,一個float是4個字節(jié),所以一個頂點要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,
};
//當前繪制的頂點位置句柄
private int vPositionHandle;
//片元著色器顏色句柄
private int vColorHandle;
//變換矩陣句柄
private int mMVPMatrixHandle;
//這個可以理解為一個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ù)轉換,頂點坐標數(shù)據(jù)float類型轉換成OpenGL格式FloatBuffer,int和short同理*/
vertexBuffer = GLUtil.floatArray2FloatBuffer(triangleCoords);
/** 2、加載編譯頂點著色器和片元著色器*/
int vertexShader = GLUtil.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = GLUtil.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
/** 3、創(chuàng)建空的OpenGL ES程序,并把著色器添加進去*/
mProgram = GLES20.glCreateProgram();
// 添加頂點著色器到程序中
GLES20.glAttachShader(mProgram, vertexShader);
// 添加片段著色器到程序中
GLES20.glAttachShader(mProgram, fragmentShader);
/** 4、鏈接程序*/
GLES20.glLinkProgram(mProgram);
}
public void draw() {
// 將程序添加到OpenGL ES環(huán)境
GLES20.glUseProgram(mProgram);
/***1.獲取句柄*/
// 獲取頂點著色器的位置的句柄(這里可以理解為當前繪制的頂點位置)
vPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 獲取片段著色器的vColor句柄
vColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 獲取變換矩陣的句柄
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
/**2.設置數(shù)據(jù)*/
// 啟用頂點屬性,最后對應禁用
GLES20.glEnableVertexAttribArray(vPositionHandle);
//準備三角形坐標數(shù)據(jù)
GLES20.glVertexAttribPointer(vPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 設置繪制三角形的顏色,給vColor 這個變量賦值
GLES20.glUniform4fv(vColorHandle, 1, mColor, 0);
// 將投影和視圖轉換傳遞給著色器,可以理解為給uMVPMatrix這個變量賦值為mvpMatrix
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
/** 3.繪制三角形,三個頂點*/
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用頂點數(shù)組(好像不禁用也沒啥問題)
GLES20.glDisableVertexAttribArray(vPositionHandle);
}
}
到這里我們對繪制三角形的步驟已經(jīng)有一個基本的了解,那么如何畫一個長方形呢?其實跟畫三角形差不多,下一節(jié)我們就學習一下如何繪制一個長方形,敬請期待。