OpenGL之基礎(chǔ)
OpenGL之繪制簡單形狀
OpenGL之顏色
OpenGL之調(diào)整屏幕寬高比
從著色器到屏幕的坐標(biāo)變換
頂點(diǎn)著色器上的原始 gl_Position 坐標(biāo)變換為最終的屏幕坐標(biāo)流程
剪裁空間
當(dāng)頂點(diǎn)著色器把一個值寫到 gl_Position 的時候班利,這個位置是在剪裁空間(clip space)中的酥艳。在剪裁空間中鸟妙,對于任何給定的位置调限,它的x茂蚓、y以及z分量都需要在那個位置的-w和w之間绣张。比如雅宾,如果一個位置的w是1唆迁,那么其x肉拓、y以及z分量都需要在-1和1之間后频。任何這個范圍外的事物在屏幕上都不可見的
透視除法
OpenGL 使用透視除法將剪裁空間中的位置坐標(biāo)變換為歸一化設(shè)備坐標(biāo),不管渲染區(qū)域的大小和形狀暖途,對于其中的每個可視坐標(biāo)卑惜,其x、y和z分量的取值都位于[-1,1]的范圍內(nèi)
為了在屏幕上創(chuàng)建三維的幻象驻售,OpenGL會把每個 gl_Position 的x露久、y和z分量都除以它的w分量。因此欺栗,當(dāng)w分量用來表示距離的時候毫痕,就使得較遠(yuǎn)處的物體被移動到距離渲染區(qū)域中心更近的地方征峦,這個中心的作用就像一個消失點(diǎn)
為什么不把 z 解釋為距離,而是保留 z 分量作為深度緩沖區(qū)(depth buffer)消请,并增加 w 作為第四個分量栏笆,因為我們可以把投影的影響與實(shí)際的 z 坐標(biāo)解耦,以便我們可以在正交投影和透視投影之間切換
視口變換
OpenGL 把歸一化設(shè)備坐標(biāo)的 x 和 y 分量映射到屏幕上的一個區(qū)域內(nèi)臊泰,這個區(qū)域是操作系統(tǒng)預(yù)留出來用于顯示的蛉加,被稱為視口(viewport),這些變換后的坐標(biāo)被稱為窗口坐標(biāo)( window coordinate)
實(shí)現(xiàn)三維顯示
在頂點(diǎn)數(shù)據(jù)中加入 z 和 w 分量缸逃,近處的點(diǎn) w 值為 1针饥,越遠(yuǎn)的 w 的值大,這樣確實(shí)可以實(shí)現(xiàn)三維的顯示效果察滑,但不能改變桌子的角度或者縮小打厘、放大。要實(shí)現(xiàn)這些變換贺辰,不能使用硬編碼指定 w 的值户盯,而要用矩陣來生成這些值,用透視投影矩陣自動生成w的值
透視投影矩陣
透視投影矩陣需要和透視除法一起發(fā)揮作用饲化。投影矩陣不能自己做透視除法莽鸭,而透視除法需要 w 才能起作用,因此吃靠,透視投影矩陣最重要的任務(wù)就是為 w 產(chǎn)生正確的值
能實(shí)現(xiàn)這些的方法之一是利用 z 分量硫眨,把它作為物體與焦點(diǎn)(觀察點(diǎn))的距離并且把這個距離映射到w,這個距離越大巢块,w值越大礁阁,所得物體越小
示意圖
可以看出,相比與正交投影矩陣族奢,透視投影矩陣是一個更加通用的投影矩陣姥闭,可以同時對寬高比和視野進(jìn)行調(diào)整,因此越走,使用透視投影矩陣棚品,就可以去掉正交投影矩陣了
創(chuàng)建透視投影矩陣
Matrix.perspectiveM()方法可以創(chuàng)建一個透視投影矩陣,其原型如下
public static void perspectiveM(float[] m, int offset,
float fovy, float aspect, float zNear, float zFar) {
參數(shù)說明
參數(shù) | 說明 |
---|---|
m | 最終生成的透視投影矩陣數(shù)組廊敌,長度至少有16個元素铜跑,才能存儲透視投影矩陣 |
offset | 正交投影矩陣起始的偏移值 |
fovy | y 方向的視野,以度為單位 |
aspect | 屏幕的寬高比骡澈,它等于寬度/高度 |
zNear | 到近處平面的距離锅纺,必須是正值。比如肋殴,如果此值被設(shè)為1伞广,那近處平面就位于一個z值為-1處 |
zFar | 到遠(yuǎn)處平面的距離拣帽,必須是正值且大于到近處平面的距離 |
也可以自己實(shí)現(xiàn)透視矩陣,透視矩陣最終的數(shù)值如下
a:如果我們想象一個相機(jī)拍攝的場景嚼锄,這個變量就代表那個相機(jī)的焦距减拭。焦距是由1/tan(視野/2) 計算得到的。這個視野必須小于180度区丑。比如拧粪,一個90度的視野,它的焦距會被設(shè)置為1/tan(90° /2)沧侥,也就是1/1或者1
其他的和Matrix.perspectiveM()方法參數(shù)含義一致
自己實(shí)現(xiàn)透視矩陣代碼
public class MatrixHelper {
/**
* @param matrix 矩陣
* @param yFovInDegrees 視野角度
* @param aspect 寬高比
* @param n 近處到平面距離
* @param f 遠(yuǎn)處到平面距離
* @return
*/
public static float[] perspectiveM(float[] matrix, float yFovInDegrees, float aspect, float n, float f) {
// 計算焦距
float angleInRadians = (float) (yFovInDegrees * Math.PI / 180.0);
float a = (float) (1f / tan(angleInRadians / 2));
// 輸出矩陣
for (int i = 0; i < matrix.length; i++) {
switch (i) {
case 0:
matrix[i] = a / aspect;
break;
case 5:
matrix[i] = a;
break;
case 10:
matrix[i] = -((f + n) / (f - n));
break;
case 11:
matrix[i] = -1f;
break;
case 14:
matrix[i] = -((2 * f * n) / (f - n));
break;
default:
matrix[i] = 0f;
break;
}
}
return matrix;
}
}
使用透視矩陣實(shí)現(xiàn)三維效果
// 用45度的視野創(chuàng)建一個透視投影可霎。這個視椎體從z值為-1的位置開始,在z值為-10的位置結(jié)束
Matrix.perspectiveM(projectionMatrix, 0, 45, (float) width / height, 1, 10);
使用上面的代碼替換之前的正交投影矩陣宴杀,運(yùn)行程序癣朗,會發(fā)現(xiàn)圖像不見了,因為沒有給頂點(diǎn)指定 z 的位置旺罢,默認(rèn)情況下它 z 在 0 的位置旷余。而這個視椎體是從z值為-1的位置開始的,除非把它移到那個距離內(nèi)扁达,否則我們無法看見圖像
無法創(chuàng)建包含 z 位置為 0 的四椎體正卧,因為 Matrix.perspectiveM 方法要求 zNear 和 zFar 都必須是正數(shù)
因此,在使用投影矩陣進(jìn)行投影之前跪解,需要使用一個平移矩陣把頂點(diǎn)移到可見的位置炉旷。通常把這個矩陣稱為模型矩陣(model matrix)
模型矩陣
把模型矩陣設(shè)為單位矩陣,再沿著z軸平移 -2叉讥,當(dāng)我們把頂點(diǎn)的坐標(biāo)與這個矩陣相乘的時候窘行,那些坐標(biāo)最終會沿著z軸負(fù)方向移動2個單位,同時图仓,如果只是平移的話抽高,看不到三維效果,因為每個頂點(diǎn)都平移了透绩,因此還需要旋轉(zhuǎn)一下
Matrix.setIdentityM(modelMatrix, 0);
// 沿著z軸平移 -2
Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
// 沿著 x 軸旋轉(zhuǎn)45度
Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);
可以把模型矩陣與投影矩陣相乘,得到一個最終矩陣壁熄,然后把這個矩陣傳遞給頂點(diǎn)著色器帚豪,這種方式可以在著色器中僅保留一個矩陣
// 設(shè)為單位矩陣
Matrix.setIdentityM(modelMatrix, 0);
// 沿著z軸平移 -2
Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
// 沿著 x 軸旋轉(zhuǎn)45度
Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);
// 將視圖投影矩陣和模型矩陣相乘
float[] temp = new float[16];
Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
不論什么時候把兩個矩陣相乘,都需要一個臨時變量來存儲其結(jié)果草丧。如果嘗試直接寫入這個結(jié)果狸臣,這個結(jié)果將是未定義的
修改渲染器
修改渲染器中的代碼,去掉正交投影矩陣昌执,加上模型矩陣和視圖投影矩陣的使用
public class MyRenderer implements GLSurfaceView.Renderer {
private final FloatBuffer mVertexBuffer;
private Context mContext;
private int a_color;
private int a_position;
private int u_matrix;
private float[] projectionMatrix = new float[16];
private float[] modelMatrix = new float[16];
public MyRenderer(Context context) {
mContext = context;
float[] vertex = new float[]{
0f, 0f, 1f, 1f, 1f,
-0.5f, -0.8f, 0f, 0f, 0f,
0.5f, -0.8f, 0f, 0f, 0f,
0.5f, 0.8f, 0f, 0f, 0f,
-0.5f, 0.8f, 0f, 0f, 0f,
-0.5f, -0.8f, 0f, 0f, 0f,
-0.5f, 0f, 1f, 0f, 0f,
0.5f, 0f, 1f, 0f, 0f,
0f, -0.5f, 0f, 1f, 0f,
0f, 0.5f, 0f, 0f, 1f
};
mVertexBuffer = ByteBuffer.allocateDirect(vertex.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertex);
mVertexBuffer.position(0);
}
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
glClearColor(1f, 1f, 1f, 1f);
int vertexShader = ShaderHelper.compileVertexShader(mContext, R.raw.ratio3_vertex);
int fragmentShader = ShaderHelper.compileFragmentShader(mContext, R.raw.ratio3_fragment);
int program = ProgramHelper.getProgram(vertexShader, fragmentShader);
glUseProgram(program);
a_color = glGetAttribLocation(program, "a_Color");
a_position = glGetAttribLocation(program, "a_Position");
u_matrix = glGetUniformLocation(program, "u_Matrix");
mVertexBuffer.position(0);
glVertexAttribPointer(a_position, POSITION_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, mVertexBuffer);
glEnableVertexAttribArray(a_position);
mVertexBuffer.position(POSITION_COMPONENT_COUNT);
glVertexAttribPointer(a_color, COLOR_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, mVertexBuffer);
glEnableVertexAttribArray(a_color);
}
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
glViewport(0, 0, width, height);
// 用45度的視野創(chuàng)建一個透視投影烛亦。這個視椎體從z值為-1的位置開始诈泼,在z值為-10的位置結(jié)束
Matrix.perspectiveM(projectionMatrix, 0, 45, (float) width / height, 1, 10);
// 設(shè)為單位矩陣
Matrix.setIdentityM(modelMatrix, 0);
// 沿著z軸平移 -2
Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
// 沿著 x 軸旋轉(zhuǎn)45度
Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);
// 將視圖投影矩陣和模型矩陣相乘
float[] temp = new float[16];
Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
}
@Override
public void onDrawFrame(GL10 gl10) {
glClear(GL_COLOR_BUFFER_BIT);
glUniformMatrix4fv(u_matrix, 1, false, projectionMatrix, 0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
glDrawArrays(GL_LINES, 6, 2);
glDrawArrays(GL_POINTS, 8, 1);
glDrawArrays(GL_POINTS, 9, 1);
}
}