Android 使用OpenGLES繪制球面

從現(xiàn)在開始,我們不用Native層的方法來使用OpenGLES了。經(jīng)過前面的介紹,Native層該怎么使用,應(yīng)該來說都比較熟悉了。
我們從現(xiàn)在開始,使用Android封裝的GLES20 接口,在Java層使用OpenGLES。不過對于性能要求比較高的繪制過程,還是應(yīng)該交給Native層做處理,比如做瘦臉、美白、磨皮等算法運算量比較大的濾鏡。
好了,廢話不多說了。關(guān)于繪制球面,我們首先需要了解的是,這么去構(gòu)建一個球面紋理。要構(gòu)建球面,我們首先需要知道的頂點和紋理需要怎么構(gòu)建。球面紋理一般情況下是通過將球面拆分成多個平面,然后使用三角形對平面進行構(gòu)建。當拆分成的平面越多,則約像一個球。球面的頂點一般都是相互連接起來的,如下圖所示:


球面頂點.png

既然知道了球面頂點的連接方式,那么該如何計算球面頂點的位置?
如下圖所示:


三維坐標公式.png

根據(jù)三角形公式可以得到:
x0 = R * cos(a) * sin(b);
y0 = R * sin(a);
z0 = R * cos(a) * cos(b);
我們可以由此得到一個球某個位置的頂點所在的位置。那么我們可以通過將球面拆分成許許多多的按照一定值的夾角的頂點,相互連接起來,就構(gòu)成了我們所需要的球。計算頂點方法如下:

    for (double vAngle = 0; vAngle < Math.PI; vAngle = vAngle + angleSpan) { // vertical
            for (double hAngle = 0; hAngle < 2 * Math.PI; hAngle = hAngle + angleSpan) { // horizontal

                float x0 = (float) (radius * Math.sin(vAngle) * Math.cos(hAngle));
                float y0 = (float) (radius * Math.sin(vAngle) * Math.sin(hAngle));
                float z0 = (float) (radius * Math.cos((vAngle)));

                float x1 = (float) (radius * Math.sin(vAngle) * Math.cos(hAngle + angleSpan));
                float y1 = (float) (radius * Math.sin(vAngle) * Math.sin(hAngle + angleSpan));
                float z1 = (float) (radius * Math.cos(vAngle));

                float x2 = (float) (radius * Math.sin(vAngle + angleSpan) * Math.cos(hAngle + angleSpan));
                float y2 = (float) (radius * Math.sin(vAngle + angleSpan) * Math.sin(hAngle + angleSpan));
                float z2 = (float) (radius * Math.cos(vAngle + angleSpan));

                float x3 = (float) (radius * Math.sin(vAngle + angleSpan) * Math.cos(hAngle));
                float y3 = (float) (radius * Math.sin(vAngle + angleSpan) * Math.sin(hAngle));
                float z3 = (float) (radius * Math.cos(vAngle + angleSpan));

                vertex.add(x1);
                vertex.add(y1);
                vertex.add(z1);

                vertex.add(x3);
                vertex.add(y3);
                vertex.add(z3);

                vertex.add(x0);
                vertex.add(y0);
                vertex.add(z0);

                vertex.add(x1);
                vertex.add(y1);
                vertex.add(z1);

                vertex.add(x2);
                vertex.add(y2);
                vertex.add(z2);

                vertex.add(x3);
                vertex.add(y3);
                vertex.add(z3);
            }
        }

上面的方式就是通過橫向和縱向按照一定的角度分割成多個頂點,將所有的頂點相互連接成一小塊平面后,拼接起來就得到了我們所需要的球面。
好了,我們來看看具體實現(xiàn)吧:
自定義GLSurfaceView:

public class SphereSurfaceView extends GLSurfaceView {

    private SphereRender mSphereRender;

    public SphereSurfaceView(Context context) {
        super(context);
        init(context);
    }

