從零開始學OpenGLES開發——第二章

從零開始學OpenGLES開發——第二章


第二章 OpenGLES1.0的紋理貼圖


將上一章的代碼精簡一下,只畫留一個三角形的繪製。
數據初始化代碼:
	private FloatBuffer vertexBuffer = null ;
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		
		float[] vertexs = new float[]{
				-5.0f,   5.0f, -30.0f, 
				 5.0f,  -5.0f, -30.0f, 
				 5.0f,   5.0f, -30.0f, 
		};
		vertexBuffer = ByteBuffer.allocateDirect(vertexs.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
		vertexBuffer.put(vertexs);
		vertexBuffer.position(0);
	}


繪製的代碼:
	public void onDrawFrame(GL10 gl) {
		GLES10.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 清空場景爲黑色。
		GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT | GLES10.GL_DEPTH_BUFFER_BIT);// 清空相關緩存。


		GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
		
		GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, vertexBuffer);
		GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, 3);
		
		GLES10.glFlush();
	}


關於OpenGLES1.0的貼圖,需要講幾點:


1、紋理貼圖是以紋理座標方式告訴OpenGL怎麼貼的。紋理座標範圍是0-1。
2、一個頂點對應一個紋理座標,紋理座標只有兩個float值。紋理座標對應的是圖片上的範圍。而圖片的左上角的紋理座標是(0,0),右下角是(1,1)。
3、OpenGLES對於紋理的圖片也有要求,官方推薦的紋理的圖片的長和寬都必須是2的次冪倍數,比如64,128,256這種,官方還要求紋理的尺寸長和寬都不能小於64。
4、對於圖片的長寬的長度是2的次冪這個限制,其實具體支持不支持是不同的顯卡決定的,比如我的垃圾 ME525+ 就不支持不是2的次冪倍的紋理,同時的小米就支持任意尺寸的。


說完了,正式開始教大家怎麼貼圖了。


首先肯定要先整一張圖到工程裏面嘛,整一個128x128的jpg圖片,放到android工程下的res/raw目錄下,我這裏的圖片叫image_test.jpg




然後在onSurfaceCreated的最後,加上初始化一個紋理的代碼:

private FloatBuffer vertexBuffer = null ;
private int			textureId	 = 0 ;
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		
	float[] vertexs = new float[]{
				-5.0f,   5.0f, -30.0f, 
				 5.0f,  -5.0f, -30.0f, 
				 5.0f,   5.0f, -30.0f, 
	};
	vertexBuffer = ByteBuffer.allocateDirect(vertexs.length * 4)
						.order(ByteOrder.nativeOrder()).asFloatBuffer();
	vertexBuffer.put(vertexs);
	vertexBuffer.position(0);
		
	int[] tempArray = new int[1];
	GLES10.glGenTextures(1, tempArray, 0);
	GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, tempArray[0]);
		
	Bitmap image = BitmapFactory.decodeStream(getResources().openRawResource(R.raw.image_test)) ;
	GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, image, 0);
	image.recycle();
		
	GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_NEAREST);
	GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_NEAREST);
		
	textureId = tempArray[0] ;
}


以上代碼解釋如下:(上一章解釋過的不繼續解釋了)
glGenTextures 創建n個紋理對象(OpenGL的對象),第一個參數是1,所以我只需要創建一個紋理對象,並把得到的紋理對象的句柄放入數組。


glBindTexture 這句話的含義,其實詳細說起來需要引入一個更大的體系(那就是多紋理貼圖,以及1D,2D,3D紋理的類型)
簡單說一下吧:OpenGL的紋理分幾個大類,1D,2D,3D。但是OpenGLES1.0裏面默認只支持2D(也就是平面圖像)。在標準的OpenGL版本里面,還有GL_TEXTURE_1D,GL_TEXTURE_3D等類型。而對於紋理的操作,默認一種類型一次只能操作一個(OpenGL的狀態和當前操作對象切換的習慣設計)。glBindTexture的意思就是說,把當前這個我創建的這個紋理,綁定到GL_TEXTURE_2D的類型上,那麼後面凡是對GL_TEXTURE_2D這個類型的紋理進行操作,就是操作的我創建的這個紋理。


