首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Android OpenGL ES 紋理映射實作:GLSurfaceView 與著色器管線

     編輯:Java 相關知識

架構概覽

Android OpenGL ES 2.0+ 使用可程式化管線。核心類別:

  • GLSurfaceView:管理 EGL 上下文、渲染執行緒、Surface 生命週期。
  • GLSurfaceView.Renderer 介面:onSurfaceCreated、onSurfaceChanged、onDrawFrame。
  • GL10 / GLES20 / GLES30:對應 OpenGL ES 版本的 Java 綁定。

完整實作範例 (ES 1.x 風格,相容性佳)

1. Renderer

package com.example.opengl;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class TextureRenderer implements GLSurfaceView.Renderer {
    private final Context context;
    private final Square square = new Square();

    public TextureRenderer(Context context) {
        this.context = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // 載入紋理
        square.loadGLTexture(gl, context);

        gl.glEnable(GL10.GL_TEXTURE_2D);      // 啟用 2D 紋理
        gl.glShadeModel(GL10.GL_SMOOTH);      // 平滑著色
        gl.glClearColor(0f, 0f, 0f, 0.5f);    // 黑色半透明背景
        gl.glClearDepthf(1f);                 // 深度緩衝初始值
        gl.glEnable(GL10.GL_DEPTH_TEST);      // 啟用深度測試
        gl.glDepthFunc(GL10.GL_LEQUAL);       // 深度函數
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); // 透視修正品質
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        if (height == 0) height = 1;
        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        GLU.gluPerspective(gl, 45f, (float) width / height, 0.1f, 100f);
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();
        gl.glTranslatef(0f, 0f, -5f); // 向螢幕內移 5 單位
        square.draw(gl);
    }
}

2. 幾何體:Square (含紋理座標)

package com.example.opengl;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import javax.microedition.khronos.opengles.GL10;
import java.nio.*;

public class Square {
    private FloatBuffer vertexBuffer, textureBuffer;
    private final int[] textureHandle = new int[1];

    // 頂點:逆時針順序 (預設正面)
    private final float[] vertices = {
        -1f, -1f, 0f,  // 左下
         1f, -1f, 0f,  // 右下
        -1f,  1f, 0f,  // 左上
         1f,  1f, 0f   // 右上
    };

    // 紋理座標 (S,T) 對應頂點順序
    private final float[] texCoords = {
        0f, 1f,  // 左下 -> 紋理左下
        1f, 1f,  // 右下 -> 紋理右下
        0f, 0f,  // 左上 -> 紋理左上
        1f, 0f   // 右上 -> 紋理右上
    };

    public Square() {
        vertexBuffer = createFloatBuffer(vertices);
        textureBuffer = createFloatBuffer(texCoords);
    }

    private FloatBuffer createFloatBuffer(float[] arr) {
        ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer fb = bb.asFloatBuffer();
        fb.put(arr);
        fb.position(0);
        return fb;
    }

    public void loadGLTexture(GL10 gl, Context context) {
        // 1. 解碼圖片 (需放在 res/drawable-nodpi/ 避免縮放)
        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.texture);

        // 2. 產生紋理 ID
        gl.glGenTextures(1, textureHandle, 0);

        // 3. 綁定紋理
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureHandle[0]);

        // 4. 設定濾波模式
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

        // 5. 上傳圖片資料到 GPU
        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

        // 6. 釋放 Bitmap 記憶體
        bitmap.recycle();
    }

    public void draw(GL10 gl) {
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureHandle[0]);

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

        gl.glFrontFace(GL10.GL_CCW); // 逆時針為正面
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);

        // TRIANGLE_STRIP: 0,1,2,3 形成兩三角形
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);

        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    }
}

3. Activity 啟動

public class MainActivity extends Activity {
    private GLSurfaceView glView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                             WindowManager.LayoutParams.FLAG_FULLSCREEN);

        glView = new GLSurfaceView(this);
        glView.setEGLContextClientVersion(1); // ES 1.x
        glView.setRenderer(new TextureRenderer(this));
        setContentView(glView);
    }

    @Override protected void onResume() { super.onResume(); glView.onResume(); }
    @Override protected void onPause() { glView.onPause(); super.onPause(); }
}

關鍵注意事項

  1. 紋理尺寸必須為 2 的冪次 (如 256x256, 512x1024),否則 GLUtils.texImage2D 可能失敗或自動縮放 (視驅動)。
  2. 圖片放在 drawable-nodpi/ 避免系統按螢幕密度縮放導致尺寸非 2 的冪次。
  3. 頂點順序與摺疊:GL_TRIANGLE_STRIP 配合正確頂點序可高效畫四邊形。
  4. ES 2.0+ 現代寫法:需自行編寫 Vertex/Fragment Shader,使用 GLES20、glUseProgram、glUniform、glVertexAttribPointer,彈性大得多但樣板碼多。

總結

Android OpenGL ES 紋理映射核心流程:建立幾何緩衝區 → 載入 Bitmap → 產生/綁定紋理 ID → 設定參數 → 上傳像素 → 繪製時綁定紋理並啟用座標陣列。理解固定功能管線 (ES 1.x) 有助於過渡到可程式化管線 (ES 2.0+),後者是現代圖形開發的基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved