一妄壶、開始
OpenGl主要需要實現(xiàn)兩個類:
**GLSurfaceView
**
This class is a View where you can draw and manipulate objects using OpenGL API calls and is similar in function to a SurfaceView. You can use this class by creating an instance of GLSurfaceView and adding your Renderer to it. However, if you want to capture touch screen events, you should extend the GLSurfaceView class to implement the touch listeners, as shown in OpenGL training lesson, Responding to Touch Events.
這個是Android官方的解釋,跟SurfaceView類似的可以用OpenGL去繪制的View辨液。
**GLSurfaceView.Renderer
**
This interface defines the methods required for drawing graphics in a GLSurfaceView. You must provide an implementation of this interface as a separate class and attach it to your GLSurfaceView instance using GLSurfaceView.setRenderer().
GLSurfaceView需要設置一個GLSurfaceView.Renderer的實現(xiàn)類去做OpenGl繪制的處理绿满。主要有三個方法:
[onSurfaceCreated()](https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer.html#onSurfaceCreated(javax.microedition.khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig)): The system calls this method once, when creating the GLSurfaceView. Use this method to perform actions that need to happen only once, such as setting OpenGL environment parameters or initializing OpenGL graphic objects.
[onSurfaceChanged()](https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer.html#onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)): The system calls this method when the GLSurfaceView geometry changes, including changes in size of the GLSurfaceView or orientation of the device screen. For example, the system calls this method when the device changes from portrait to landscape orientation. Use this method to respond to changes in the GLSurfaceView container.
onDrawFrame(): The system calls this method on each redraw of the GLSurfaceView. Use this method as the primary execution point for drawing (and re-drawing) graphic objects.
從方法名字就可以看出來
- onSurfaceCreated()主要做一些初始化的操作
- onSurfaceChanged()主要做一些在view改變時候的處理
- onDrawFrame()主要做繪制處理
二厅目、繪制簡單的撞球圖面
1.總覽
我們最終完成的效果如下圖:
首先我們思考下我們怎么去畫圖妖碉,我們需要一只筆猴仑,需要知道在怎么地方畫什么审轮,所以我們的使用OpenGL去繪制的大概步驟如下:
- 定義繪圖數(shù)據(jù)
- 告訴手機如何使用這些數(shù)據(jù)去繪制
2.定義繪圖數(shù)據(jù)
首先我們需要知道一個概念肥哎,OpenGL的坐標系是從-1到1的辽俗,如下圖:
然后我們還要了解到OpenGL只能繪制三種圖形,點篡诽、線崖飘、三角形,所以我們是不能直接畫出來一個矩形的杈女,但是呢朱浴,我們可以用兩個三角形去拼成一個矩形。
所以我們可以這樣定義我們的數(shù)據(jù):
private float[] mData = new float[]{
//三角形
-0.5f , 0.5f,
-0.5f , -0.5f,
0.5f , 0.5f,
-0.5f , 0.5f,
0.5f , 0.5f,
0.5f , -0.5f,
//線
-0.5f , 0f,
0.5f , 0f,
//點
0f , 0.25f,
0f , -0.25f
};
我們如果用Java的話可以這樣去實現(xiàn)达椰,但是現(xiàn)在我們又需要去知道一個知識點了翰蠢,OpenGL是拿不到虛擬機里面的數(shù)據(jù)的,OpenGL只能使用Native的數(shù)據(jù)啰劲,所以現(xiàn)在我們需要找個辦法去讓OpenGL能夠使用我們定義的數(shù)據(jù)梁沧,我們可以這樣去做:
1.定義常量
private static final int BYTE_PRE_FLOAT = 4;
2.定義全局變量
private final FloatBuffer mVertexData;
3.在構造方法中添加如下代碼
mVertexData = ByteBuffer
.allocateDirect(mData.length * BYTE_PRE_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mVertexData.put(mData);
3.使用OpenGL繪制
現(xiàn)在我們有了數(shù)據(jù),而且能夠使用數(shù)據(jù)了蝇裤,現(xiàn)在我們就可以開始去做繪制的工作了廷支。
首先我們需要知道OpenGL的繪制是通過Shader去實現(xiàn)的,而Shader分為兩類:
1.vertex shader:主要告訴GPU哪個點要畫在哪里栓辜,就是獲得點繪制的位置
2.fragment shader:主要告訴GPU哪個點要用什么顏色去畫恋拍,就是獲得點繪制的顏色
現(xiàn)在我們就可以去寫代碼了,在res/
下建個raw
包來存放OpenGL的實現(xiàn)代碼藕甩,然后創(chuàng)建simple_vertex_shader.glsl
文件施敢,實現(xiàn)如下代碼:
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
我們通過a_Position來獲取位置傳給gl_Position,gl_Position就是OpenGL最后用來繪制的位置。這里解釋下vec4表示我們定義了一個向量狭莱,這個向量有4個參數(shù)僵娃,分別為(x,y,z,w),xyz為3D坐標贩毕,w后面的文章會講到悯许,默認值為1。
然后我們還需要創(chuàng)建一個simple_fragment_shader.glsl
文件辉阶,實現(xiàn)如下代碼:
precision mediump float;
uniform vec4 u_Color;
void main() {
gl_FragColor = u_Color;
}
類似simple_vertex_shader.glsl
先壕,我們也是通過u_Color獲取顏色傳遞給gl_FragColor,gl_FragColor就是最后要繪制的顏色瘩扼。
第一行代碼確定所有float型的清晰度,就像是Java中double和float垃僚。我們有三個值可以選擇lowp
集绰,mediump
,highp
谆棺,分別表示低清晰度栽燕,中等清晰度,高清晰度改淑,vertex shader對清晰度要求比較高碍岔,默認設置為highp
,所以我們不用設置朵夏。
這次我們用了uniform
蔼啦,在vertex shader我們使用的是attribute
,attribute
對于每個點都是不同的仰猖,每個點都有自己的值捏肢,但是uniform
的值是不變的,除非我們再次改變它饥侵。
這個我們依然用了vec4表示我們聲明了一個4個參數(shù)的向量鸵赫,只不過這次內容不一樣了,是(r,g,b,a)躏升,應該都猜到了辩棒,就是紅、綠煮甥、藍盗温、透明度。
這樣我們就完成了OpenGl部分代碼的編寫成肘,并對OpenGL有了一定的理解卖局。
現(xiàn)在問題來了,我們要怎么用這兩個東西去繪制到View上呢双霍?現(xiàn)在就讓我們去解決這個問題砚偶,大概需要五個步驟:
1.從.glsl
文件讀出代碼內容
2.編譯讀出來的代碼
3.鏈接到程序
4.獲取參數(shù)的位置
5.使用獲取到的參數(shù)的位置去繪制
現(xiàn)在就讓我們一步一步去實現(xiàn)吧。
- 從
.glsl
文件讀出代碼內容
可能很多地方都會用到讀取文件內容的功能洒闸,所以我們把該功能抽出來做一個工具類染坯,建一個util
包,在該包下建一個TextResouceReader.java
類丘逸,并實現(xiàn)如下方法:
public static String readTextFileFromResource(Context context , int resourceId){
StringBuilder body = new StringBuilder();
InputStream inputStream = null;
InputStreamReader reader = null;
BufferedReader bufferedReader = null;
try {
inputStream = context.getResources().openRawResource(resourceId);
reader = new InputStreamReader(inputStream);
bufferedReader = new BufferedReader(reader);
String line = null;
while ((line = bufferedReader.readLine()) != null){
body.append(line + "\n");
}
}catch (Exception e){
Logger.debug(TAG , "read file has error");
}finally {
try {
if (bufferedReader != null){
bufferedReader.close();
}
if (reader != null){
reader.close();
}
if (inputStream != null){
inputStream.close();
}
}catch (Exception e){}
}
return body.toString();
}
然后我們第一步就可以完成了单鹿,在onSurfaceCreated()
方法中讀取代碼
String vertexShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.sample_vertex_shader);
String fragmentShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.simple_fragment_shader);
- 編譯讀出來的代碼
我們在util
包下創(chuàng)建一個ShaderHelper.java
類,然后實現(xiàn)如下方法
public static int compileShaderCode(int type , String sourceCode){
final int shaderObjectId = glCreateShader(type);
if (shaderObjectId == 0){
Logger.debug(TAG , "can not create sahder");
return 0;
}
glShaderSource(shaderObjectId , sourceCode);
glCompileShader(shaderObjectId);
final int[] compileStatus = new int[1];
glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
if (compileStatus[0] == 0){
Logger.debug(TAG , "compile fail");
}
return shaderObjectId;
}
首先我們創(chuàng)建了一個shader對象深纲,type有兩個值GL_VERTEX_SHADER,GL_FRAGMENT_SHADER
仲锄,返回了一個int型劲妙,這個值是我們OpeGL對象的引用,當我們以后需要應用的時候儒喊,就要用這個值镣奋,如果返回0則表示創(chuàng)建失敗了。然后我們就需要用glShaderSource()
上傳代碼怀愧,glCompileShader()
編譯代碼侨颈,最后,我們檢查了編譯的狀態(tài)芯义。
為了方便使用我們在ShaderHelper.java
方法中再添加兩個方法:
public static int compileVertexShader(String sourceCode){
return compileShaderCode(GL_VERTEX_SHADER , sourceCode);
}
public static int compileFragmentShader(String sourceCode){
return compileShaderCode(GL_FRAGMENT_SHADER , sourceCode);
}
- 鏈接到程序
接下來我們需要在ShaderHelper.java
實現(xiàn)如下的方法:
public static int linkProgram(int vertexShaderId , int fragmentShaderId){
int programId = glCreateProgram();
if (programId == 0){
Logger.debug(TAG , "create program fail");
return 0;
}
glAttachShader(programId , vertexShaderId);
glAttachShader(programId , fragmentShaderId);
glLinkProgram(programId);
final int[] linkStatus = new int[1];
glGetProgramiv(programId , GL_LINK_STATUS , linkStatus , 0);
if (linkStatus[0] == 0){
Logger.debug(TAG , "link fail");
return 0;
}
return programId;
}
跟編譯代碼類似哈垢,我們先創(chuàng)建一個program對象,然后把vertexShader毕贼,fragmentShader傳給program温赔,最后鏈接程序蛤奢,檢查是否鏈接成功鬼癣。
- 獲取參數(shù)的位置
在獲取參數(shù)位置之前我們需要使我們鏈接的程序生效,還需要在ShaderHelper.java
實現(xiàn)如下方法:
public static boolean vaildProgram(int programId){
glValidateProgram(programId);
final int[] validateStatus = new int[1];
glGetProgramiv(programId, GL_VALIDATE_STATUS, validateStatus, 0);
return validateStatus[0] != 0;
}
我們已經在ShaderHelper.java
中完成了這些方法啤贩,我們現(xiàn)在可以方便的用這些方法在Renderder的onSurfaceCreated()
方法中去使用這些方法了待秃。
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
String vertexShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.sample_vertex_shader);
String fragmentShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.simple_fragment_shader);
int vertextShaderId = ShaderHelper.compileVertexShader(vertexShaderCode);
int fragmentShaderId = ShaderHelper.compileFragmentShader(fragmentShaderCode);
mProgram = ShaderHelper.linkProgram(vertextShaderId , fragmentShaderId);
ShaderHelper.vaildProgram(mProgram);
glUseProgram(mProgram);
}
在讓我們的程序生效后,我們就要開始使用它了痹屹,所以我們調用了glUseProgram()
方法章郁。
接下來我們來獲取我們定義的u_Color的位置信息,我們先做如下聲明:
private static final String U_COLOR = "u_Color";
private int mUColorLocation;
然后在onSurfaceCreated()
方法中獲取u_Color的位置信息
mUColorLocation = glGetUniformLocation(mProgram , U_COLOR);
類似的我們可以獲取a_Position的位置信息:
private static final String A_POSITION = "a_Position";
private int mAPositionLocation;
mAPositionLocation = glGetAttribLocation(mProgram , A_POSITION);
- 使用獲取到的參數(shù)的位置去繪制
最后我們就開始去繪制了志衍,首先我們在Renderer的onSurfaceCreated()
方法中添加如下代碼:
mVertexData.position(0);
glVertexAttribPointer(mAPositionLocation , POSITION_COMPOENT_COUNT , GL_FLOAT , false , 0 , mVertexData);
POSITION_COMPOENT_COUNT
為常量:
private static final int POSITION_COMPOENT_COUNT = 2;
mVertexData.position(0)
讓我們可以保證從數(shù)據(jù)的第一個值開始讀取暖庄,然后我們用glVertexAttribPointer()
方法去從vertexData中讀取數(shù)據(jù)賦值給a_Position,該方法的各參數(shù)解釋如下:
POSITION_COMPOENT_COUNT
:表示每個向量讀取兩個值
GL_FLOAT
:表示數(shù)據(jù)類型
false
:該值只有在用int型值的時候為true
0
:這個值只有在數(shù)據(jù)有多個參數(shù)的時候需要使用楼肪,后面會討論培廓,目前置為0
具體的大家可以自己去查下Api。
現(xiàn)在OpenGL就知道如何從vertexData中讀取a_Position的值了春叫,最后我們需要添加一行代碼glEnableVertexAttribArray(aPositionLocation);
肩钠,去讓a_Position能夠被使用。
接下來我們就可以去繪制了暂殖,在onDrawFrame()
中添加如下代碼:
//繪制兩個三角形
glUniform4f(mUColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
glDrawArrays(GL_TRIANGLES, 0, 6);
//繪制線
glUniform4f(mUColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_LINES, 6, 2);
//繪制點
glUniform4f(mUColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
glDrawArrays(GL_POINTS, 8, 1);
glUniform4f(mUColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_POINTS, 9, 1);
glUniform4f()
用來給u_Color賦值价匠。
glDrawArrays()
來繪制,第一個參數(shù)表示要繪制什么呛每,第二個參數(shù)表示從之前載入的數(shù)據(jù)哪里開始讀取踩窖,第三個參數(shù)表示讀幾個值。
這個時候就算完成了我們的基本功能了晨横,但是運行之后洋腮,你會發(fā)現(xiàn)繪制的點不見了廉沮,其實只是太小了,看不到而已徐矩,在simple_vertex_shader.glsl
文件中添加如下代碼:gl_PointSize = 10;
再運行一遍滞时,就能看到我們想要的效果了。
項目代碼在這里:https://github.com/KevinKmoo/SimpleAirHockey
能力有限滤灯,自己讀書的學習所得坪稽,有錯誤請指導,輕虐鳞骤!
轉載請注明出處窒百。----by kmoo