Android openGl 繪制簡(jiǎn)單圖形的實(shí)現(xiàn)示例
學(xué)習(xí)五部曲,弄清楚5個(gè)W一個(gè)H(when(什么時(shí)候使用)、where(在哪個(gè)地方使用?)、who(對(duì)誰使用)、what(是個(gè)什么東西)、why(為什么要這么用?).一個(gè)H即:how(到底該怎么用?)),基本的概念篇主要圍繞這幾個(gè)方面進(jìn)行分析
1. What? openGl是什么?openGl ES又是什么?
相信很多人從事開發(fā)的都或多或少聽到過有關(guān)OpenGl這個(gè)東西,但是平時(shí)用的少,只知道有這么個(gè)東西,而且學(xué)起來不簡(jiǎn)單,所以大多數(shù)人都不能講出個(gè)個(gè)所以然來。
官方對(duì)OpenGl的描述為:
OpenGL(Open Graphics Library開發(fā)圖形接口)是一個(gè)跨平臺(tái)的圖形API,用于指定3D圖形處理硬件中的標(biāo)準(zhǔn)軟件接口。
OpenGl的前身是SGI公司為其圖形工作站開發(fā)的IRIS GL,后來因?yàn)镮RIS GL的移植性不好,所以在其基礎(chǔ)上,開發(fā)出了OpenGl。OpenGl一般用于在圖形工作站,PC端使用,由于性能各方面原因,在移動(dòng)端使用OpenGl基本帶不動(dòng)。為此,Khronos公司就為OpenGl提供了一個(gè)子集,OpenGl ES(OpenGl for Embedded System)
什么是OpenGl ES呢?
OpenGl ES是免費(fèi)的跨平臺(tái)的功能完善的2D/3D圖形庫接口的API,是OpenGL的一個(gè)子集。
移動(dòng)端使用到的基本上都是OpenGl ES,當(dāng)然Android開發(fā)下還專門為OpenGl提供了android.opengl包,并且提供了GlSurfaceView,GLU,GlUtils等工具類。
2. How? Android中的openGL 如何使用?
在了解OpenGl的使用之前,我們需要了解兩個(gè)基本類別的Android框架:GlSurfaceView和GlSurfaceView.Renderer
3. GlSurfaceView是什么? GLSurfaceView的作用是什么? GLSurfaceView如何使用?
GlSurfaceView從名字就可以看出,它是一個(gè)SurfaceView,看源碼可知,GlSurfaceView繼承自SurfaceView。并增加了Renderer.它的作用就是專門為OpenGl顯示渲染使用的。
GLSurfaceView的使用方法:
可以通過創(chuàng)建的實(shí)例使用這個(gè)類,并增加你的Renderer.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GLSurfaceView glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
@Override
public void onDrawFrame(GL10 gl) {
}
});
setContentView(glSurfaceView);
}
4. GlSurfaceView.Renderer是什么?GLSurfaceView.Renderer的作用?GLSurfaceView.Renderer的用法?
該接口定義了用于繪制在圖形所需的方法GLSurfaceView。你必須提供這個(gè)接口作為一個(gè)單獨(dú)的類的實(shí)現(xiàn),并將其連接到您的GLSurfaceView使用實(shí)例 GLSurfaceView.setRenderer()。如上面的代碼所示。作用就是提供各種渲染方法,OpenGl的渲染操作均在此接口中實(shí)習(xí)。下面說下實(shí)現(xiàn)該接口的方法含義:
- onSurfaceCreated():系統(tǒng)調(diào)用這個(gè)方法一次創(chuàng)建時(shí)GLSurfaceView。使用此方法來執(zhí)行只需要發(fā)生一次的操作,比如設(shè)置OpenGL的環(huán)境參數(shù)或初始化的OpenGL圖形對(duì)象。
- onDrawFrame():系統(tǒng)調(diào)用上的每個(gè)重繪此方法GLSurfaceView。使用此方法作為主要執(zhí)行點(diǎn)用于繪制(和重新繪制)的圖形對(duì)象。
- 系統(tǒng)調(diào)用此方法時(shí)的GLSurfaceView幾何形狀的變化,包括尺寸變化GLSurfaceView或設(shè)備屏幕的取向。例如,當(dāng)設(shè)備從縱向變?yōu)闄M向的系統(tǒng)調(diào)用這個(gè)方法。使用此方法可以在變化做出反應(yīng)GLSurfaceView容器。
介紹完了GlSurfaceView和GlSurfaceView.renderer之后,接下來說下如何使用GlSurfaceView;
1. 創(chuàng)建一個(gè)GlSurfaceView
2. 為這個(gè)GlSurfaceView設(shè)置渲染
3. 在GlSurfaceView.renderer中繪制處理顯示數(shù)據(jù)
5. OpenGl的簡(jiǎn)單使用實(shí)例(繪制一個(gè)三角形)
在使用OpenGl之前,需要在AndroidManifest.xml中設(shè)置OpenGl的版本:這里我們使用的是OpenGl ES 2.0,所以需要添加如下說明:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
使用GLSufaceView(上面有介紹)
- 具體在GlSurfaceView.Renderer中的繪制步驟:
- 設(shè)置視圖展示窗口(viewport) :在onSurfaceChanged中調(diào)用GLES20.glViewport(0, 0, width, height);
- 創(chuàng)建圖形類,確定好頂點(diǎn)位置和圖形顏色,將頂點(diǎn)和顏色數(shù)據(jù)轉(zhuǎn)換為OpenGl使用的數(shù)據(jù)格式
- 加載頂點(diǎn)找色器和片段著色器用來修改圖形的顏色,紋理,坐標(biāo)等屬性
- 創(chuàng)建投影和相機(jī)視圖來顯示視圖的顯示狀態(tài),并將投影和相機(jī)視圖的轉(zhuǎn)換傳遞給著色器。
- 創(chuàng)建項(xiàng)目(Program),連接頂點(diǎn)著色器片段著色器。
- 將坐標(biāo)數(shù)據(jù)傳入到OpenGl ES程序中:
使用OpenGl修改背景顏色
創(chuàng)建一個(gè)GlSurfaceView,并為其設(shè)置渲染OneGlRenderer;
public class OneGlSurfaceView extends GLSurfaceView {
private final OneGlRenderer mRenderer;
public OneGlSurfaceView(Context context) {
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
mRenderer = new OneGlRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
}
實(shí)現(xiàn)渲染接口
public class OneGlRenderer implements GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
public void onDrawFrame(GL10 unused) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
}
展示渲染后的GlSurfaceView
public class OneOpenGlActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OneGlSurfaceView glSurfaceView = new OneGlSurfaceView(this);
setContentView(glSurfaceView);
}
}
效果如下:就是簡(jiǎn)單給GlSurfaceView渲染一層黑色。

