Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> OpenglES2.0 for Android:來畫個三角形吧

OpenglES2.0 for Android:來畫個三角形吧

編輯:關於Android編程

先看看我們的整個流程:

  \  

理解坐標系:

  \   左側是Opengl默認的坐標系,右邊是典型的android設備屏幕的坐標系。左側的瘦瘦的三角形映射到android屏幕上就變成了胖胖的三角形(屏幕橫向的時候),我們可以使用 camera和投影解決這個問題,具體怎麼解決這裡就先不累述了。這裡我們只需要知道屏幕的左上角是(-1.0,1.0)橫向向右為X軸正向,縱向向下為Y軸 負向,其范圍都是從 -1到 +1。   定義三角形頂點:   我們在第一個android的小demo的工程裡新建一個包shape,然後新建一個類Triangle 。然後我們在該類中定義三角形的三個頂點的數據: 此時該類如下(Triangle.java):  
package com.cumt.shape;

import android.content.Context;

public class Triangle {
	
	private Context context;
	// 數組中每個頂點的坐標數
    static final int COORDS_PER_VERTEX = 2;
	// 每個頂點的坐標數  	X ,  Y 
    static float triangleCoords[] = { 0.0f,  0.5f ,   // top
                                     -0.5f, -0.5f ,   // bottom left
                                      0.5f, -0.5f };   // bottom right
    
    public Triangle(Context context){
    	this.context = context;
    }
}

我們在該類中定義的float類型的數據並不能直接被opengl使用,float[ ]是屬於虛擬機環境的,而Opengl作為本地系統庫直接運行在硬件上,所以我們需要將float[ ] 轉化為 FloatBuffer以使數據可以被opengl使用,此時該類如下(Triangle.java ):  
package com.cumt.shape;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import android.content.Context;

public class Triangle {
	
	private Context context;
	
	private static final int BYTES_PER_FLOAT = 4;
	private FloatBuffer vertexBuffer;
	
	// 數組中每個頂點的坐標數
    static final int COORDS_PER_VERTEX = 2;
	// 每個頂點的坐標數  	X ,  Y 
    static float triangleCoords[] = { 0.0f,  0.5f ,   // top
                                     -0.5f, -0.5f ,   // bottom left
                                      0.5f, -0.5f };   // bottom right
    
    public Triangle(Context context){
    	this.context = context;
    	
    	vertexBuffer = ByteBuffer
    			.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
    			.order(ByteOrder.nativeOrder())
    			.asFloatBuffer();
    	// 把坐標們加入FloatBuffer中
        vertexBuffer.put(triangleCoords);
        // 設置buffer,從第一個坐標開始讀
        vertexBuffer.position(0);
    }
}

編寫著色器:

著色器語言(shading language)是opengl es2.0 比opengl es 1.X 新增的內容,使我們更加自由的實現自己想要的效果,該語言基於C/C++。該語言的具體類型等等這裡不詳細說明。   我們需要兩個著色器——頂點著色器和片段著色器,顧名思義頂點著色器是用於處理頂點坐標的(不要與屏幕的像素點混淆),而片段著色器用於處理片段,所謂片段簡單理解就是頂點著色器處理後的一堆頂點形成的片段。   現在我們來編寫這兩個頂點著色器:在 res 目錄下新建一個文件夾 raw 在該文件夾下新建一個文件simple_vertex_shader.glsl ,其內容如下:  
attribute vec4 a_Position;   
  		
void main()                    
{                              
    gl_Position = a_Position;
}   
gl_Position即opengl定義的頂點的坐標,我們目的就是通過這個來告訴opengl我們的頂點數據。vec4是著色器語言中的向量類型的一種,包含了四個浮點數的向量。   接下來在raw文件夾內新建文件simple_fragment_shader.glsl ,其內容如下:  
precision mediump float; 
      	 								
uniform vec4 u_Color;     							
  
void main()                    		
{                              	
    gl_FragColor = u_Color;                                  		
}

這裡我們只傳入一個顏色信息。這裡注意一下 上面頂點著色器的 限定符attribute 和uniform 。attribute 一般用於每個頂點各不相同的量,如頂點位置等,後者一般用於 同一組頂點組成的相同的量,如光源位置,一組顏色等。    

編譯著色器及繪制:

