android平臺下OpenGL ES 3.0實現2D紋理貼圖顯示bitmap
OpenGL ES 3.0學習實踐
- android平臺下OpenGL ES 3.0從零開始
- android平臺下OpenGL ES 3.0繪製純色背景
- android平臺下OpenGL ES 3.0繪製圓點、直線和三角形
- android平臺下OpenGL ES 3.0繪製彩色三角形
- android平臺下OpenGL ES 3.0從矩形中看矩陣和正交投影
- android平臺下OpenGL ES 3.0著色語言基礎知識(上)
- android平臺下OpenGL ES 3.0著色語言基礎知識(下)
- android平臺下OpenGL ES 3.0例項詳解頂點屬性、頂點陣列
- android平臺下OpenGL ES 3.0例項詳解頂點緩衝區物件(VBO)和頂點陣列物件(VAO)
- android平臺下OpenGL ES 3.0繪製立方體的幾種方式
目錄
2D紋理簡介
2D紋理是OpenGL ES中最基本和常用的紋理形式。2D紋理本質上其實:是一個影象資料的二維陣列
。一個紋理的單獨資料元素稱作"紋素(texel)"
。用2D紋理渲染時,紋理座標用作紋理影象中的索引。2D紋理的紋理座標用一對2D座標(s,t)
(u,v)
座標。
紋理影象的左下角由對座標(0.0, 0.0)
指定,右上角由對座標(1.0,1.0)
指定。在[0.0,1.0]
區間之外的座標是允許的,在該區間之外的紋理讀取行為由紋理包裝模式定義。
OpenGL 2D紋理座標:
通過指定紋理座標,可以對映到紋素。
例如一個256x256大小的二維紋理,座標(0.5,1.0)
對應的紋素即是(256x0.5 = 128, 256x1.0 = 256)
。
紋理對映時只需要為物體的頂點指定紋理座標即可,其餘部分由片元著色器插值完成。
模型變換和紋理座標
模型變換,就是對物體進行縮放、旋轉、平移等操作,後面會著重介紹。當對物體進行這些操作時,頂點對應的紋理座標不會進行改變,通過插值後,物體的紋理也像緊跟著物體發生了變化一樣。
經過旋轉等變換後,物體和對應的紋理座標如下圖所示,可以看出上面圖中紋理部分的房子也跟著發生了旋轉。
紋理座標和OpenGL座標
由於對一個OpenGL紋理來說,它沒有內在的方向性,因此我們可以使用不同的座標把它定向到任何我們喜歡的方向上,然而大多數計算機影象都有一個預設的方向,它們通常被規定為y軸向下
,y的值隨著向影象的底部移動而增加。
如果想用正確的方向觀看影象,那紋理座標就必須要考慮這點。
可以對照這個座標系檢視本例程式碼中定義的座標點:
灰色為OpenGL座標系中的頂點座標
紅色為紋理座標系中的紋理座標
紋理貼圖顯示bitmap實踐
還是以一個示例來實踐一下,基於之前的專案工程,新增一個新的工具類TextureUtils.java
:
/**
* @anchor: andy
* @date: 18-11-10
*/
public class TextureUtils {
private static final String TAG = "TextureUtils";
public static int loadTexture(Context context, int resourceId) {
final int[] textureIds = new int[1];
//建立一個紋理物件
GLES30.glGenTextures(1, textureIds, 0);
if (textureIds[0] == 0) {
Log.e(TAG, "Could not generate a new OpenGL textureId object.");
return 0;
}
final BitmapFactory.Options options = new BitmapFactory.Options();
//這裡需要載入原圖未經縮放的資料
options.inScaled = false;
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
if (bitmap == null) {
Log.e(TAG, "Resource ID " + resourceId + " could not be decoded.");
GLES30.glDeleteTextures(1, textureIds, 0);
return 0;
}
// 繫結紋理到OpenGL
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0]);
//設定預設的紋理過濾引數
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR_MIPMAP_LINEAR);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
// 載入bitmap到紋理中
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0);
// 生成MIP貼圖
GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D);
// 資料如果已經被載入進OpenGL,則可以回收該bitmap
bitmap.recycle();
// 取消繫結紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
return textureIds[0];
}
}
新建TextureRenderer.java
檔案:
/**
* @anchor: andy
* @date: 2018-11-09
* @description: 基於紋理貼圖顯示bitmap
*/
public class TextureRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "TextureRenderer";
private final FloatBuffer vertexBuffer, mTexVertexBuffer;
private final ShortBuffer mVertexIndexBuffer;
private int mProgram;
private int textureId;
/**
* 頂點座標
* (x,y,z)
*/
private float[] POSITION_VERTEX = new float[]{
0f, 0f, 0f, //頂點座標V0
1f, 1f, 0f, //頂點座標V1
-1f, 1f, 0f, //頂點座標V2
-1f, -1f, 0f, //頂點座標V3
1f, -1f, 0f //頂點座標V4
};
/**
* 紋理座標
* (s,t)
*/
private static final float[] TEX_VERTEX = {
0.5f, 0.5f, //紋理座標V0
1f, 0f, //紋理座標V1
0f, 0f, //紋理座標V2
0f, 1.0f, //紋理座標V3
1f, 1.0f //紋理座標V4
};
/**
* 索引
*/
private static final short[] VERTEX_INDEX = {
0, 1, 2, //V0,V1,V2 三個頂點組成一個三角形
0, 2, 3, //V0,V2,V3 三個頂點組成一個三角形
0, 3, 4, //V0,V3,V4 三個頂點組成一個三角形
0, 4, 1 //V0,V4,V1 三個頂點組成一個三角形
};
public TextureRenderer() {
//分配記憶體空間,每個浮點型佔4位元組空間
vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
//傳入指定的座標資料
vertexBuffer.put(POSITION_VERTEX);
vertexBuffer.position(0);
mTexVertexBuffer = ByteBuffer.allocateDirect(TEX_VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(TEX_VERTEX);
mTexVertexBuffer.position(0);
mVertexIndexBuffer = ByteBuffer.allocateDirect(VERTEX_INDEX.length * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(VERTEX_INDEX);
mVertexIndexBuffer.position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//設定背景顏色
GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
//編譯
final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_texture_shader));
final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_texture_shader));
//連結程式片段
mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
//載入紋理
textureId = TextureUtils.loadTexture(AppCore.getInstance().getContext(), R.drawable.main);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
//使用程式片段
GLES30.glUseProgram(mProgram);
//啟用頂點座標屬性
GLES30.glEnableVertexAttribArray(0);
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
//啟用紋理座標屬性
GLES30.glEnableVertexAttribArray(1);
GLES30.glVertexAttribPointer(1, 2, GLES30.GL_FLOAT, false, 0, mTexVertexBuffer);
//啟用紋理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
//繫結紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
// 繪製
GLES20.glDrawElements(GLES20.GL_TRIANGLES, VERTEX_INDEX.length, GLES20.GL_UNSIGNED_SHORT, mVertexIndexBuffer);
}
}
頂點著色器
#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec2 aTextureCoord;
//輸出紋理座標(s,t)
out vec2 vTexCoord;
void main() {
gl_Position = vPosition;
gl_PointSize = 10.0;
vTexCoord = aTextureCoord;
}
片段著色器
#version 300 es
precision mediump float;
uniform sampler2D uTextureUnit;
//接收剛才頂點著色器傳入的紋理座標(s,t)
in vec2 vTexCoord;
out vec4 vFragColor;
void main() {
vFragColor = texture(uTextureUnit,vTexCoord);
}
注意:在OpenGL ES 2.0
中這裡的方法是texture2D
紋理過濾的介紹
由於紋理座標與解析度無關,因此它們並不總是精確匹配畫素。當紋理影象拉伸超過其原始大小或尺寸縮小時,會發生這種情況。當發生這種情況時,OpenGL提供了各種方法來決定取樣顏色。此過程稱為過濾,針對上述情況,我們可以配置OpenGL使用一個紋理過濾器。
GL_NEAREST
:返回最接近座標的畫素。GL_LINEAR
:返回給定座標周圍4個畫素的加權平均值。GL_NEAREST_MIPMAP_NEAREST,GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR,GL_LINEAR_MIPMAP_LINEAR
:從MIP貼圖,而不是樣品。
最近和線性插值的區別:
雖然線性插值可以提供更平滑的結果,但它並不總是最理想的選擇。由於畫素化的外觀,最近鄰插值更適合想要模仿8點陣圖形的遊戲。
可以指定哪種插值應用於兩種不同的情況:縮小影象並向上縮放影象。這兩個案例由關鍵字GL_TEXTURE_MIN_FILTER和GL_TEXTURE_MAG_FILTER
。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
當然了,還有另一種過濾紋理的方法:mipmap
。Mipmap
是紋理的較小副本,已預先調整大小並進行過濾。推薦使用它們,因為它們可以帶來更高的質量和更高的效能。
glGenerateMipmap(GL_TEXTURE_2D);
要使用mipmap,請選擇四種mipmap過濾方法之一:
GL_NEAREST_MIPMAP_NEAREST
:使用最接近匹配紋理畫素大小的mipmap和最近鄰插值的樣本。GL_LINEAR_MIPMAP_NEAREST
:使用線性插值對最近的mipmap進行取樣。GL_NEAREST_MIPMAP_LINEAR
:使用與紋理畫素大小最匹配的兩個mipmap和最近鄰插值的樣本。GL_LINEAR_MIPMAP_LINEAR
:採用線性插值的樣本最接近兩個mipmap。
正交投影調整顯示效果
剛才也看到最終的顯示效果有種拉伸的感覺,現在使用正交投影來調整這個顯示效果,修改一下頂點著色器
,加入矩陣,可以參考之前的例子:android平臺下OpenGL ES 3.0從矩形中看矩陣和正交投影。
#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec2 aTextureCoord;
//矩陣
uniform mat4 u_Matrix;
out vec2 vTexCoord;
void main() {
gl_Position = u_Matrix * vPosition;
gl_PointSize = 10.0;
vTexCoord = aTextureCoord;
}
在onSurfaceChanged
方法中
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);
final float aspectRatio = width > height ?
(float) width / (float) height :
(float) height / (float) width;
if (width > height) {
//橫屏
Matrix.orthoM(mMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);
} else {
//豎屏
Matrix.orthoM(mMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
}
}
在onDrawFrame
回撥中修改如下:
@Override
public void onDrawFrame(GL10 gl) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
//使用程式片段
GLES30.glUseProgram(mProgram);
//正交投影矩陣
GLES30.glUniformMatrix4fv(uMatrixLocation, 1, false, mMatrix, 0);
......
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
//繫結紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
// 繪製
GLES20.glDrawElements(GLES20.GL_TRIANGLES, VERTEX_INDEX.length, GLES20.GL_UNSIGNED_SHORT, mVertexIndexBuffer);
}
顯示效果如下:
專案地址:opengles-texture
https://github.com/byhook/opengles4android
參考:
https://open.gl/textures
http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-5-a-textured-cube/
https://blog.piasy.com/2016/06/14/Open-gl-es-android-2-part-2/
《OpenGL ES 3.0 程式設計指南第2版》
《OpenGL ES應用開發實踐指南Android卷》