使用OpenGl繪制幾何圖形
一:圖形創(chuàng)建
創(chuàng)建一個(gè)幾何圖形(這里主要列舉三角形和正方形),需要注意一點(diǎn),我們?cè)O(shè)置圖形的頂點(diǎn)坐標(biāo)后,需要將頂點(diǎn)坐標(biāo)轉(zhuǎn)為ByteBuffer,這樣OpenGl才能進(jìn)行圖形處理。
三角形圖形創(chuàng)建:
public class Triangle {
private FloatBuffer vertexBuffer;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
static float triangleCoords[] = { // in counterclockwise order:
0.0f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
// Set color with red, green, blue and alpha (opacity) values
float color[] = { 255, 0, 0, 1.0f };
public Triangle() {
// 初始化ByteBuffer,長(zhǎng)度為arr數(shù)組的長(zhǎng)度*4,因?yàn)橐粋€(gè)float占4個(gè)字節(jié)
ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * 4);
// 數(shù)組排列用nativeOrder
bb.order(ByteOrder.nativeOrder());
// 從ByteBuffer創(chuàng)建一個(gè)浮點(diǎn)緩沖區(qū)
vertexBuffer = bb.asFloatBuffer();
// 將坐標(biāo)添加到FloatBuffer
vertexBuffer.put(triangleCoords);
// 設(shè)置緩沖區(qū)來讀取第一個(gè)坐標(biāo)
vertexBuffer.position(0);
}
}
正方型圖:
public class Square {
private FloatBuffer vertexBuffer;
private ShortBuffer drawListBuffer;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
static float squareCoords[] = {
-0.5f, 0.5f, 0.0f, // top left
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, // bottom right
0.5f, 0.5f, 0.0f }; // top right
private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices
public Square() {
// 初始化ByteBuffer,長(zhǎng)度為arr數(shù)組的長(zhǎng)度*4,因?yàn)橐粋€(gè)float占4個(gè)字節(jié)
ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
// 初始化ByteBuffer,長(zhǎng)度為arr數(shù)組的長(zhǎng)度*2,因?yàn)橐粋€(gè)short占2個(gè)字節(jié)
ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
}
}
創(chuàng)建圖形基本沒什么技巧可言,按部就班就行了,為什么數(shù)據(jù)需要轉(zhuǎn)換格式呢?主要是因?yàn)镴ava的緩沖區(qū)數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)為大端字節(jié)序(BigEdian),而OpenGl的數(shù)據(jù)為小端字節(jié)序(LittleEdian),因?yàn)閿?shù)據(jù)存儲(chǔ)結(jié)構(gòu)的差異,所以,在Android中使用OpenGl的時(shí)候必須要進(jìn)行下轉(zhuǎn)換。當(dāng)然,一般我們?cè)谑褂玫臅r(shí)候都會(huì)做個(gè)簡(jiǎn)單的工具類。這里提供幾個(gè)簡(jiǎn)單的封裝。(占幾個(gè)字節(jié)就初始化ByteBuffer長(zhǎng)度的時(shí)候*幾)
將int[]轉(zhuǎn)成IntBuffer
private IntBuffer intBufferUtil(int[] arr)
{
IntBuffer mBuffer;
// 初始化ByteBuffer,長(zhǎng)度為arr數(shù)組的長(zhǎng)度*4,因?yàn)橐粋€(gè)int占4個(gè)字節(jié)
ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
// 數(shù)組排列用nativeOrder
qbb.order(ByteOrder.nativeOrder());
mBuffer = qbb.asIntBuffer();
mBuffer.put(arr);
mBuffer.position(0);
return mBuffer;
}
將float[]數(shù)組轉(zhuǎn)為OpenGl 所需要的FloatBuffer
private FloatBuffer floatBufferUtil(float[] arr)
{
FloatBuffer mBuffer;
// 初始化ByteBuffer,長(zhǎng)度為arr數(shù)組的長(zhǎng)度*4,因?yàn)橐粋€(gè)int占4個(gè)字節(jié)
ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
// 數(shù)組排列用nativeOrder
qbb.order(ByteOrder.nativeOrder());
mBuffer = qbb.asFloatBuffer();
mBuffer.put(arr);
mBuffer.position(0);
return mBuffer;
}
當(dāng)然,依葫蘆畫瓢,如何將short[]轉(zhuǎn)ShortBuffer這個(gè)就照著寫就ok了
private ShortBuffer shortBufferUtil(short[] arr){
ShortBuffer mBuffer;
// 初始化ByteBuffer,長(zhǎng)度為arr數(shù)組的長(zhǎng)度*2,因?yàn)橐粋€(gè)short占2個(gè)字節(jié)
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (# of coordinate values * 2 bytes per short)
arr.length * 2);
dlb.order(ByteOrder.nativeOrder());
mBuffer = dlb.asShortBuffer();
mBuffer.put(arr);
mBuffer.position(0);
return mBuffer;
}
創(chuàng)建完形狀之后,我們就要進(jìn)行我們的第二步了,將這些形狀渲染到GlSurfaceView中去。主要可分為下面幾步:
1. 首先我們需要在GlSurfaceView.Renderer中初始化需要渲染的幾何圖形
private Triangle mTriangle;
private Square mSquare;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// 設(shè)置背景顏色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 初始化triangle
mTriangle = new Triangle();
// 初始化 square
mSquare = new Square();
}
二.:繪制圖形,因?yàn)樾枰峁┖芏嗉?xì)節(jié)的圖形渲染管線,所以繪制圖形前至少需要一個(gè)頂點(diǎn)著色器來繪制形狀和一個(gè)片段著色器的顏色,形狀。這些著色器必須被編譯,然后加入到一個(gè)OpenGL ES程序,然后將其用于繪制形狀。簡(jiǎn)單介紹下這幾個(gè)概念:
- 頂點(diǎn)著色器(Vertex Shader)頂點(diǎn)著色器是GPU上運(yùn)行的小程序,由名字可以知道,通過它來處理頂點(diǎn),他用于渲染圖形頂點(diǎn)的OpenGL ES圖形代碼。頂點(diǎn)著色器可用來修改圖形的位置,顏色,紋理坐標(biāo),不過不能用來創(chuàng)建新的頂點(diǎn)坐標(biāo)。
- 片段著色器(Fragment Shader ) 用于呈現(xiàn)與顏色或紋理的形狀的面的OpenGL ES代碼。
- 項(xiàng)目(Program) -包含要用于繪制一個(gè)或多個(gè)形狀著色器的OpenGL ES的對(duì)象。
下面給Triangle類定義一個(gè)基本的著色器代碼:
public class Triangle {
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
...
}
當(dāng)然,上面我們創(chuàng)建了著色器的編譯代碼,代碼編寫完成,需要寫個(gè)方法來執(zhí)行這段代碼,這里我們?cè)阡秩酒髦袑懸粋€(gè)如下方法來執(zhí)行著色器代碼:
public static int loadShader(int type, String shaderCode){
// 創(chuàng)造頂點(diǎn)著色器類型(GLES20.GL_VERTEX_SHADER)
// 或者是片段著色器類型 (GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
// 添加上面編寫的著色器代碼并編譯它
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
這里有一點(diǎn)需要注意,因?yàn)橹鞯拇a執(zhí)行是很昂貴滴,所以避免多次執(zhí)行,需要我們一般將執(zhí)行代碼的邏輯寫帶圖形類的構(gòu)造方法中。比如上面的Triangle,我們就這么寫:
private final int mProgram;
public Triangle() {
... ...//數(shù)據(jù)轉(zhuǎn)換
int vertexShader = OneGlRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = OneGlRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// 創(chuàng)建空的OpenGL ES程序
mProgram = GLES20.glCreateProgram();
// 添加頂點(diǎn)著色器到程序中
GLES20.glAttachShader(mProgram, vertexShader);
// 添加片段著色器到程序中
GLES20.glAttachShader(mProgram, fragmentShader);
// 創(chuàng)建OpenGL ES程序可執(zhí)行文件
GLES20.glLinkProgram(mProgram);
}
最后,所有繪制的所有基本配置都配置完成之后,我們來寫繪制圖形的方法,我們?cè)趫D形類(Triangle)中創(chuàng)建一個(gè)繪制的方法onDraw(),可以在onDraw()方法中設(shè)置繪制邏輯。
private int mPositionHandle;
private int mColorHandle;
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
public void draw() {
// 將程序添加到OpenGL ES環(huán)境
GLES20.glUseProgram(mProgram);
// 獲取頂點(diǎn)著色器的位置的句柄
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 啟用三角形頂點(diǎn)位置的句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);
//準(zhǔn)備三角形坐標(biāo)數(shù)據(jù)
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 獲取片段著色器的顏色的句柄
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 設(shè)置繪制三角形的顏色
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// 繪制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用頂點(diǎn)數(shù)組
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
完成上面所有步驟,只需要在GlSurfaceView.Renderer的onDrawFrame()方法中調(diào)用圖形類的繪制方法即可(上面的onDraw()):
public void onDrawFrame(GL10 unused) {
mTriangle.draw();
}
最后的呈現(xiàn)效果如下圖所示:

