從零開始之OpenGL ES 2.0【1】
這一節主要內容是矩陣的使用,投影和相機相關知識。
一、矩陣相關
由於OpenGL ES使用的是列向量,所以向量與矩陣計算方式為 矩陣乘向量;在DirectX中使用的是行向量計算方式為 向量乘矩陣。
二、投影相關
在OpenGL ES中投影方式有兩種,分別為 正交投影和透視投影兩種。
1、正交投影
Matrix.orthoM( orthoM, //儲存生成矩陣元素的float[]型別陣列 0, //填充起始偏移量 left, right, //near面的left,right bottom, top, //near面的bottom,top near, far //near面,far面與視點的距離 );
與現實觀察物體不一樣。
可以把正交投影看成一個透明的長方體,叫做視景體。
視景體分為 上平面【up】、下平面【bottom】、左平面【left】、右平面【right】、近平面【near】、遠平面【far】
視景體(也就是透明的長方體盒子)內物體會平行的投影在近平面,超出視景體部分物體會被剪裁掉。並且不會因為物體位置而發生和近大遠小的效果。遠近一樣大。
近平面可以理解為離你最近的那個長方體的一個面。當然前提是你和相機是在一個位置。
2、透視投影
Matrix.frustumM( frustumM, //儲存生成矩陣元素的float[]型別陣列 0, //填充起始偏移量 left, right, //near面的left,right bottom, top, //near面的bottom,top near, far //near面,far面與視點的距離 );
與現實世界觀察物體一樣,會產生近大遠小的效果。
視景體類似於圓錐體,越近越大,越遠越小。
三、相機
setLookAtM(
float[] rm, //儲存生成矩陣元素的float[]型別陣列
int rmOffset,//填充起始偏移量
float eyeX,
float eyeY,
float eyeZ, //攝像機位置X,Y,Z座標
float centerX,
float centerY,
float centerZ, //觀察目標X,Y,Z座標
float upX,
float upY,
float upZ) //up向量在X,Y,Z上的分量
相機是觀察物體的視點,相當於人的眼睛的位置。
下面改裝一下我們上一節程式碼,看看投影效果。
修改頂點著色器程式碼如下
//頂點著色器程式碼
private final String vertex = "" +
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"void main(){" +
"gl_Position=vMatrix*vPosition;"+
"}";
這裡增加了一個矩陣vMatrix定義為 uniform mat4 vMatrix;
並與頂點座標向量相乘,我們前面說過,在OpenGL ES中使用的是列向量,所以向量和矩陣相乘時,矩陣在前向量在後。
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//計算螢幕寬高比
float ratio = (float)width/height;
//儲存投影矩陣
float[] mPMatrix = new float[16];
//儲存相機位置矩陣
float[] mVMatrix = new float[16];
//最終得到的矩陣
mMVPMatrix = new float[16];
//透視矩陣
//儲存生成矩陣元素的float[]型別陣列
//填充起始偏移量
//near面的left,right,bottom,top
//near面,far面與視點的距離
Matrix.frustumM(mPMatrix, 0, -ratio, ratio, -1,1,3, 6);
//儲存生成矩陣元素的float[]型別陣列
//填充起始偏移量
//攝像機位置X,Y,Z座標
//觀察目標X,Y,Z座標
//up向量在X,Y,Z上的分量,也就是相機上方朝向,upY=1朝向手機上方,upX=1朝向手機右側,upZ=1朝向與手機螢幕垂直
Matrix.setLookAtM(mVMatrix, 0, 0,0,6, 0,0,0,0,1,0);
//以上兩個方法只能得到矩陣並不能使其生效
//下面通過矩陣計算得到最終想要的矩陣
//存放結果的總變換矩陣
//結果矩陣偏移量
//左矩陣
//左矩陣偏移量
//右矩陣
//右矩陣偏移量
Matrix.multiplyMM(mMVPMatrix, 0,mPMatrix, 0, mVMatrix, 0);
//當大小改變時重置視區大小
GLES20.glViewport(0,0, width, height);
}
然後就是透視矩陣和相機矩陣的設定,最後通過Matrix.multiplyMM();方法將兩個矩陣相乘,得到最終需要的矩陣。mMVPMatrix 是個成員變數,方便下面使用。
@Override
public void onDrawFrame(GL10 gl) {
//清空緩衝區,與 GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);對應
GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);
//使用OpenGL程式
GLES20.glUseProgram(program);
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
GLES20.glUniformMatrix4fv(vMatrix, 1 ,false, mMVPMatrix, 0);
……………………
}
在onDrawFrame方法增加兩行程式碼
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
GLES20.glUniformMatrix4fv(vMatrix, 1 ,false, mMVPMatrix, 0);
執行一下看下效果
發現得到了一個等腰直角三角形。
紅色的三角形有點難看,讓我們為他增加更多的顏色。
同樣是修改頂點著色器程式碼
如果想要檢視正交投影效果只需要將frustumM修改成orthoM即可
//頂點著色器程式碼
private final String vertex = "" +
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"varying vec4 vColor;" +
"attribute vec4 aColor;" +
"void main(){" +
"gl_Position=vMatrix*vPosition;"+
"vColor=aColor;" +
"}";
在程式碼中增加
"varying vec4 vColor;" +
"attribute vec4 aColor;" +
和
"vColor=aColor;" +
varying是頂點著色器與片元著色器傳遞資料用的,一般頂點著色器修改varying的值,片元著色器使用varying的值。
現在頂點著色器已經提供了傳遞的值,那麼我們需要在片元著色器內使用所以簡單修改一下片元著色器程式碼。
//片元著色器程式碼
private final String fragment = "" +
"precision mediump float;" +
"varying vec4 vColor;" +
"void main(){" +
"gl_FragColor=vColor;"+
"}";
有沒有發現改動呢,其實這裡只改了一個位置那就是將之前的uniform vec4 vColor;改成了varying vec4 vColor;
現在著色器程式碼修改完成,下一步就是顏色值資料。將原來的一組顏色值修改為三組,分別是紅綠藍
//顏色值
private final float[] colors = {
1.0f,0.0f,0.0f,1.0f,
0.0f,1.0f,0.0f,1.0f,
0.0f,0.0f,1.0f,1.0f
};
在
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//將背景設定為灰色,這裡只是設定,並沒有立即生效
GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);
//建立一個定點座標Buffer,一個float為4位元組所以這裡需要
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(pos.length*4);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer .put(pos);
vertexBuffer .position(0);
ByteBuffer cbyteBuffer = ByteBuffer.allocateDirect(colors.length*4);
cbyteBuffer.order(ByteOrder.nativeOrder());
coordBuffer = cbyteBuffer.asFloatBuffer();
coordBuffer .put(colors);
coordBuffer .position(0);
……………………
}
增加新的ByteBuffer儲存顏色值資訊
在
@Override
public void onDrawFrame(GL10 gl) {
//清空緩衝區,與 GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);對應
GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);
//使用OpenGL程式
GLES20.glUseProgram(program);
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
GLES20.glUniformMatrix4fv(vMatrix, 1 ,false, mMVPMatrix, 0);
//獲取頂點著色器變數vPosition
int vPositionHandler = GLES20.glGetAttribLocation(program, "vPosition");
//允許使用頂點座標陣列
GLES20.glEnableVertexAttribArray(vPositionHandler);
//第一個引數頂點屬性的索引值
// 第二個引數頂點屬性的元件數量。必須為1、2、3或者4,如position是由3個(x,y,z)組成,而顏色是4個(r,g,b,a))
// 第三個引數陣列中每個元件的資料型別
// 第四個引數指定當被訪問時,固定點資料值是否應該被歸一化(GL_TRUE)或者直接轉換為固定點值(GL_FALSE)
// 第五個引數指定連續頂點屬性之間的偏移量,這裡由於是三個點 每個點4位元組(float) 所以就是 3*4
// 第六個引數前面的頂點座標陣列
GLES20.glVertexAttribPointer(vPositionHandler, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);
int aColorHandler = GLES20.glGetAttribLocation(program, "aColor");
GLES20.glEnableVertexAttribArray(aColorHandler);
GLES20.glVertexAttribPointer(aColorHandler, 4, GLES20.GL_FLOAT, false, 0, coordBuffer);
//三角形繪製方式
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);
//禁止使用頂點座標陣列
GLES20.glDisableVertexAttribArray(vPositionHandler);
}
增加
int aColorHandler = GLES20.glGetAttribLocation(program, "aColor");
GLES20.glEnableVertexAttribArray(aColorHandler);
GLES20.glVertexAttribPointer(aColorHandler, 4, GLES20.GL_FLOAT, false, 0, coordBuffer);
下面看一下效果。
下面是完整程式碼
package pers.wtt.opengles10.render;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* Created by WT on 2018/4/8.
*/
public class DGLRender implements GLSurfaceView.Renderer {
//頂點著色器程式碼
private final String vertex = "" +
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"varying vec4 vColor;" +
"attribute vec4 aColor;" +
"void main(){" +
"gl_Position=vMatrix*vPosition;"+
"vColor=aColor;" +
"}";
//片元著色器程式碼
private final String fragment = "" +
"precision mediump float;" +
"varying vec4 vColor;" +
"void main(){" +
"gl_FragColor=vColor;"+
"}";
//頂點座標
private final float[] pos = {
-0.5f,-0.5f,0.0f,
0.5f,0.5f,0.0f,
0.5f,-0.5f,0.0f,
};
//顏色值
private final float[] colors = {
1.0f,0.0f,0.0f,1.0f,
0.0f,1.0f,0.0f,1.0f,
0.0f,0.0f,1.0f,1.0f
};
//GL程式
int program;
//定點座標Buffer
FloatBuffer vertexBuffer;
FloatBuffer coordBuffer;
float[] mMVPMatrix;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//將背景設定為灰色,這裡只是設定,並沒有立即生效
GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);
//建立一個定點座標Buffer,一個float為4位元組所以這裡需要
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(pos.length*4);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer .put(pos);
vertexBuffer .position(0);
ByteBuffer cbyteBuffer = ByteBuffer.allocateDirect(colors.length*4);
cbyteBuffer.order(ByteOrder.nativeOrder());
coordBuffer = cbyteBuffer.asFloatBuffer();
coordBuffer .put(colors);
coordBuffer .position(0);
//裝載頂點著色器和片元著色器,從source
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertex);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragment);
//建立Opengl程式,獲取程式控制代碼,為了方便onDrawFrame方法使用所以宣告為成員變數
program = GLES20.glCreateProgram();
//啟用著色器
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
//連結程式
GLES20.glLinkProgram(program);
}
/**
* 裝載著色器從資原始碼,需要檢測是否生成成功,暫時不檢測
* @param type 著色器型別
* @param source 著色器程式碼源
* @return 返回著色器控制代碼
*/
private int loadShader(int type, String source) {
int shader = 0;
shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
return shader;
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//計算螢幕寬高比
float ratio = (float)width/height;
//儲存投影矩陣
float[] mPMatrix = new float[16];
//儲存相機位置矩陣
float[] mVMatrix = new float[16];
//最終得到的矩陣
mMVPMatrix = new float[16];
//透視矩陣
//儲存生成矩陣元素的float[]型別陣列
//填充起始偏移量
//near面的left,right,bottom,top
//near面,far面與視點的距離
Matrix.frustumM(mPMatrix, 0, -ratio, ratio, -1,1,3, 6);
//儲存生成矩陣元素的float[]型別陣列
//填充起始偏移量
//攝像機位置X,Y,Z座標
//觀察目標X,Y,Z座標
//up向量在X,Y,Z上的分量,也就是相機上方朝向,upY=1朝向手機上方,upX=1朝向手機右側,upZ=1朝向與手機螢幕垂直
Matrix.setLookAtM(mVMatrix, 0, 0,0,6, 0,0,0,0,1,0);
//以上兩個方法只能得到矩陣並不能使其生效
//下面通過矩陣計算得到最終想要的矩陣
//存放結果的總變換矩陣
//結果矩陣偏移量
//左矩陣
//左矩陣偏移量
//右矩陣
//右矩陣偏移量
Matrix.multiplyMM(mMVPMatrix, 0,mPMatrix, 0, mVMatrix, 0);
//當大小改變時重置視區大小
GLES20.glViewport(0,0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
//清空緩衝區,與 GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);對應
GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);
//使用OpenGL程式
GLES20.glUseProgram(program);
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
GLES20.glUniformMatrix4fv(vMatrix, 1 ,false, mMVPMatrix, 0);
//獲取頂點著色器變數vPosition
int vPositionHandler = GLES20.glGetAttribLocation(program, "vPosition");
//允許使用頂點座標陣列
GLES20.glEnableVertexAttribArray(vPositionHandler);
//第一個引數頂點屬性的索引值
// 第二個引數頂點屬性的元件數量。必須為1、2、3或者4,如position是由3個(x,y,z)組成,而顏色是4個(r,g,b,a))
// 第三個引數陣列中每個元件的資料型別
// 第四個引數指定當被訪問時,固定點資料值是否應該被歸一化(GL_TRUE)或者直接轉換為固定點值(GL_FALSE)
// 第五個引數指定連續頂點屬性之間的偏移量,這裡由於是三個點 每個點4位元組(float) 所以就是 3*4
// 第六個引數前面的頂點座標陣列
GLES20.glVertexAttribPointer(vPositionHandler, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);
int aColorHandler = GLES20.glGetAttribLocation(program, "aColor");
GLES20.glEnableVertexAttribArray(aColorHandler);
GLES20.glVertexAttribPointer(aColorHandler, 4, GLES20.GL_FLOAT, false, 0, coordBuffer);
//獲取片元著色器變數vColor
//int vColor = GLES20.glGetUniformLocation(program, "vColor");
//GLES20.glUniform4fv(vColor, 1, colors, 0);
//三角形繪製方式
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);
//禁止使用頂點座標陣列
GLES20.glDisableVertexAttribArray(vPositionHandler);
}
}