前言
之前我們的所有圖形效果,都是變形的,比如我們?cè)纠L制的是長(zhǎng)寬比是1:1的,結(jié)果在手機(jī)屏幕上的效果展示卻是長(zhǎng)方形。那么,本節(jié)課我們通過正交投影來解決這個(gè)問題。
本節(jié)課主要講解如何去編寫相關(guān)代碼來解決問題,而具體的原理、概念、GL坐標(biāo)體系變換等暫不做深入說明,會(huì)在之后的課程在講解。
歸一化設(shè)備坐標(biāo)
在OpenGL中,我們要渲染的所有物體都要映射到x軸、y軸、z軸上的[-1, 1]范圍內(nèi),這個(gè)范圍內(nèi)的坐標(biāo)被稱為歸一化設(shè)備坐標(biāo),其獨(dú)立于屏幕的實(shí)際尺寸或者形狀。歸一化設(shè)備坐標(biāo)假定的坐標(biāo)空間是一個(gè)正方形。如下圖

但是我們手機(jī)設(shè)備一般都不是正方形的,而是長(zhǎng)方形的。所以導(dǎo)致x和y兩個(gè)方向上,同樣的比例值,但是視覺上所占的長(zhǎng)度卻是不一樣的。如下圖,繪制一個(gè)半徑占0.5的圓時(shí),效果卻是一個(gè)橢圓。

解決這個(gè)問題,一般我們的解決方案步驟如下:
- 在設(shè)置物體的坐標(biāo)、尺寸時(shí),將短邊視為標(biāo)準(zhǔn)邊,取值范圍是[-1,1],而較長(zhǎng)邊的取值范圍則是[-N,N],其中N≥1,N是長(zhǎng)邊/短邊的比例系數(shù)。
- 頂點(diǎn)著色器設(shè)置頂點(diǎn)參數(shù)的時(shí)候,將長(zhǎng)邊上的值從[-N,N]換算為[-1,1]的范圍內(nèi)。
步驟如下圖:


代碼實(shí)現(xiàn)
針對(duì)上面的解決步驟,步驟1只需要我們?cè)谠O(shè)置頂點(diǎn)的時(shí)候按照這個(gè)標(biāo)準(zhǔn)即可。而步驟2則是本課程的關(guān)鍵。
要對(duì)坐標(biāo)向量進(jìn)行換算,可以使用矩陣來解決問題。
在三維圖形學(xué)中,一般使用的是4階矩陣。OpenGL中使用的是列向量,如[xyzw]T,所以與矩陣相乘時(shí),矩陣在前,向量在后。
知道了原理之后,我們代碼實(shí)現(xiàn)上需要解決以下幾個(gè)問題:
- 如何獲得一個(gè)矩陣,可以把坐標(biāo)范圍從[-N,N]換算為[-1,1]的范圍內(nèi)
- 如何將矩陣傳遞到GLSL中
- 對(duì)于問題1,Android提供了Matrix.orthoM這個(gè)方法來處理矩陣。
- 對(duì)于問題2,與獲取頂點(diǎn)索引類似,可以再GLSL中聲明一個(gè)mat4類型的矩陣變量,獲取其索引,再傳遞值給她
具體代碼實(shí)現(xiàn)如下:
private static final String VERTEX_SHADER = "" +
// mat4:4×4的矩陣
"uniform mat4 u_Matrix;\n" +
"attribute vec4 a_Position;\n" +
"void main()\n" +
"{\n" +
// 矩陣與向量相乘得到最終的位置
" gl_Position = u_Matrix * a_Position;\n" +
"}";
private int uMatrixLocation;
/**
* 矩陣數(shù)組
*/
private final float[] mProjectionMatrix = new float[]{
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
};
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
// 省略部分代碼
uMatrixLocation = getUniform("u_Matrix");
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
// 邊長(zhǎng)比(>=1),非寬高比
float aspectRatio = width > height ?
(float) width / (float) height :
(float) height / (float) width;
// 1. 矩陣數(shù)組
// 2. 結(jié)果矩陣起始的偏移量
// 3. left:x的最小值
// 4. right:x的最大值
// 5. bottom:y的最小值
// 6. top:y的最大值
// 7. near:z的最小值
// 8. far:z的最大值
if (width > height) {
// 橫屏
Matrix.orthoM(mProjectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);
} else {
// 豎屏or正方形
Matrix.orthoM(mProjectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
}
// 更新u_Matrix的值,即更新矩陣數(shù)組
GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, mProjectionMatrix, 0);
}
參考
見Android OpenGL ES學(xué)習(xí)資料所列舉的博客、資料。
GitHub代碼工程
本系列課程所有相關(guān)代碼請(qǐng)參考我的GitHub項(xiàng)目GLStudio。
課程目錄
本系列課程目錄詳見 簡(jiǎn)書 - Android OpenGL ES教程規(guī)劃