Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> OpenglES2.0 for Android:紋理映射

OpenglES2.0 for Android:紋理映射

編輯:關於Android編程

OpenglES2.0 for Android:紋理映射

前言

  紋理映射又叫做紋理貼圖,是將紋理空間中的紋理像素映射到屏幕空間中的像素的過程。就是把一幅圖像貼到三維物體的表面上來增強真實感, 可以和光照計算、圖像混合等技術結合起來形成許多非常漂亮的效果 (百度百科)。簡單來說,紋理就是一個圖形或者照片,我們可以將它們 加載到Opengl中用以美化我們繪制的物體。  

前期准備

  我們現在准備實現這樣一個功能:將一張圖片貼到一個正方形中 。我們在以前畫矩形的那節代碼的基礎上進行實現紋理貼圖。這裡我們新建一個項目 OpenglESRectangle ,然後將畫矩形的相關代碼copy過來~~,然後還記得我們繪制圓變成橢圓的問題嗎,我們在這裡給矩形也加入正交投影,以便來繪制 一個正方形。此時目錄結構如下 :   \\   因為加入了正交投影比起原先繪制矩形那一節的代碼有些變動,發生變動的文件有 MyRender.java , Square.java ,simple_vertex_shader.glsl ,此時這三個文件代碼如下:  
package com.cumt.shape;

import java.nio.ByteBuffer;  
import java.nio.ByteOrder;  
import java.nio.FloatBuffer;  
import com.cumt.openglesrectangle.R;
import com.cumt.utils.ShaderHelper;
import com.cumt.utils.TextResourceReader;
import android.content.Context;  
import android.opengl.GLES20;  
import android.opengl.Matrix;
  
public class Square {  
      
    private Context context;  
  
    //float類型的字節數  
    private static final int BYTES_PER_FLOAT = 4;  
    // 數組中每個頂點的坐標數  
    static final int COORDS_PER_VERTEX = 2;  
      
    /*------------------第一步: 修改頂點數據-------------------------*/  
    //矩形頂點坐標  
    static float squareCoords[] = { //以三角形扇的形式繪制
    	-0.5f,  0.5f ,   // top left  
        0.5f,  0.5f  , // top right  
        0.5f, -0.5f  , // bottom right  
       -0.5f, -0.5f  };  // bottom left  
      
    private FloatBuffer vertexBuffer;  
      
    //------------第一個是頂點著色器的變量名,第二個是片段著色器的變量名  
    private static final String A_POSITION = "a_Position";  
    private static final String U_COLOR = "u_Color";  
    private static final String U_MATRIX = "u_Matrix";
    //------------獲得program的ID的含義類似的  
    private int uColorLocation;  
    private int aPositionLocation;  
    private int uMatrixLocation;  
    private int program;//保存program的id  
      
    /*------------------第二步: 修改頂點個數-------------------------*/  
    private static final int POSITION_COMPONENT_COUNT = 4;  
          
    float[] projectionMatrix = new float[16];//變換矩陣
      
    public Square(Context context) {  
        this.context = context;  
        vertexBuffer = ByteBuffer  
                .allocateDirect(squareCoords.length * BYTES_PER_FLOAT)  
                .order(ByteOrder.nativeOrder())  
                .asFloatBuffer();  
        // 把坐標們加入FloatBuffer中  
        vertexBuffer.put(squareCoords);  
        // 設置buffer,從第一個坐標開始讀  
        vertexBuffer.position(0);  
          
        getProgram();  
          
        uColorLocation = GLES20.glGetUniformLocation(program, U_COLOR);  
        aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION);  
        uMatrixLocation = GLES20.glGetUniformLocation(program, U_MATRIX);
        
        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 projectionMatrix(int width,int height){  
        final float aspectRatio = width > height ?  
                (float) width / (float) height :  
                (float) height / (float) width;  
        if(width > height){  
            Matrix.orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);  
        }else{  
            Matrix.orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);  
        }  
    }  
    
    //以GL_LINE_LOOP方式繪制  
    public void draw(){  
    	GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
        GLES20.glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);       
        /*------------------第三步: 修改繪制方式-------------------------*/  
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, POSITION_COMPONENT_COUNT);  
    }  
}
 
package com.cumt.render;
import javax.microedition.khronos.egl.EGLConfig;  
import javax.microedition.khronos.opengles.GL10;  
  