    public SphereSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mSphereRender = new SphereRender(context);
        setEGLContextClientVersion(2);
        setRenderer(mSphereRender);
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
        setOnTouchListener(new OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN://檢測到點擊事件時
                        mSphereRender.rotate(20f, 0, 1, 0); //繞y軸旋轉(zhuǎn)
                }
                return true;
            }
        });
    }
    
}

點擊GLSurfaceView時,球面會繞Y軸旋轉(zhuǎn)20度。
我們來看看Render的實現(xiàn):

public class SphereRender implements GLSurfaceView.Renderer {

    // 視圖矩陣
    private float[] mViewMatrix = new float[16];
    // 模型矩陣
    private float[] mModelMatrix = new float[16];
    // 投影矩陣
    private float[] mProjectionMatrix = new float[16];
    // 總變換矩陣
    private float[] mMVPMatrix = new float[16];

    private SphereFilter mSphereFilter;

    private Context mContext;

    public SphereRender(Context context) {
        mContext = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //設(shè)置屏幕背景色RGBA
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
        //打開深度檢測
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
        //打開背面剪裁
        GLES20.glEnable(GLES20.GL_CULL_FACE);
        // 創(chuàng)建球面繪制實體
        mSphereFilter = new SphereFilter();
        // 初始化矩陣
        initMatrix();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
        float ratio = (float) width / height;
        // 調(diào)用此方法計算產(chǎn)生透視投影矩陣
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 20, 100);
        // 調(diào)用此方法產(chǎn)生攝像機9參數(shù)位置矩陣
        Matrix.setLookAtM(mViewMatrix, 0,
                0f, 0f, 30f,    // 相機位置
                0f, 0f, 0f,     // 目標位置
                0f, 1.0f, 0.0f  // 相機朝向
        );
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //清除深度緩沖與顏色緩沖
        GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        calculateMatrix();
        mSphereFilter.setMVPMatrix(mMVPMatrix);
        mSphereFilter.drawSphere();
    }

    /**
     * 初始化矩陣
     */
    private void initMatrix() {
        Matrix.setIdentityM(mViewMatrix, 0);
        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.setIdentityM(mProjectionMatrix, 0);
        Matrix.setIdentityM(mMVPMatrix, 0);
    }

    /**
     * 計算總變換矩陣
     */
    private void calculateMatrix() {
        Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
    }

    /**
     * 物體旋轉(zhuǎn)
     * @param angle
     * @param x
     * @param y
     * @param z
     */
    public void rotate(float angle, float x, float y, float z) {
        Matrix.rotateM(mModelMatrix, 0, angle, x, y, z);
    }
}

Render主要是啟用了深度檢測,然后設(shè)置相應(yīng)的視圖矩陣、投影矩陣和模型矩陣等,在繪制時計算出綜合變換矩陣傳遞給球面繪制實體,然后繪制球面。球面繪制實體的實現(xiàn)如下:

public class SphereFilter {

    private static final String VERTEX_SHADER =
            "uniform mat4 u_Matrix;//最終的變換矩陣\n" +
            "attribute vec4 a_Position;//頂點位置\n" +
            "varying vec4 vPosition;//用于傳遞給片元著色器的頂點位置\n" +
            "void main() {\n" +
            "    gl_Position = u_Matrix * a_Position;\n" +
            "    vPosition = a_Position;\n" +
            "}";

    private static final String FRAGMENT_SHADER =
            "precision mediump float;\n" +
            "varying vec4 vPosition;\n" +
            "void main() {\n" +
            "    float uR = 0.6;//球的半徑\n" +
            "    vec4 color;\n" +
            "    float n = 8.0;//分為n層n列n行\(zhòng)n" +
            "    float span = 2.0*uR/n;//正方形長度\n" +
            "    //計算行列層數(shù)\n" +
            "    int i = int((vPosition.x + uR)/span);//行數(shù)\n" +
            "    int j = int((vPosition.y + uR)/span);//層數(shù)\n" +
            "    int k = int((vPosition.z + uR)/span);//列數(shù)\n" +
            "    int colorType = int(mod(float(i+j+k),2.0));\n" +
            "    if(colorType == 1) {//奇數(shù)時為綠色\n" +
            "        color = vec4(0.2,1.0,0.129,0);\n" +
            "    } else {//偶數(shù)時為白色\n" +
            "        color = vec4(1.0,1.0,1.0,0);//白色\n" +
            "    }\n" +
            "    //將計算出的顏色給此片元\n" +
            "    gl_FragColor = color;\n" +
            "}";