接下來我們進行最後一步,編譯著色器以及繪制三角形,這裡我們來寫一個工具類用於加載著色器程序以及編譯著色器。 首先新建一個包utils ,此時目錄結構如下圖所示:   \ 在utils包下新建類 LoggerConfig 用於管理我們的日志(是否輸出Logcat的信息),代碼如下(LoggerConfig.java):  
package com.cumt.utils;

public class LoggerConfig {
    public static final boolean ON = true;
}
  再在該包下新建類ShaderHelper用於加載著色器程序以及編譯著色器:  
package com.cumt.utils;

import static android.opengl.GLES20.GL_COMPILE_STATUS;
import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
import static android.opengl.GLES20.GL_LINK_STATUS;
import static android.opengl.GLES20.GL_VALIDATE_STATUS;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glAttachShader;
import static android.opengl.GLES20.glCompileShader;
import static android.opengl.GLES20.glCreateProgram;
import static android.opengl.GLES20.glCreateShader;
import static android.opengl.GLES20.glDeleteProgram;
import static android.opengl.GLES20.glDeleteShader;
import static android.opengl.GLES20.glGetProgramInfoLog;
import static android.opengl.GLES20.glGetProgramiv;
import static android.opengl.GLES20.glGetShaderInfoLog;
import static android.opengl.GLES20.glGetShaderiv;
import static android.opengl.GLES20.glLinkProgram;
import static android.opengl.GLES20.glShaderSource;
import static android.opengl.GLES20.glValidateProgram;
import android.util.Log;

public class ShaderHelper {
	
    private static final String TAG = "ShaderHelper";

    /**
     * 加載並編譯頂點著色器,返回得到的opengl id
     * @param shaderCode
     * @return
     */
    public static int compileVertexShader(String shaderCode) {
        return compileShader(GL_VERTEX_SHADER, shaderCode);
    }

    /**
     * 加載並編譯片段著色器,返回opengl id
     * @param shaderCode
     * @return
     */
    public static int compileFragmentShader(String shaderCode) {
        return compileShader(GL_FRAGMENT_SHADER, shaderCode);
    }

    /**
     * 加載並編譯著色器,返回opengl id
     * @param type
     * @param shaderCode
     * @return
     */
    private static int compileShader(int type, String shaderCode) {
        // 建立新的著色器對象
        final int shaderObjectId = glCreateShader(type);

        if (shaderObjectId == 0) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "不能創建新的著色器.");
            }

            return 0;
        }

        // 傳遞著色器資源代碼.
        glShaderSource(shaderObjectId, shaderCode);

        //編譯著色器
        glCompileShader(shaderObjectId);

        // 獲取編譯的狀態
        final int[] compileStatus = new int[1];
        glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS,
            compileStatus, 0);

        if (LoggerConfig.ON) {
        	//打印log
            Log.v(TAG, "代碼編譯結果:" + "\n" + shaderCode
                + "\n:" + glGetShaderInfoLog(shaderObjectId));
        }

        // 確認編譯的狀態
        if (compileStatus[0] == 0) {
            // 如果編譯失敗,則刪除該對象
            glDeleteShader(shaderObjectId);

            if (LoggerConfig.ON) {
                Log.w(TAG, "編譯失敗!.");
            }

            return 0;
        }

        // 返回著色器的opengl id
        return shaderObjectId;
    }

    /**
     * 鏈接頂點著色器和片段著色器成一個program
     * 並返回這個pragram的opengl id
     * @param vertexShaderId
     * @param fragmentShaderId
     * @return
     */
    public static int linkProgram(int vertexShaderId, int fragmentShaderId) {

        // 新建一個program對象
        final int programObjectId = glCreateProgram();

        if (programObjectId == 0) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "不能新建一個 program");
            }

            return 0;
        }

        // Attach the vertex shader to the program.
        glAttachShader(programObjectId, vertexShaderId);

        // Attach the fragment shader to the program.
        glAttachShader(programObjectId, fragmentShaderId);

        // 將兩個著色器連接成一個program
        glLinkProgram(programObjectId);

        // 獲取連接狀態
        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));
        }

        // 驗證連接狀態
        if (linkStatus[0] == 0) {
            // If it failed, delete the program object.
            glDeleteProgram(programObjectId);

            if (LoggerConfig.ON) {
                Log.w(TAG, "連接 program 失敗!.");
            }

            return 0;
        }

        // Return the program object ID.
        return programObjectId;
    }

    /**
     * Validates an OpenGL program. Should only be called when developing the
     * application.
     */
    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;
    }

    /**
     * /**
     * 編譯,連接 ,返回 program 的 ID
     * @param vertexShaderSource
     * @param fragmentShaderSource
     * @return
     */
    public static int buildProgram(String vertexShaderSource,
        String fragmentShaderSource) {
        int program;

        // Compile the shaders.
        int vertexShader = compileVertexShader(vertexShaderSource);
        int fragmentShader = compileFragmentShader(fragmentShaderSource);

        // Link them into a shader program.
        program = linkProgram(vertexShader, fragmentShader);

        if (LoggerConfig.ON) {
            validateProgram(program);
        }
        return program;
    }
}