import com.cumt.shape.Square;  
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;  
    }  
      
    //定義矩形對象  
    Square square;  
      
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {  
        Log.w("MyRender","onSurfaceCreated");  
        // TODO Auto-generated method stub  
        //First:設置清空屏幕用的顏色,前三個參數對應紅綠藍,最後一個對應alpha  
        glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        square = new Square(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);  
        square.projectionMatrix(width, height);
    }  
  
    public void onDrawFrame(GL10 gl) {  
        Log.w("MyRender","onDrawFrame");  
        // TODO Auto-generated method stub  
        //Third:清空屏幕,擦除屏幕上所有的顏色,並用之前glClearColor定義的顏色填充整個屏幕  
        glClear(GL_COLOR_BUFFER_BIT);  
        square.draw();  
    }  
}

//simple_vertex_shader.glsl
uniform mat4 u_Matrix;
attribute vec4 a_Position;   
  		
void main()                    
{                              
    gl_Position = u_Matrix * a_Position;
}   

此時運行效果如下 :  

  

\
我們要貼的圖如下 (512 * 512 ) 要注意opengl es2.0中紋理不必須是正方形,但每個緯度應該是2的冪 ):  

  

\
  我們要將該圖片貼到上面的矩形中  

正式開始

  下面讓我們正式開始做紋理貼圖 ,看下過程 :  

  

\
   

紋理工具類

 
package com.cumt.utils;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Log;

public class TextureHelper {
	
	public static final String TAG = "TextureHelper";
	
	public static int loadTexture(Context context,int resourceId){
		/*
		 * 第一步 : 創建紋理對象
		 */
		final int[] textureObjectId = new int[1];//用於存儲返回的紋理對象ID
		GLES20.glGenTextures(1,textureObjectId, 0);
		if(textureObjectId[0] == 0){//若返回為0,,則創建失敗
			if(LoggerConfig.ON){
				Log.w(TAG,"Could not generate a new Opengl texture object");
			}
			return 0;
		}
		/*
		 * 第二步: 加載位圖數據並與紋理綁定
		 */
		final BitmapFactory.Options options = new BitmapFactory.Options();
		options.inScaled = false;//Opengl需要非壓縮形式的原始數據
		final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),resourceId, options);
		if(bitmap == null){
			if(LoggerConfig.ON){
				Log.w(TAG,"ResourceId:"+resourceId+"could not be decoded");
			}
			GLES20.glDeleteTextures(1, textureObjectId, 0);
			return 0;
		}
		GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectId[0]);//通過紋理ID進行綁定
		/*
		 * 第三步: 設置紋理過濾
		 */
		//設置縮小時為三線性過濾
		GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR_MIPMAP_LINEAR);
		//設置放大時為雙線性過濾
		GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
		/*
		 * 第四步: 加載紋理到Opengl並返回ID
		 */
		GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
		bitmap.recycle();
		GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
		return textureObjectId[0];
	}
}

這裡要說下紋理過濾 的內容,紋理過濾應用通常有兩種情況,一種是GL_TEXTURE_MIN_FILTER ,即紋理被縮小時,比如一個512 x 512的紋理貼到一個平行於xy平面的正方形上,最後該正方形在屏幕上只占256 x 256的像素矩陣,這種情況下一個象素對應著多個紋理單元。另一種是GL_TEXTURE_MAG_FILTER,即紋理被放大時。下面簡要說下Opengl的紋理過濾模式 , Opengl紋理過濾模式如下 : ---------------------------------------------------------------------------------------------------------------------------------------------------------------- GL_NEAREST 最近鄰過濾 GL_NEAREST_MIPMAP_NEAREST 使用MIP貼圖的最近鄰過濾 GL_NEAREST_MIPMAP_LINEAR 使用MIP貼圖級別之間插值的最近鄰過濾 GL_LINEAR 雙線性過濾 GL_LINEAR_MIPMAP_NEAREST 使用MIP貼圖的雙線性過濾 GL_LINEAR_MIPMAP_LINEAR 三線性過濾 (使用MIP貼圖級別之間插值的雙線性過濾 ) --------------------------------------------------------------------------------------------------------------------------------------------------------------- 在縮小和放大情況下,Opengl支持不同的過濾模式,如下所示: --------------------------------------------------------------------------------------------------------------------------------------------------------------- 縮小情況: GL_NEAREST GL_NEAREST_MIPMAP_NEAREST GL_NEAREST_MIPMAP_LINEAR GL_LINEAR GL_LINEAR_MIPMAP_NEAREST GL_LINEAR_MIPMAP_LINEAR ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 放大 情況 : GL_NEAREST GL_LINEAR ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 最近鄰過濾:這個比較簡單,每個像素的紋理坐標,並不是剛好對應一個采樣點時,按照最接近的采樣點進行采樣。當放大紋理時,鋸齒會比較明顯, 每個紋理單元都顯示為一個小方塊。當縮小時,會丟失許多細節。 雙線性過濾:使用雙線性插值平滑像素之間的過渡。放大時,鋸齒看起來會比最近鄰過濾少許多,看起來更加平滑。 MIP貼圖 :當縮小到一定程度時,使用雙線性過濾會失去太多細節,還可能引起噪聲以及物體移動過程中的閃爍,為了克服這些問題就有了MIP貼圖技術。 關於MIP貼圖 ,維基上有更詳細解釋 :https://en.wikipedia.org/wiki/Mipmap 三線性過濾 :用於消除每個MIP貼圖級別之間的過渡,得到一個更為平滑的圖像。  