運(yùn)用投影和相機(jī)視圖
通常情況下,OpenGl中展示的視圖和在Android上顯示的圖形會(huì)有偏差。借用官方圖片:
當(dāng)然我們可以通過矩陣轉(zhuǎn)換來解決這種問題,讓OpenGl上的視圖在任何android設(shè)備上顯示的比例都是一樣的,這里說下什么是投影和相機(jī)視圖:
投影的定義
使用OpenGl繪制的3D圖形,需要展示在移動(dòng)端2D設(shè)備上,這就是投影。Android OpenGl ES中有兩種投影方式:一種是正交投影,一種是透視投影:
正交投影投影物體的帶下不會(huì)隨觀察點(diǎn)的遠(yuǎn)近而發(fā)生變化,我們可以使用下面方法來執(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),視圖越小,反之越大,我們可以通過如下方法來設(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)面距離
相機(jī)視圖
什么是相機(jī)視圖?簡(jiǎn)單來說生活中我們拍照,你站的高度,拿相機(jī)的位置,姿勢(shì)不同,拍出來的照片也就不一樣,相機(jī)視圖就是來修改相機(jī)位置,觀察方式以及相機(jī)的傾斜角度等屬性。我們可以通過下面方法來修改相機(jī)視圖屬性:
Matrix.setLookAtM (float[] rm, //接收相機(jī)變換矩陣
int rmOffset, //變換矩陣的起始位置(偏移量)
float eyeX,float eyeY, float eyeZ, //相機(jī)位置
float centerX,float centerY,float centerZ, //觀察點(diǎn)位置
float upX,float upY,float upZ) //up向量在xyz上的分量
轉(zhuǎn)換矩陣(變換矩陣)
轉(zhuǎn)換矩陣用來做什么的呢?是否記得上面我們繪制的圖形坐標(biāo)需要轉(zhuǎn)換為OpenGl中能處理的小端字節(jié)序(LittleEdian),沒錯(cuò),轉(zhuǎn)換矩陣就是用來將數(shù)據(jù)轉(zhuǎn)為OpenGl ES可用的數(shù)據(jù)字節(jié),我們將相機(jī)視圖和投影設(shè)置的數(shù)據(jù)相乘,便得到一個(gè)轉(zhuǎn)換矩陣,然后我們?cè)僦v此矩陣傳給頂點(diǎn)著色器,具體使用方法及參數(shù)說明如下:
Matrix.multiplyMM (float[] result, //接收相乘結(jié)果
int resultOffset, //接收矩陣的起始位置(偏移量)
float[] lhs, //左矩陣
int lhsOffset, //左矩陣的起始位置(偏移量)
float[] rhs, //右矩陣
int rhsOffset) //右矩陣的起始位置(偏移量)
下面簡(jiǎn)單講解下如何使用投影和相機(jī)視圖來實(shí)現(xiàn)矩陣變換并傳遞給頂點(diǎn)著色器;
定義一個(gè)投影:
// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// 這個(gè)投影矩陣被應(yīng)用于對(duì)象坐標(biāo)在onDrawFrame()方法中
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
定義一個(gè)相機(jī)視圖
@Override
public void onDrawFrame(GL10 unused) {
...
// Set the camera position (View matrix)
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
// Draw shape
mTriangle.draw(mMVPMatrix);
}
修改圖形類執(zhí)行代碼
public class Triangle {
private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// the matrix must be included as a modifier of gl_Position
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" +
"}";
// Use to access and set the view transformation
private int mMVPMatrixHandle;
...
}
投影和相機(jī)視圖代碼到圖形類的繪制方法中去onDraw()
public void draw(float[] mvpMatrix){
... ...
// 得到形狀的變換矩陣的句柄
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// 將投影和視圖轉(zhuǎn)換傳遞給著色器
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
// 畫三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用頂點(diǎn)數(shù)組
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
做完這些,我們就能得到如下圖:

沒錯(cuò),這才沒有變形的視圖。到這里,基本的通過OpenGl繪制簡(jiǎn)單圖形就over了,下面我們講解下如何添加一些交互動(dòng)作。
添加動(dòng)作
前面都是簡(jiǎn)單的動(dòng)作介紹,使用OpenGl在屏幕上繪制對(duì)象是使用openGl的基本功。下面我來說下如何添加旋轉(zhuǎn)形狀。使用OpenGl的描繪對(duì)象是相對(duì)簡(jiǎn)單的,首先需要在渲染器中創(chuàng)建一組旋轉(zhuǎn)矩陣,然后使用之前提到過的投影和相機(jī)視圖變換矩陣結(jié)合起來使用:
private float[] mRotationMatrix = new float[16];
public void onDrawFrame(GL10 gl) {
float[] scratch = new float[16];
...
// 創(chuàng)建一個(gè)旋轉(zhuǎn)矩陣
long time = SystemClock.uptimeMillis() % 4000L;
float angle = 0.090f * ((int) time);
Matrix.setRotateM(mRotationMatrix, 0, angle, 0, 0, -1.0f);
// 將旋轉(zhuǎn)矩陣與投影和相機(jī)視圖組合在一起
// Note that the mMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);
// Draw triangle
mTriangle.draw(scratch);
}
運(yùn)行效果圖如下:

修改頂點(diǎn)顏色
一個(gè)顏色是不是太單調(diào)了?如何讓做成多彩的呢?接下來我們來做一個(gè)多彩三角形,如何來做一個(gè)多彩三角形?我們通過頂點(diǎn)著色器來做?;谏厦娴拇a,我們只需要做一點(diǎn)點(diǎn)改動(dòng),下面是基本步驟:
1. 修改著色器代碼
2. 將顏色值修改為float數(shù)組并轉(zhuǎn)為floatBuffer
3. 將獲取的floatBuffer傳遞給頂點(diǎn)著色器。
修改著色器代碼:
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 uMVPMatrix;"+
"varying vec4 vColor;"+
"attribute vec4 aColor;"+
"void main() {" +
" gl_Position = uMVPMatrix*vPosition;" +
" vColor=aColor;"+
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"varying vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
shader的變量類型(uniform,attribute和varying)的區(qū)別
關(guān)于shader的變量類型(uniform,attribute和varying)的區(qū)別及使用,下面做下說明:
1. uniform:uniform變量在vertex和fragment兩者之間聲明方式完全一樣,則它可以在vertex和fragment共享使用。(相當(dāng)于一個(gè)被vertex和fragment shader共享的全局變量)uniform變量一般用來表示:變換矩陣,材質(zhì),光照參數(shù)和顏色等信息。在代碼中通過GLES20.glGetUniformLocation(int program, String name)來獲取屬性值。并通過 GLES20.glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset);方法將數(shù)據(jù)傳遞給著色器。
2. attribute:這個(gè)變量只能在頂點(diǎn)著色器中使用(vertex Shader),用來表示頂點(diǎn)的數(shù)據(jù),比如頂點(diǎn)坐標(biāo),頂點(diǎn)顏色,法線,紋理坐標(biāo)等。在繪制的時(shí)候通過GLES20.glGetAttribLocation(int program, String name)來獲取變量值,通過 GLES20.glEnableVertexAttribArray(int index)來啟動(dòng)句柄,最后通過 GLES20.glVertexAttribPointer(int indx,int size,int type,boolean normalized,int stride,java.nio.Buffer ptr)來設(shè)置圖形數(shù)據(jù)。
3. varying變量:這個(gè)變量只能用來在vertex和fragment shader之間傳遞數(shù)據(jù)時(shí)使用,不可以通過代碼獲取其變量值。
接來下我們進(jìn)行數(shù)據(jù)轉(zhuǎn)換:
float color[] = {
1.0f, 0f, 0f, 1.0f ,
0f, 1.0f, 0f, 1.0f ,
0f, 0f, 1.0f, 1.0f
};
public Triangle() {
... ...
ByteBuffer dd = ByteBuffer.allocateDirect(
color.length * 4);
dd.order(ByteOrder.nativeOrder());
colorBuffer = dd.asFloatBuffer();
colorBuffer.put(color);
colorBuffer.position(0);
}
最后我們需要獲取著色器的句柄并設(shè)置著色器的顏色:
public void draw(float[] mvpMatrix){
... ...
/* // 獲取片段著色器的vColor成員的句柄
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 設(shè)置繪制三角形的顏色
GLES20.glUniform4fv(mColorHandle, 1, colorBuffer, 0);*/
//獲取片元著色器的vColor成員的句柄
mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
//設(shè)置繪制三角形的顏色
GLES20.glEnableVertexAttribArray(mColorHandle);
GLES20.glVertexAttribPointer(mColorHandle,4,
GLES20.GL_FLOAT,false,
0,colorBuffer);
... ...
}