    private float radius = 1.0f; // 球的半徑
    final double angleSpan = Math.PI / 90f; // 將球進行單位切分的角度
    private FloatBuffer mVertexBuffer;// 頂點坐標
    int mVertexCount = 0;// 頂點個數(shù),先初始化為0

    // float類型的字節(jié)數(shù)
    private static final int BYTES_PER_FLOAT = 4;

    // 數(shù)組中每個頂點的坐標數(shù)
    private static final int COORDS_PER_VERTEX = 3;

    private int mProgramHandle;
    private int muMatrixHandle;
    private int maPositionHandle;

    private float[] mMVPMatrix = new float[16];


    public SphereFilter() {
        initSphereVertex();
        createProgram();
        Matrix.setIdentityM(mMVPMatrix, 0);
    }

    /**
     * 計算球面頂點
     */
    public void initSphereVertex() {
        ArrayList<Float> vertex = new ArrayList<Float>();

        for (double vAngle = 0; vAngle < Math.PI; vAngle = vAngle + angleSpan) { // vertical
            for (double hAngle = 0; hAngle < 2 * Math.PI; hAngle = hAngle + angleSpan) { // horizontal

                float x0 = (float) (radius * Math.sin(vAngle) * Math.cos(hAngle));
                float y0 = (float) (radius * Math.sin(vAngle) * Math.sin(hAngle));
                float z0 = (float) (radius * Math.cos((vAngle)));

                float x1 = (float) (radius * Math.sin(vAngle) * Math.cos(hAngle + angleSpan));
                float y1 = (float) (radius * Math.sin(vAngle) * Math.sin(hAngle + angleSpan));
                float z1 = (float) (radius * Math.cos(vAngle));

                float x2 = (float) (radius * Math.sin(vAngle + angleSpan) * Math.cos(hAngle + angleSpan));
                float y2 = (float) (radius * Math.sin(vAngle + angleSpan) * Math.sin(hAngle + angleSpan));
                float z2 = (float) (radius * Math.cos(vAngle + angleSpan));

                float x3 = (float) (radius * Math.sin(vAngle + angleSpan) * Math.cos(hAngle));
                float y3 = (float) (radius * Math.sin(vAngle + angleSpan) * Math.sin(hAngle));
                float z3 = (float) (radius * Math.cos(vAngle + angleSpan));

                vertex.add(x1);
                vertex.add(y1);
                vertex.add(z1);

                vertex.add(x3);
                vertex.add(y3);
                vertex.add(z3);

                vertex.add(x0);
                vertex.add(y0);
                vertex.add(z0);

                vertex.add(x1);
                vertex.add(y1);
                vertex.add(z1);

                vertex.add(x2);
                vertex.add(y2);
                vertex.add(z2);

                vertex.add(x3);
                vertex.add(y3);
                vertex.add(z3);
            }
        }

        mVertexCount = vertex.size() / COORDS_PER_VERTEX;
        float vertices[] = new float[vertex.size()];
        for (int i = 0; i < vertex.size(); i++) {
            vertices[i] = vertex.get(i);
        }
        mVertexBuffer = GlUtil.createFloatBuffer(vertices);
    }

    /**
     * 創(chuàng)建Program
     */
    private void createProgram() {
        mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
        maPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
        muMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_Matrix");
    }

    /**
     * 繪制球面
     */
    public void drawSphere() {
        GLES20.glUseProgram(mProgramHandle);
        GLES20.glVertexAttribPointer(maPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false, 0, mVertexBuffer);
        GLES20.glEnableVertexAttribArray(maPositionHandle);
        GLES20.glUniformMatrix4fv(muMatrixHandle, 1, false, mMVPMatrix, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertexCount);
        GLES20.glDisableVertexAttribArray(maPositionHandle);
        GLES20.glUseProgram(0);
    }