新的著色器

  在創建新的著色器之前,我們先明確一下Opengl的二維紋理坐標  

  

\
對於一個二維的紋理,它有著自己的坐標空間,如上圖所示。其有兩個維度,S 與 T ,范圍都是 0 到 1 。   頂點著色器代碼如下 :
//texture_vertex_shader.glsl
uniform mat4 u_Matrix;
attribute vec4 a_Position;  
attribute vec2 a_TextureCoordinates;

varying vec2 v_TextureCoordinates;

void main()                    
{                            
    v_TextureCoordinates = a_TextureCoordinates;	  	  
    gl_Position = u_Matrix * a_Position;    
} 
其中a_TextureCoordinates 用於接收紋理的坐標數據,v_TextureCoordinates用於將紋理數據傳遞給片段著色器,因為紋理有兩個分量 S與 T 所以使用vec2類型。   片段著色器如下:
//texture_fragment_shader.glsl
precision mediump float;       	 				
uniform sampler2D u_TextureUnit;      	 								
varying vec2 v_TextureCoordinates;      	   								
  
void main()                    		
{                              	
    gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);                           		
}

texture2D是著色器語言內置的紋理采樣函數,用於根據指定的紋理坐標就行紋理采樣,返回值類型為vec4 。    

紋理坐標

 
//矩形頂點坐標  與 紋理坐標
    static float squareCoords[] = { //以三角形扇的形式繪制
    	//x     y       s   t
    	-0.5f,  0.5f ,  0 , 0 , // top left  
        0.5f,  0.5f  ,  1 , 0 ,// top right  
        0.5f, -0.5f  ,  1 , 1 ,// bottom right  
       -0.5f, -0.5f  ,  0 , 1};  // bottom left  
  我們繪制的矩形的左上角對應著紋理的 (0,0 ) 大家注意這個映射關系,也就是我們拿出一張圖其左上角的紋理坐標為 (0,0)而不是 (0,1)。 前面我們已經完成了紋理工具類,下面只需要使用它,然互將數據傳入著色器。此時Square類代碼如下 (Square.java):  
package com.cumt.shape;

import static android.opengl.GLES20.GL_TEXTURE0;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glUniform1i;
import java.nio.ByteBuffer;  
import java.nio.ByteOrder;  
import java.nio.FloatBuffer;  
import com.cumt.openglesrectangle.R;
import com.cumt.utils.ShaderHelper;
import com.cumt.utils.TextResourceReader;
import com.cumt.utils.TextureHelper;
import android.content.Context;  
import android.opengl.GLES20;  
import android.opengl.Matrix;
  
public class Square {  
      
    private Context context;  
  
    //float類型的字節數  
    private static final int BYTES_PER_FLOAT = 4;  
    // 數組中每個頂點的坐標數  
    static final int COORDS_PER_VERTEX = 2;  
      
    /*------------------修改頂點數據 ,加入頂點對應的紋理坐標-------------------------*/  
    //矩形頂點坐標  與 紋理坐標
    static float squareCoords[] = { //以三角形扇的形式繪制
    	//x     y       s   t
    	-0.5f,  0.5f ,  0 , 0 , // top left  
        0.5f,  0.5f  ,  1 , 0 ,// top right  
        0.5f, -0.5f  ,  1 , 1 ,// bottom right  
       -0.5f, -0.5f  ,  0 , 1};  // bottom left  
      