6. 參考鏈接:
7. 項(xiàng)目地址:
AserbaosAndroid此項(xiàng)目為博主所有的系列學(xué)習(xí)的代碼匯總項(xiàng)目,該文章的代碼位于:opengl/OneOpenGl/OneOpenGlActivity
到此這篇關(guān)于Android openGl 繪制簡(jiǎn)單圖形的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Android openGl 繪制簡(jiǎn)單圖形內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android黑科技之讀取用戶短信+修改系統(tǒng)短信數(shù)據(jù)庫
這篇文章主要介紹了Android黑科技之讀取用戶短信+修改系統(tǒng)短信數(shù)據(jù)庫 的相關(guān)資料,需要的朋友可以參考下2015-12-12
Android學(xué)習(xí)筆記之應(yīng)用單元測(cè)試實(shí)例分析
這篇文章主要介紹了Android學(xué)習(xí)筆記之應(yīng)用單元測(cè)試,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android單元測(cè)試的實(shí)現(xiàn)原理與具體步驟,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Android實(shí)現(xiàn)計(jì)時(shí)器功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)計(jì)時(shí)器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04
Android Binder進(jìn)程間通信工具AIDL使用示例深入分析
Binder作為Android 眾多的IPC通訊手段之一,在Framework的數(shù)據(jù)傳輸中起到極為關(guān)鍵的作用。Binder機(jī)制可謂是Android 知識(shí)體系里的重中之重,作為偏底層的基礎(chǔ)組件,平時(shí)我們很少關(guān)注它,而它卻是無處不在,也是Android 面試易考察的點(diǎn)之一2022-11-11
Android創(chuàng)建外部lib庫及自定義View的圖文教程
這篇文章主要給大家介紹了關(guān)于Android創(chuàng)建外部lib庫及自定義View的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
Android中Button實(shí)現(xiàn)點(diǎn)擊換圖案及顏色
大家好,本篇文章主要講的是Android中Button實(shí)現(xiàn)點(diǎn)擊換圖案及顏色,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
Android使用Messenger實(shí)現(xiàn)service與activity交互
這篇文章主要介紹了android使用Messenger實(shí)現(xiàn)service與activity交互的相關(guān)資料,需要的朋友可以參考下2016-06-06
android屏蔽按鈕連續(xù)點(diǎn)擊的示例代碼
這篇文章主要介紹了android屏蔽按鈕連續(xù)點(diǎn)擊的示例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12