上面整個過程如圖所示:     \   以後我們就可以很方便的一直使用該工具類來加載編譯著色器了。只需要調用buildProgram,傳入兩個著色器的String文本就可以獲得program的id是不是很方便。 我們似乎遺忘了什麼,對的我們的兩個著色器的文本內容該怎麼活的呢,我們再在該目錄下定義一個工具類TextResourceReader類來獲得文本的內容,返回String類型 代碼如下(TextResourceReader.java):  
package com.cumt.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import android.content.Context;
import android.content.res.Resources;

public class TextResourceReader {
    /**
     * Reads in text from a resource file and returns a String containing the
     * text.
     */
    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 nextLine;

            while ((nextLine = bufferedReader.readLine()) != null) {
                body.append(nextLine);
                body.append('\n');
            }
        } catch (IOException e) {
            throw new RuntimeException(
                "Could not open resource: " + resourceId, e);
        } catch (Resources.NotFoundException nfe) {
            throw new RuntimeException("Resource not found: " + resourceId, nfe);
        }

        return body.toString();
    }
}

OK ,接下來就讓我們來使用者兩個工具類獲得program的id吧,先回到 前面定義的Triangle 類: 我們來定義一個私有方法getProgram 並在構造函數中調用 ,此時代碼如下 (Triangle.java):  
package com.cumt.shape;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import com.cumt.openglestwo_test_one.R;
import com.cumt.utils.ShaderHelper;
import com.cumt.utils.TextResourceReader;
import android.content.Context;
import android.opengl.GLES20;

public class Triangle {
	
	private Context context;
	
	private static final int BYTES_PER_FLOAT = 4;
	private FloatBuffer vertexBuffer;
	
	// 數組中每個頂點的坐標數
    static final int COORDS_PER_VERTEX = 2;
	// 每個頂點的坐標數  						X ,  Y 
    static float triangleCoords[] = { 0.0f,  0.5f ,   // top
                                     -0.5f, -0.5f ,   // bottom left
                                      0.5f, -0.5f };   // bottom right
    
    private int program;
    
    public Triangle(Context context){
    	this.context = context;
    	
    	vertexBuffer = ByteBuffer
    			.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
    			.order(ByteOrder.nativeOrder())
    			.asFloatBuffer();
    	// 把坐標們加入FloatBuffer中
        vertexBuffer.put(triangleCoords);
        // 設置buffer,從第一個坐標開始讀
        vertexBuffer.position(0);
        
        getProgram();
        
    }
    
    //獲取program的id
    private void getProgram(){
    	//獲取頂點著色器文本
    	String vertexShaderSource = TextResourceReader
				.readTextFileFromResource(context, R.raw.simple_vertex_shader);
    	//獲取片段著色器文本
		String fragmentShaderSource = TextResourceReader
				.readTextFileFromResource(context, R.raw.simple_fragment_shader);
		//獲取program的id
		program = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);
		GLES20.glUseProgram(program);
    }
}
至此我們已經勝利在望了,接下來思考一下,我們應該做哪些工作?顯然我們需要將定義的數據傳入著色器中來使用。詳細的步驟見下面的代碼(Triangle.java):  
package com.cumt.shape;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import com.cumt.openglestwo_test_one.R;
import com.cumt.utils.ShaderHelper;
import com.cumt.utils.TextResourceReader;
import android.content.Context;
import android.opengl.GLES20;