    /**
     * 設(shè)置最終的變換矩陣
     * @param matrix
     */
    public void setMVPMatrix(float[] matrix) {
        mMVPMatrix = matrix;
    }
}

實現(xiàn)過程主要是創(chuàng)建Program、綁定對應(yīng)的Attribute屬性、設(shè)置總變換矩陣、繪制球面等。下面是工具,用于創(chuàng)建Program、創(chuàng)建mipmap紋理等方法的封裝:

public class GlUtil {

    public static final String TAG = "GlUtil";

    // 從初始化失敗
    public static final int GL_NOT_INIT = -1;

    // 單位矩陣
    public static final float[] IDENTITY_MATRIX;
    static {
        IDENTITY_MATRIX = new float[16];
        Matrix.setIdentityM(IDENTITY_MATRIX, 0);
    }

    private static final int SIZEOF_FLOAT = 4;

    private GlUtil() {}

    /**
     * 創(chuàng)建program
     * @param vertexSource
     * @param fragmentSource
     * @return
     */
    public static int createProgram(String vertexSource, String fragmentSource) {
        int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader == 0) {
            return 0;
        }
        int pixelShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentSource);
        if (pixelShader == 0) {
            return 0;
        }

        int program = GLES30.glCreateProgram();
        checkGlError("glCreateProgram");
        if (program == 0) {
            Log.e(TAG, "Could not create program");
        }
        GLES30.glAttachShader(program, vertexShader);
        checkGlError("glAttachShader");
        GLES30.glAttachShader(program, pixelShader);
        checkGlError("glAttachShader");
        GLES30.glLinkProgram(program);
        int[] linkStatus = new int[1];
        GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0);
        if (linkStatus[0] != GLES30.GL_TRUE) {
            Log.e(TAG, "Could not link program: ");
            Log.e(TAG, GLES30.glGetProgramInfoLog(program));
            GLES30.glDeleteProgram(program);
            program = 0;
        }
        return program;
    }

    /**
     * 加載Shader
     * @param shaderType
     * @param source
     * @return
     */
    public static int loadShader(int shaderType, String source) {
        int shader = GLES30.glCreateShader(shaderType);
        checkGlError("glCreateShader type=" + shaderType);
        GLES30.glShaderSource(shader, source);
        GLES30.glCompileShader(shader);
        int[] compiled = new int[1];
        GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);
        if (compiled[0] == 0) {
            Log.e(TAG, "Could not compile shader " + shaderType + ":");
            Log.e(TAG, " " + GLES30.glGetShaderInfoLog(shader));
            GLES30.glDeleteShader(shader);
            shader = 0;
        }
        return shader;
    }

    /**
     * 檢查是否出錯
     * @param op
     */
    public static void checkGlError(String op) {
        int error = GLES30.glGetError();
        if (error != GLES30.GL_NO_ERROR) {
            String msg = op + ": glError 0x" + Integer.toHexString(error);
            Log.e(TAG, msg);
            throw new RuntimeException(msg);
        }
    }

    /**
     * 創(chuàng)建FloatBuffer
     * @param coords
     * @return
     */
    public static FloatBuffer createFloatBuffer(float[] coords) {
        ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer fb = bb.asFloatBuffer();
        fb.put(coords);
        fb.position(0);
        return fb;
    }

    /**
     * 創(chuàng)建FloatBuffer
     * @param data
     * @return
     */
    public static FloatBuffer createFloatBuffer(ArrayList<Float> data) {
        float[] coords = new float[data.size()];
        for (int i = 0; i < coords.length; i++){
            coords[i] = data.get(i);
        }
        return createFloatBuffer(coords);
    }

    /**
     * 創(chuàng)建Texture對象
     * @param textureType
     * @return
     */
    public static int createTextureObject(int textureType) {
        int[] textures = new int[1];
        GLES30.glGenTextures(1, textures, 0);
        GlUtil.checkGlError("glGenTextures");
        int textureId = textures[0];
        GLES30.glBindTexture(textureType, textureId);
        GlUtil.checkGlError("glBindTexture " + textureId);
        GLES30.glTexParameterf(textureType, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
        GLES30.glTexParameterf(textureType, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
        GLES30.glTexParameterf(textureType, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
        GLES30.glTexParameterf(textureType, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
        GlUtil.checkGlError("glTexParameter");
        return textureId;
    }

    /**
     * 創(chuàng)建Sampler2D的Framebuffer 和 Texture
     * @param frameBuffer
     * @param frameBufferTex
     * @param width
     * @param height
     */
    public static void createSampler2DFrameBuff(int[] frameBuffer, int[] frameBufferTex,
                                                int width, int height) {
        GLES30.glGenFramebuffers(1, frameBuffer, 0);
        GLES30.glGenTextures(1, frameBufferTex, 0);
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, frameBufferTex[0]);
        GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, width, height, 0,
                GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]);
        GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0,
                GLES30.GL_TEXTURE_2D, frameBufferTex[0], 0);
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
        checkGlError("createCamFrameBuff");
    }

    /**
     * 加載mipmap紋理
     * @param bitmap bitmap圖片
     * @return
     */
    public static int createTexture(Bitmap bitmap) {
        int[] texture = new int[1];
        if (bitmap != null && !bitmap.isRecycled()) {
            //生成紋理
            GLES30.glGenTextures(1, texture, 0);
            checkGlError("glGenTexture");
            //生成紋理
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture[0]);
            //設(shè)置縮小過濾為使用紋理中坐標最接近的一個像素的顏色作為需要繪制的像素顏色
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_MIN_FILTER,GLES30.GL_NEAREST);
            //設(shè)置放大過濾為使用紋理中坐標最接近的若干個顏色,通過加權(quán)平均算法得到需要繪制的像素顏色
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_MAG_FILTER,GLES30.GL_LINEAR);
            //設(shè)置環(huán)繞方向S,截取紋理坐標到[1/2n,1-1/2n]。將導致永遠不會與border融合
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_WRAP_S,GLES30.GL_CLAMP_TO_EDGE);
            //設(shè)置環(huán)繞方向T,截取紋理坐標到[1/2n,1-1/2n]。將導致永遠不會與border融合
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_WRAP_T,GLES30.GL_CLAMP_TO_EDGE);
            //根據(jù)以上指定的參數(shù),生成一個2D紋理
            GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0);
            return texture[0];
        }
        return 0;
    }

    /**
     * 創(chuàng)建OES類型的Texture
     * @return
     */
    public static int createOESTexture() {
        int[] texture = new int[1];

        GLES20.glGenTextures(1, texture, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

        return texture[0];
    }

    /**
     * 加載mipmap紋理
     * @param context
     * @param name
     * @return
     */
    public static int loadMipmapTextureFromAssets(Context context, String name) {
        int[] textureHandle = new int[1];
        GLES30.glGenTextures(1, textureHandle, 0);
        if (textureHandle[0] != 0) {
            Bitmap bitmap = getImageFromAssetsFile(context, name);
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureHandle[0]);

            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
            GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0);
            bitmap.recycle();
        }
        if (textureHandle[0] == 0) {
            throw new RuntimeException("Error loading texture.");
        }

        return textureHandle[0];
    }

    /**
     * 加載Assets文件夾下的圖片
     * @param context
     * @param fileName
     * @return
     */
    public static Bitmap getImageFromAssetsFile(Context context, String fileName) {
        Bitmap bitmap = null;
        AssetManager manager = context.getResources().getAssets();
        try {
            InputStream is = manager.open(fileName);
            bitmap = BitmapFactory.decodeStream(is);
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

至此,我們就可以看到一個球面了:


Screenshot_20170829-115027.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容