    private FloatBuffer vertexBuffer;  
    private static final int VERTEX_COUNTS = 4;//頂點坐標數
    private static final int POSITION_COMPONENT_COUNT = 2;  //一個頂點坐標含有的元素個數
    private static final int TEXTURE_COORDIANTES_COMPONENT_COUNT = 2; //一個紋理坐標含有的元素個數
    //因為我們的頂點數據和紋理坐標數據放在了一起 ,所以在使用glVertexAttribPointer等函數時,其中的stride參數就需要傳入了,
    //用於高速著色器應該如何讀取坐標值 ,比如這裡我們的著色器讀取坐標時,設置從位置 0開始讀,讀取x , y後就會跳過 s t 接著讀取 x y
    //這就是通過傳入stride參數實現的
	private static final int STRIDE = (POSITION_COMPONENT_COUNT + TEXTURE_COORDIANTES_COMPONENT_COUNT)
			* BYTES_PER_FLOAT;
    //------------第一個是頂點著色器的變量名,第二個是片段著色器的變量名  
    private static final String A_POSITION = "a_Position";  
    private static final String U_MATRIX = "u_Matrix";
    private static final String A_TEXTURE_COORDINATES = "a_TextureCoordinates";//紋理
	private static final String U_TEXTURE_UNIT = "u_TextureUnit";//紋理
	
    private int aPositionLocation;  
    private int uMatrixLocation;  
    private int uTextureUnitLocation;
    private int aTextureCoordinates;
    private int program;//保存program的id  
    private int texture;
    
    float[] projectionMatrix = new float[16];//變換矩陣
      
    public Square(Context context) {  
        this.context = context;  
        vertexBuffer = ByteBuffer  
                .allocateDirect(squareCoords.length * BYTES_PER_FLOAT)  
                .order(ByteOrder.nativeOrder())  
                .asFloatBuffer();  
        // 把坐標們加入FloatBuffer中  
        vertexBuffer.put(squareCoords);  
        // 設置buffer,從第一個坐標開始讀  
        vertexBuffer.position(0);  
          
        getProgram();  
          
        aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION);  
        uMatrixLocation = GLES20.glGetUniformLocation(program, U_MATRIX);
        aTextureCoordinates = GLES20.glGetAttribLocation(program, A_TEXTURE_COORDINATES);
		uTextureUnitLocation = GLES20.glGetAttribLocation(program, U_TEXTURE_UNIT);
		texture = TextureHelper.loadTexture(context, R.drawable.umei);
		// Set the active texture unit to texture unit 0.
        glActiveTexture(GL_TEXTURE0);
        // Bind the texture to this unit.
        glBindTexture(GL_TEXTURE_2D, texture);
        // Tell the texture uniform sampler to use this texture in the shader by
        // telling it to read from texture unit 0.
        glUniform1i(uTextureUnitLocation, 0);
        //傳入頂點坐標和紋理坐標
        GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT,
				GLES20.GL_FLOAT, false, STRIDE, vertexBuffer);
		GLES20.glEnableVertexAttribArray(aPositionLocation);
		//設置從第二個元素開始讀取,因為從第二個元素開始才是紋理坐標
		vertexBuffer.position(POSITION_COMPONENT_COUNT);
		GLES20.glVertexAttribPointer(aTextureCoordinates, TEXTURE_COORDIANTES_COMPONENT_COUNT,
				GLES20.GL_FLOAT, false, STRIDE, vertexBuffer);
		GLES20.glEnableVertexAttribArray(aTextureCoordinates);
    }  
      
    //獲取program  
    private void getProgram(){  
        //獲取頂點著色器文本  
        String vertexShaderSource = TextResourceReader  
                .readTextFileFromResource(context, R.raw.texture_vertex_shader);  
        //獲取片段著色器文本  
        String fragmentShaderSource = TextResourceReader  
                .readTextFileFromResource(context, R.raw.texture_fragment_shader);  
        //獲取program的id  
        program = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);  
        GLES20.glUseProgram(program);  
    }  
    
    //設置正交投影矩陣
    public void projectionMatrix(int width,int height){  
        final float aspectRatio = width > height ?  
                (float) width / (float) height :  
                (float) height / (float) width;  
        if(width > height){  
            Matrix.orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);  
        }else{  
            Matrix.orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);  
        }  
    }  
    
    //以GL_LINE_LOOP方式繪制  
    public void draw(){  
    	GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, VERTEX_COUNTS);  
    }  
}

為了方便觀察,將MyRender類onSurfaceCreated方法中設置的顏色更改為glClearColor(0.5f,0.5f,0.5f, 1.0f);   運行一下 :  

  

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