後面的代碼就是加載我那個圖片文件,得到Bitmap,然後調用GLUtils.texImage2D將這個圖片,設置到當前活動的2D紋理,注意它第一個參數GLES10.GL_TEXTURE_2D(就像我前面說的,實際操作的是這個類型下,當前活動的對象)。


圖像被設置到紋理對象之後,紋理對象保存的位置是服務端,就相當於圖像已經被存儲到顯卡中了,所以這個Bitmap可以立即recycle。


後面兩句代碼glTexParameterx,設置當前操作的這個紋理的貼圖操作,主要講的是當一個文理座標具體在查找像素點的時候,如果發現它位於兩個像素之間時,怎麼取值的問題,GLES10.GL_NEAREST 表示查找最近的像素點。比如我有4個像素的圖片
而紋理座標最後映射的時候,標準的紋理座標只能取0.0 0.25 0.5 0.75等幾個值,如果介於這幾個之間怎麼辦呢,所以需要設置參數告訴OpenGL這種時候怎麼取
GLES10.GL_NEAREST告訴OpenGL,取最近的1個像素點。
GLES10.GL_LINEAR 告訴OpenGL,取最近的幾個像素點求平均值。還有其他的參數,可以查看更加詳細的資料。
實際畫出來的效果是 GLES10.GL_NEAREST 會有鋸齒感,圖像更加銳利;GLES10.GL_LINEAR會有平滑感,但圖像相對模糊。


最後只有一個textureId,即紋理對象句柄值,作爲了成員變量,會在具體繪製的時候使用它。

除了紋理,我們還需要構建文理座標數組,然後紋理圖像才能根據座標貼到模型上。

還是在onSurfaceCreated代碼後面加上紋理座標生成的代碼:

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		
		float[] vertexs = new float[]{
				-5.0f,   5.0f, -30.0f, 
				 5.0f,  -5.0f, -30.0f, 
				 5.0f,   5.0f, -30.0f, 
		};
		vertexBuffer = ByteBuffer.allocateDirect(vertexs.length * 4)
						.order(ByteOrder.nativeOrder()).asFloatBuffer();
		vertexBuffer.put(vertexs);
		vertexBuffer.position(0);
		
		int[] tempArray = new int[1];
		GLES10.glGenTextures(1, tempArray, 0);
		GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, tempArray[0]);
		
		Bitmap image = BitmapFactory.decodeStream(getResources().openRawResource(R.raw.image_test)) ;
		GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, image, 0);
		image.recycle();
		
		GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_NEAREST);
		GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_NEAREST);
		
		textureId = tempArray[0] ;
		
		
		float[] texcoods = new float[]{
				0,	0,
				1,	1,
				1,	0,
		};
		txtCoodsBuffer = ByteBuffer.allocateDirect(texcoods.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
		txtCoodsBuffer.put(texcoods);
		txtCoodsBuffer.position(0);


		GLES10.glEnable(GLES10.GL_TEXTURE_2D);
	}



三個頂點,對應三個座標,最後一句是真正開啓2D紋理貼圖功能。


繪製的代碼改成如下形式:

	public void onDrawFrame(GL10 gl) {
		GLES10.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 清空場景爲黑色。
		GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT | GLES10.GL_DEPTH_BUFFER_BIT);// 清空相關緩存。


		GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
		GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, vertexBuffer);
		
		GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
		GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, txtCoodsBuffer);
		
		GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, 3);
		
		GLES10.glFlush();
	}


glTexCoordPointer 和 glVertexPointer類似,只是前者一個頂點的座標數據個數是兩個。


運行吧!!騷年,可以看到半個中國心了噢。



繼續講一點額外的事項:
這裏面只有一個紋理,一個三角形。如果是多個紋理對象,多個三角形,分別不同的貼圖的話
那麼前面初始化的過程差不多,只不過會產生多個紋理的句柄

而後面在繪製的時候,就是在glDrawArrays之前,調用一次glBindTexture即可,激活某一個紋理,那麼繪製的時候就是用的當前的紋理對象。一個紋理的話,默認就始終是它,所以不需要重複glBindTexture了。


下一章將繼續更新,關於光照材質等技術點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章