public class Triangle {
	
	private Context context;
	
	private static final int BYTES_PER_FLOAT = 4;
	private FloatBuffer vertexBuffer;
	
	//---------第四步:定義坐標元素的個數,這裡有三個頂點
	private static final int POSITION_COMPONENT_COUNT = 3;
	
	// 數組中每個頂點的坐標數
    static final int COORDS_PER_VERTEX = 2;
	// 每個頂點的坐標數  						X ,  Y 
    static float triangleCoords[] = { 0.0f,  0.5f ,   // top
                                     -0.5f, -0.5f ,   // bottom left
                                      0.5f, -0.5f };   // bottom right
    
    private int program;
    
    
    //------------第一步 : 定義兩個標簽,分別於著色器代碼中的變量名相同, 
    //------------第一個是頂點著色器的變量名,第二個是片段著色器的變量名
	private static final String A_POSITION = "a_Position";
	private static final String U_COLOR = "u_Color";
	
	//------------第二步: 定義兩個ID,我們就是通ID來實現數據的傳遞的,這個與前面
	//------------獲得program的ID的含義類似的
	private int uColorLocation;
	private int aPositionLocation;
	
    public Triangle(Context context){
    	this.context = context;
    	
    	vertexBuffer = ByteBuffer
    			.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
    			.order(ByteOrder.nativeOrder())
    			.asFloatBuffer();
    	// 把坐標們加入FloatBuffer中
        vertexBuffer.put(triangleCoords);
        // 設置buffer,從第一個坐標開始讀
        vertexBuffer.position(0);
        
        getProgram();
        
        //----------第三步: 獲取這兩個ID ,是通過前面定義的標簽獲得的
        uColorLocation = GLES20.glGetUniformLocation(program, U_COLOR);
		aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION);
		
		//---------第五步: 傳入數據
		GLES20.glVertexAttribPointer(aPositionLocation, COORDS_PER_VERTEX,
				GLES20.GL_FLOAT, false, 0, vertexBuffer);
		GLES20.glEnableVertexAttribArray(aPositionLocation);
        
    }
    
    //獲取program
    private void getProgram(){
    	//獲取頂點著色器文本
    	String vertexShaderSource = TextResourceReader
				.readTextFileFromResource(context, R.raw.simple_vertex_shader);
    	//獲取片段著色器文本
		String fragmentShaderSource = TextResourceReader
				.readTextFileFromResource(context, R.raw.simple_fragment_shader);
		//獲取program的id
		program = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);
		GLES20.glUseProgram(program);
    }
    
    //----------第七步:繪制
    public void draw(){
    	GLES20.glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);		
		GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);
    }
}

  最後我們只需要在我們前面定義的MyRender中使用即可,此時MyRender代碼(這裡在構造函數中引入了Context上下文環境,因為需要我們的Triangle對象的構造函數需要Context), 如下(MyRender.java ):  
package com.cumt.render;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import com.cumt.shape.Triangle;

import android.content.Context;
import android.opengl.GLSurfaceView.Renderer;
import android.util.Log;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glViewport;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;

public class MyRender implements Renderer {
	
	private Context context;
	
	public MyRender(Context context){
		this.context = context;
	}
	
	//定義三角形對象
	Triangle triangle;
	
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		Log.w("MyRender","onSurfaceCreated");
		// TODO Auto-generated method stub
		//First:設置清空屏幕用的顏色,前三個參數對應紅綠藍,最後一個對應alpha
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		triangle = new Triangle(context);
	}

	public void onSurfaceChanged(GL10 gl, int width, int height) {
		Log.w("MyRender","onSurfaceChanged");
		// TODO Auto-generated method stub
		//Second:設置視口尺寸,即告訴opengl可以用來渲染的surface大小
		glViewport(0,0,width,height);
	}

	public void onDrawFrame(GL10 gl) {
		Log.w("MyRender","onDrawFrame");
		// TODO Auto-generated method stub
		//Third:清空屏幕,擦除屏幕上所有的顏色,並用之前glClearColor定義的顏色填充整個屏幕
		glClear(GL_COLOR_BUFFER_BIT);
		//繪制三角形
		triangle.draw();
	}
}

  運行結果如下:   \    
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved