前言
上一篇文章OpenGL ES入門教程(一)編寫第一個OpenGL程序,我們創(chuàng)建了自己的第一個OpenGL程序,實(shí)現(xiàn)了繪制紅色背景的Activity頁面,算是OpenGL ES的hello world程序吧。本篇文章基于上一篇文章基礎(chǔ)上講解如何使用OpenGL繪制一張平面桌子,桌子由一個長方形構(gòu)成,且長方形中間繪制一條線,長方形兩頭繪制兩個點(diǎn)。文章中提到的示例代碼我都共享到gitee上了,各位博友可以從我的gitee倉庫OpenGL_ES_DEMO下載完整的項(xiàng)目代碼,代碼都有很詳細(xì)的提交記錄。
0. OpenGL繪制圖形的整體框架概述
- 要繪制圖形,就要有圖形的位置坐標(biāo)數(shù)據(jù),OpenGL中稱這些位置為頂點(diǎn),因此,首先需要定義頂點(diǎn)數(shù)據(jù)
- OpenGL如何操作頂點(diǎn)數(shù)據(jù)呢?答案是通過著色器操作圖形數(shù)據(jù),因此,第二步我們要定義著色器
- 定義完著色器需要加載著色器以供OpenGL使用
- OpenGL編譯著色器
- 將著色器鏈接為一個OpenGL程序?qū)ο?/li>
- 通過OpenGL程序?qū)ο髮⒅髋c圖形數(shù)據(jù)相關(guān)
- 以上操作算是使用OpenGL的初始化操作,步驟比較固定,編寫完成后,就可以開心的繪制圖形啦
繪制圖形都是在OpenGL渲染器類中完成,所以,本篇文章的所有代碼都是在上一篇文章中定義的AirHockeyRenderer類中編寫。
1. 定義頂點(diǎn)
OpenGL包括三類基礎(chǔ)圖形,點(diǎn),直線,三角形。其余的任何圖形,都是由這三種基本圖形組成。
因此如果我們想繪制一個前言中所述的平面桌子,可以由兩個三角形組成一個長方形,并在長方形的中間繪制一條直線,兩端繪制兩個點(diǎn),如下圖所示:
無論是x還是y坐標(biāo),OpenGL都會把屏幕映射到[-1,1]的范圍內(nèi)。 即屏幕的左邊對應(yīng)x軸的-1,右邊對應(yīng)+1;屏幕的底邊對應(yīng)y軸的-1,頂邊對應(yīng)+1;因此如果將上面的圖形繪制到屏幕中間,需要的頂點(diǎn)坐標(biāo)如下圖所示:
如上圖所示坐標(biāo)數(shù)據(jù),我們將每個頂點(diǎn)(由x坐標(biāo)和y坐標(biāo)組成)的數(shù)據(jù)存儲到數(shù)組中,上圖主要包含的OpenGL基本圖形是兩個三角形,一條線,兩個點(diǎn),其中三角形的頂點(diǎn)我們統(tǒng)一按照逆時針方向進(jìn)行存儲,這一步數(shù)據(jù)準(zhǔn)備,我可以將它放在渲染器類的構(gòu)造函數(shù)中,最終定義的頂點(diǎn)數(shù)據(jù)如下:
private Context context;//后面我們加載著色器需要用到該上下文,因此也通過構(gòu)造函數(shù)傳進(jìn)去
public AirHockeyRenderer(Context context)
{
this.context = context;
float[] tableVerticesWithTriangles = {
/**
無論是x還是y坐標(biāo),OpenGL都會把屏幕映射到[-1,1]的范圍內(nèi)。
即屏幕的左邊對應(yīng)x軸的-1,右邊對應(yīng)+1;
屏幕的底邊對應(yīng)y軸的-1,頂邊對應(yīng)+1
*/
// Triangle 1
-0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
// Triangle 2
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f,
// Line 1
-0.5f, 0f,
0.5f, 0f,
// Mallets
0f, -0.25f,
0f, 0.25f
};
}
OpenGL作為本地系統(tǒng)庫直接運(yùn)行在硬件上,無法直接讀取java程序中定義的數(shù)據(jù),因此我們需要把上面定義的頂點(diǎn)數(shù)據(jù)復(fù)制到本地內(nèi)存中。具體實(shí)現(xiàn)代碼如下:
private Context context;
private static final int BYTES_PER_FLOAT = 4;
private final FloatBuffer vertexData;
public AirHockeyRenderer(Context context)
{
this.context = context;
float[] tableVerticesWithTriangles = {
/**
無論是x還是y坐標(biāo),OpenGL都會把屏幕映射到[-1,1]的范圍內(nèi)。
即屏幕的左邊對應(yīng)x軸的-1,右邊對應(yīng)+1;
屏幕的底邊對應(yīng)y軸的-1,頂邊對應(yīng)+1
*/
// Triangle 1
-0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
// Triangle 2
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f,
// Line 1
-0.5f, 0f,
0.5f, 0f,
// Mallets
0f, -0.25f,
0f, 0.25f
};
vertexData = ByteBuffer
//申請本地內(nèi)存空間大小,單位為字節(jié)。tableVerticesWithTriangles中存儲的是float類型數(shù)據(jù),由32bit組成,即4個字節(jié)組成;
.allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT)
//本地內(nèi)存空間的一種排序方式
.order(ByteOrder.nativeOrder())
//轉(zhuǎn)換為我們需要的FloatBuffer類型
.asFloatBuffer();
//將tableVerticesWithTriangles中的數(shù)據(jù)拷貝到本地內(nèi)存中
vertexData.put(tableVerticesWithTriangles);
}
2. 定義著色器
OpenGL繪制圖形的流程稱為OpenGL管道(pipeline)。上面我們定義了頂點(diǎn)數(shù)據(jù),并將其拷貝到了本地內(nèi)存,下面就是將本地內(nèi)存中的圖形頂點(diǎn)數(shù)據(jù)在OpenGL管道進(jìn)行流動,并通過著色器告訴GPU如何繪制數(shù)據(jù)。
著色器是一種只能運(yùn)行在GPU上的特殊類型程序。OpenGL著色器分為頂點(diǎn)著色器和片段著色器兩種類型:
- 頂點(diǎn)著色器(vertex shader)用于處理頂點(diǎn)(主要作用是確定位置);
- 片段著色器用于處理,由點(diǎn),線,三角形組成的片段(主要作用是告訴GPU每個片段的最終顏色)
著色器處理完成后(顏色生成),openGL將它們寫入一塊稱為幀緩沖區(qū)(frame buffer)的內(nèi)存塊,然后,Android把這分幀緩沖區(qū)中的數(shù)據(jù)顯示在屏幕上。
OpenGL管道(pipeline)流程如下:
- 讀取頂點(diǎn)數(shù)據(jù) -》執(zhí)行頂點(diǎn)著色器-》組裝圖元-》光柵化圖元-》執(zhí)行片段著色器-》寫入幀緩沖器-》顯示在屏幕上
其中,光柵化技術(shù)是指:移動設(shè)備的顯示屏通過大量像素的堆積(紅綠藍(lán)三種顏色不同比例的混合,就足以創(chuàng)造出人眼可見范圍內(nèi)的顏色),在視覺上創(chuàng)造出巨量顏色范圍的技術(shù)。而OpenGL光柵化就是把每個點(diǎn),直線,三角形,分解成大量的小片段,通常情況下,一個片段直接映射到屏幕的一個像素。
OpenGL著色器的定義采用著色器特定的語言(語法結(jié)構(gòu)類似C語言),著色器文件后綴名為glsl(OpenGL shader language)。我們在工程的res目錄下新建一個raw文件夾,并在raw文件夾中創(chuàng)建頂點(diǎn)著色器文件simple_vertex_shader.glsl和片段著色器文件simple_fragment_shader.glsl,如下圖所示:
頂點(diǎn)著色器文件內(nèi)容如下:
/*
attribute:定義頂點(diǎn)類型位置數(shù)據(jù)的特定標(biāo)識
vec4:一種包含4個分量的向量數(shù)據(jù)類型(x,y,z,w)
其中x,y,z代表頂點(diǎn)的三維位置坐標(biāo),w是一個特殊坐標(biāo),后面會講解
a_Position:變量名稱,該名稱后面OpenGL的glGetAttribLocation方法要用到,
如果修改后面就要一起修改
*/
attribute vec4 a_Position;
//和C語言類似,main函數(shù)是著色器的入口函數(shù)
void main()
{ //gl_Position :OpenGL特定的變量名,用于存儲我們定義的頂點(diǎn)數(shù)據(jù)
gl_Position = a_Position;
//gl_PointSize:OpenGL特定的變量名,用于存儲點(diǎn)的大小
gl_PointSize = 10.0;
}
片段著色器內(nèi)容如下:
//OpenGL定義float數(shù)據(jù)類型的精度(lowp;mediump;highp),就像java代碼中浮點(diǎn)型選擇float類型還是double類型。
//精度是以性能為代價(jià)的,這里選擇mediump
precision mediump float;
/*
uniform:定義片段顏色的一種特殊標(biāo)識
vec4:一種包含4個分量的向量數(shù)據(jù)類型(r,g,b,a),分別代碼紅,綠,藍(lán),透明度。
其中rgba的取值范圍是0-1,rgba色彩不了解的可以去其它文章了解一下。
u_Color:變量名稱,該名稱后面OpenGL的glGetUniformLocation方法要用到,
如果修改后面就要一起修改
*/
uniform vec4 u_Color;
//和C語言類似,main函數(shù)是著色器的入口函數(shù)
void main()
{
gl_FragColor = u_Color;
}
3. 加載著色器
加載著色器其實(shí)非常簡單就是通過Java IO流的方式將著色器文件中的內(nèi)容讀取為一個字符,以供OpenGL后面編譯著色器使用。我以前寫過一篇詳細(xì)的Java IO流文章,如果有興趣的博友可以移步去看看Java IO流最全詳解
為了復(fù)用代碼,我們定義一個TextResourceReader類,并在類中實(shí)現(xiàn)一個靜態(tài)方法readTextFileFromResource,專用于加載著色器,具體實(shí)現(xiàn)代碼如下:
public class TextResourceReader {
public static String readTextFileFromResource(Context context, int resourceId) {
StringBuilder body = new StringBuilder();
try {
InputStream inputStream = context.getResources().openRawResource(resourceId);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String newLine;
while ((newLine = bufferedReader.readLine()) != null)
{
body.append(newLine);
body.append("\n");
}
}
catch (IOException e)
{
throw new RuntimeException("Could not open resource: " + resourceId, e);
}
catch (Resources.NotFoundException e)
{
throw new RuntimeException("Resource not found: " + resourceId, e);
}
return body.toString();
}
}
因?yàn)榧虞d著色器算是OpenGL繪圖的初始化操作,我們可以在surface創(chuàng)建的時候調(diào)用一次該邏輯,即在渲染器類重寫的onSurfaceCreated方法中調(diào)用加載著色器的邏輯,具體代碼如下所示:
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
//加載著色器
String vertexShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_vertex_shader);
String fragmentShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_fragment_shader);
}
4. 編譯著色器
編譯著色器和將著色器鏈接到OpenGL程序?qū)ο蠖际潜容^固定的渲染步驟,為了重復(fù)利用它們,我們定義一個ShaderHelper類,專門用于編譯著色器,將著色器鏈接為OpenGL程序?qū)ο蠛万?yàn)證OpenGL程序?qū)ο蟮挠行浴?/p>
編譯著色的實(shí)現(xiàn)流程比較固定,我們只需要會用就行了,不必死記硬背,具體實(shí)現(xiàn)代碼如下:
/**
* Compiles a shader, returning the OpenGL object ID.
* 1. glCreateShader 創(chuàng)建著色器對象 0代表失敗,檢查創(chuàng)建狀態(tài)
* 2. glShaderSource 向著色器對象中上傳著色器源碼
* 3. glCompileShader 著色器對象編譯源碼
* 4. glGetShaderiv 獲取編譯狀態(tài),若編譯失敗,則刪除著色器對象id,否則返回著色器對象id
* 5. glGetShaderInfoLog 獲取編譯結(jié)果的詳細(xì)信息
* 6. 如果編譯失敗,glDeleteShader刪除渲染器對象id
* 7. 編譯成功,返回渲染器對象id
* @param type 著色器類型:頂點(diǎn)著色器 GL_VERTEX_SHADER,片段著色器 GL_FRAGMENT_SHADER
* @param shaderCode 加載的著色器代碼
* @return
*/
private static int compileShader(int type, String shaderCode) {
// Create a new shader object.
final int shaderObjectId = glCreateShader(type);
if (shaderObjectId == 0) {
if (LoggerConfig.ON) {
Log.w(TAG, "Could not create new shader.");
}
return 0;
}
// Pass in the shader source.
glShaderSource(shaderObjectId, shaderCode);
// Compile the shader.
glCompileShader(shaderObjectId);
// Get the compilation status.
final int[] compileStatus = new int[1];
glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
if (LoggerConfig.ON) {
// Print the shader info log to the Android log output.
Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode + "\n:"
+ glGetShaderInfoLog(shaderObjectId));
}
// Verify the compile status.
if (compileStatus[0] == 0) {
// If it failed, delete the shader object.
glDeleteShader(shaderObjectId);
if (LoggerConfig.ON) {
Log.w(TAG, "Compilation of shader failed.");
}
return 0;
}
// Return the shader object ID.
return shaderObjectId;
}
/**
* Loads and compiles a vertex shader, returning the OpenGL object ID.
*/
public static int compileVertexShader(String shaderCode) {
return compileShader(GL_VERTEX_SHADER, shaderCode);
}
/**
* Loads and compiles a fragment shader, returning the OpenGL object ID.
*/
public static int compileFragmentShader(String shaderCode) {
return compileShader(GL_FRAGMENT_SHADER, shaderCode);
}
因?yàn)榫幾g著色器算是OpenGL繪圖的初始化操作,我們可以在surface創(chuàng)建的時候調(diào)用一次該邏輯,即在渲染器類重寫的onSurfaceCreated方法中調(diào)用編譯著色器的邏輯,具體代碼如下所示:
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
//加載著色器
String vertexShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_vertex_shader);
String fragmentShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_fragment_shader);
//編譯著色器
int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource);
int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderSource);
}
5. 將著色器鏈接為OpenGL程序?qū)ο?/h3>
將著色器鏈接為OpenGL程序?qū)ο蟮膶?shí)現(xiàn)流程也比較固定,我們只需要會用就行了,不必死記硬背,具體實(shí)現(xiàn)代碼如下:
/**
*
* Links a vertex shader and a fragment shader together into an OpenGL
* program. Returns the OpenGL program object ID, or 0 if linking failed.
*
* 1. glCreateProgram 創(chuàng)建OpenGL鏈接程序?qū)ο?,獲取對象id, 對象id為0代表創(chuàng)建失敗
* 2. glAttachShader 附上著色器
* 3. glLinkProgram 鏈接程序,把著色器聯(lián)合起來
* 4. glGetProgramiv 獲取鏈接狀態(tài),若成功則返回鏈接對象id,否則glDeleteProgram刪除鏈接對象
*
* @param vertexShaderId 頂點(diǎn)著色器對象id
* @param fragmentShaderId 片段著色器對象id
* @return
*/
public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
// Create a new program object.
final int programObjectId = glCreateProgram();
if (programObjectId == 0) {
if (LoggerConfig.ON) {
Log.w(TAG, "Could not create new program");
}
return 0;
}
// Attach the vertex shader to the program.
glAttachShader(programObjectId, vertexShaderId);
// Attach the fragment shader to the program.
glAttachShader(programObjectId, fragmentShaderId);
// Link the two shaders together into a program.
glLinkProgram(programObjectId);
// Get the link status.
final int[] linkStatus = new int[1];
glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);
if (LoggerConfig.ON) {
// Print the program info log to the Android log output.
Log.v(TAG, "Results of linking program:\n"
+ glGetProgramInfoLog(programObjectId));
}
// Verify the link status.
if (linkStatus[0] == 0) {
// If it failed, delete the program object.
glDeleteProgram(programObjectId);
if (LoggerConfig.ON) {
Log.w(TAG, "Linking of program failed.");
}
return 0;
}
// Return the program object ID.
return programObjectId;
}
/**
* Validates an OpenGL program. Should only be called when developing the application.
* 1. glValidateProgram驗(yàn)證鏈接到OpenGL程序?qū)ο蟮挠行? * 2. glGetProgramiv獲取OpenGL程序?qū)ο笥行缘臓顟B(tài),如果返回0代表無效,否則代表鏈接OpenGL程序?qū)ο蟪晒? * @param programObjectId
* @return
*/
public static boolean validateProgram(int programObjectId) {
glValidateProgram(programObjectId);
final int[] validateStatus = new int[1];
glGetProgramiv(programObjectId, GL_VALIDATE_STATUS, validateStatus, 0);
Log.v(TAG, "Results of validating program: " + validateStatus[0]
+ "\nLog:" + glGetProgramInfoLog(programObjectId));
return validateStatus[0] != 0;
}
因?yàn)閷⒅麈溄訛镺penGL程序?qū)ο笏闶荗penGL繪圖的初始化操作,我們可以在surface創(chuàng)建的時候調(diào)用一次該邏輯,即在渲染器類重寫的onSurfaceCreated方法中實(shí)現(xiàn)鏈接OpenGL程序?qū)ο蟮倪壿?,具體代碼如下所示:
private int program;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
//加載著色器
String vertexShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_vertex_shader);
String fragmentShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_fragment_shader);
//編譯著色器
int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource);
int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderSource);
//將著色器鏈接到OpenGL程序
program = ShaderHelper.linkProgram(vertexShader, fragmentShader);
//打印OpenGL程序?qū)ο蟮挠行孕畔?/span>
if (LoggerConfig.ON) {
ShaderHelper.validateProgram(program);
}
}
6. 將著色器需要的數(shù)據(jù)與拷貝到本地的數(shù)組相關(guān)聯(lián)
至此,我們已經(jīng)完成了所要繪制圖形的頂點(diǎn)數(shù)據(jù)定義,著色器定義、加載、編譯,以及OpenGL程序?qū)ο螳@取。下面我們需要通過OpenGL程序?qū)ο螳@取著色器中定義的屬性,并將著色器需要的數(shù)據(jù)與我們拷貝到本地的數(shù)據(jù)相關(guān)聯(lián),繼而完成OpenGL繪圖的所有前置操作。因?yàn)檫@一步仍然屬于OpenGL繪圖的初始化或者前置工作,我們可以在surface創(chuàng)建的時候調(diào)用一次該邏輯,即在渲染器類重寫的onSurfaceCreated方法中調(diào)用實(shí)現(xiàn)該邏輯,具體實(shí)現(xiàn)代碼如下:
//這個字符串一定要和片段著色器中定義的屬性名一致
private static final String U_COLOR = "u_Color";
private int uColorLocation;
//這個字符串一定要和頂點(diǎn)著色器中定義的屬性名一致
private static final String A_POSITION = "a_Position";
private int aPositionLocation;
//每個頂點(diǎn)由兩個浮點(diǎn)數(shù)組成:x,y
private static final int POSITION_COMPONENT_COUNT = 2;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
//加載著色器
String vertexShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_vertex_shader);
String fragmentShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_fragment_shader);
//編譯著色器
int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource);
int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderSource);
//將著色器鏈接到OpenGL程序
program = ShaderHelper.linkProgram(vertexShader, fragmentShader);
//打印OpenGL程序?qū)ο蟮挠行孕畔?/span>
if (LoggerConfig.ON) {
ShaderHelper.validateProgram(program);
}
/*
將著色器需要的數(shù)據(jù)與拷貝到本地的數(shù)組相關(guān)聯(lián)
*/
//告訴OpenGL在繪制任何東西到屏幕上時候,使用這里定義程序
glUseProgram(program);
//獲取片段著色器中uniform的顏色屬性
uColorLocation = glGetUniformLocation(program, U_COLOR);
//獲取頂點(diǎn)著色器中(attribute)位置屬性
aPositionLocation = glGetAttribLocation(program, A_POSITION);
//告訴OpenGL從vertexData中讀取a_Position的數(shù)據(jù)
vertexData.position(0);//將緩沖區(qū)數(shù)據(jù)中的指針指向第一個數(shù)據(jù),即從第一個數(shù)據(jù)開始讀
/*
將著色器中的位置屬性與本地頂點(diǎn)數(shù)據(jù)相關(guān)聯(lián)。
aPositionLocation:著色器中定義的位置屬性
POSITION_COMPONENT_COUNT:每次從本地?cái)?shù)組中讀取兩個數(shù)據(jù)(即x,y代表一個頂點(diǎn)坐標(biāo))
GL_FLOAT:OpenGL采用的數(shù)據(jù)類型,因?yàn)槲覀兌x的是浮點(diǎn)數(shù)數(shù)組,所以采用GL_FLOAT
vertexData:要關(guān)聯(lián)的本地?cái)?shù)據(jù)列表
*/
glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT,
false, 0, vertexData);
//使能頂點(diǎn)數(shù)組
glEnableVertexAttribArray(aPositionLocation);
}
glVertexAttribPointer方法所需參數(shù)的詳細(xì)說明如下圖所示:
7. 在屏幕上繪制圖形
至此,我們已經(jīng)完成了使用OpenGL繪制圖形的所有必要的前置步驟,下面就是繪制圖形了,繪制圖形的邏輯在渲染類中重寫的onDrawFrame方法中實(shí)現(xiàn)。
OpenGL繪制基本圖形的步驟如下:
- 通過glUniform4f方法,更新片段著色器的顏色,該顏色將應(yīng)用于下面它后面繪制的所有圖形,直至再次更新顏色。
glUniform4f方法的定義如下:
public static native void glUniform4f(
int location,//片段著色器的顏色屬性
float x, //rgb色彩的r分量
float y, //rgb色彩的g分量
float z, //rgb色彩的b分量
float w //rgb色彩的透明度分量
);
- 通過glDrawArrays方法,繪制基本圖形
glDrawArrays方法的定義如下:
public static native void glDrawArrays(
int mode, //要繪制的基本圖元類型:三角形 GL_TRIANGLES;直線 GL_LINES;點(diǎn) GL_POINTS
int first,//從本地頂點(diǎn)數(shù)組中讀取數(shù)據(jù)的開始位置
int count //一共讀取多少個頂點(diǎn),上面glVertexAttribPointer方法中我定義了一個頂點(diǎn)由2個數(shù)據(jù)組成
);
因此,如果我們想繪制一個白色的三角形,實(shí)現(xiàn)代碼如下:
//更新著色器中u_Color的值(白色)
glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
//因?yàn)槿切斡扇齻€頂點(diǎn)組成,因此我們從本地頂點(diǎn)數(shù)據(jù)列表中的第一個頂點(diǎn)開始,連續(xù)讀取3個頂點(diǎn)
glDrawArrays(GL_TRIANGLES, 0, 3);
如果我們想要實(shí)現(xiàn)繪制上面提到的如下圖形,具體實(shí)現(xiàn)代碼如下:
@Override
public void onDrawFrame(GL10 gl) {
glClear(GL_COLOR_BUFFER_BIT);
/*
繪制桌子
*/
//更新著色器中u_Color的值(白色)
glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
//參數(shù)1:繪制三角形;參數(shù)2:從頂點(diǎn)數(shù)組的開頭開始讀頂點(diǎn);參數(shù)3:讀取6個頂點(diǎn)(即繪制兩個三角形)
//之前glVertexAttribPointer告訴過OpenGL每個頂點(diǎn)的位置包含兩個浮點(diǎn)分量,因此OpenGL會使用vertexData中如下12個浮點(diǎn)數(shù)繪制兩個三角形
/**
// Triangle 1
-0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
// Triangle 2
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f,
*/
glDrawArrays(GL_TRIANGLES, 0, 6);
/*
繪制分割線
*/
//更新u_Color的值(紅色)
glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
//參數(shù)1:繪制直線; 參數(shù)2:從頂點(diǎn)數(shù)組的第6個頂點(diǎn)之后(即第7個頂點(diǎn))開始讀??;參數(shù)3:讀取兩個頂點(diǎn)
glDrawArrays(GL_LINES, 6, 2);
/*
繪制兩個木槌
*/
//更新u_Color的值(藍(lán)色)
glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
glDrawArrays(GL_POINTS, 8, 1);
//更新u_Color的值(綠色)
glUniform4f(uColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
glDrawArrays(GL_POINTS, 9, 1);
}
至此,我們已經(jīng)實(shí)現(xiàn)了使用OpenGL繪制一個平面桌子的全部代碼,可以從我的gitee倉庫OpenGL_ES_DEMO下載完整的項(xiàng)目代碼,并執(zhí)行g(shù)it reset --hard f1a8e96f0e126814be1a4275459bd7be37c183c0切到文章目前所實(shí)現(xiàn)代碼的節(jié)點(diǎn),運(yùn)行程序效果如下:
8. 讓桌子有邊框的效果
為了更好的驗(yàn)證我們是否掌握了使用OpenGL靈活繪制圖形的能力,可以在以上繪制圖形的基礎(chǔ)上,再在長方形內(nèi)添加一個小的長方形,形成桌子邊框的效果,各位博友如果能夠獨(dú)立實(shí)現(xiàn)這一功能,那么應(yīng)該對OpenGL基本圖形的繪制有了比較熟練的掌握。具體實(shí)現(xiàn)思路和代碼如下:文章來源:http://www.zghlxwxcb.cn/news/detail-796436.html
- 既然要再加一個小長方形,第一步就需要定義它的頂點(diǎn)坐標(biāo),添加小長方形后的頂點(diǎn)坐標(biāo)數(shù)組如下:
float[] tableVerticesWithTriangles = {
/**
無論是x還是y坐標(biāo),OpenGL都會把屏幕映射到[-1,1]的范圍內(nèi)。
即屏幕的左邊對應(yīng)x軸的-1,右邊對應(yīng)+1;
屏幕的底邊對應(yīng)y軸的-1,頂邊對應(yīng)+1
*/
// Triangle 1
-0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
// Triangle 2
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f,
// Triangle 3
-0.4f, -0.4f,
0.4f, 0.4f,
-0.4f, 0.4f,
// Triangle 4
-0.4f, -0.4f,
0.4f, -0.4f,
0.4f, 0.4f,
// Line 1
-0.5f, 0f,
0.5f, 0f,
// Mallets
0f, -0.25f,
0f, 0.25f
};
- 頂點(diǎn)坐標(biāo)數(shù)據(jù)有了后,就可以開始使用OpenGL繪制圖形了,添加一個小長方形后的繪制代碼如下:
@Override
public void onDrawFrame(GL10 gl) {
glClear(GL_COLOR_BUFFER_BIT);
/*
繪制桌子
*/
//更新著色器中u_Color的值(藍(lán)色)
glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
//參數(shù)1:繪制三角形;參數(shù)2:從頂點(diǎn)數(shù)組的開頭開始讀頂點(diǎn);參數(shù)3:讀取6個頂點(diǎn)(即繪制兩個三角形)
//之前glVertexAttribPointer告訴過OpenGL每個頂點(diǎn)的位置包含兩個浮點(diǎn)分量,因此OpenGL會使用vertexData中如下12個浮點(diǎn)數(shù)繪制兩個三角形
/**
// Triangle 1
-0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
// Triangle 2
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f,
*/
glDrawArrays(GL_TRIANGLES, 0, 6);
/**
* 繪制第二個內(nèi)長方形,形成邊框的效果
*/
//更新著色器中u_Color的值(白色)
glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
glDrawArrays(GL_TRIANGLES, 6, 6);
/*
繪制分割線
*/
//更新u_Color的值(紅色)
glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
//參數(shù)1:繪制直線; 參數(shù)2:從頂點(diǎn)數(shù)組的第6個頂點(diǎn)之后(即第7個頂點(diǎn))開始讀?。粎?shù)3:讀取兩個頂點(diǎn)
glDrawArrays(GL_LINES, 12, 2);
/*
繪制兩個木槌
*/
//更新u_Color的值(黑色)
glUniform4f(uColorLocation, 0.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_POINTS, 14, 1);
//更新u_Color的值(綠色)
glUniform4f(uColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
glDrawArrays(GL_POINTS, 15, 1);
}
下載完整的項(xiàng)目代碼后,執(zhí)行g(shù)it reset --hard bd8d6607592abcddd1547a5e36a01688772eda64切到實(shí)現(xiàn)帶邊框效果的桌面節(jié)點(diǎn),運(yùn)行程序效果如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-796436.html
到了這里,關(guān)于OpenGL ES入門教程(二)之繪制一個平面桌子的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!