天天看点

Opengl ES系列学习--glActiveTexture API使用

     本节我们来看一下glActiveTexture API的使用,对应的代码是OpenGL\learn\src\main\java\com\opengl\learn\GlActiveTextureRender.java文件。

     所有实例均有提供源码,下载地址:Opengl ES Source Code。

     API中文说明:GLES2.0中文API-glActiveTexture。

     我们本节的内容很多都是基于上一节的基础上用的,只是新加了一部分。glActiveTexture的API是用来激活纹理的,我们本节是直接调用glActiveTexture(GL_TEXTURE0),方法参数只有一个,表示需要激活的纹理ID。比如我们可以在片段着色器中定义多个纹理采样器,每个采样器都可以对应一个纹理ID,我们就可以同时激活它们,只要传入每个纹理的ID就可以了。我们本节就来学习一下纹理基本的操作方法。

     我们本节实现的效果图如下:

Opengl ES系列学习--glActiveTexture API使用

     我们把一张世界地图用纹理画了出来,GlActiveTextureRender类的所有代码如下:

package com.opengl.learn;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.Log;

import com.lime.common.ESShader;
import com.lime.common.TextureHelper;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_ELEMENT_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_STATIC_DRAW;
import static android.opengl.GLES20.GL_TEXTURE0;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_UNSIGNED_SHORT;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindBuffer;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glBufferData;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glDisableVertexAttribArray;
import static android.opengl.GLES20.glDrawElements;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGenBuffers;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;

public class GlActiveTextureRender implements GLSurfaceView.Renderer {
    private static final String TAG = GlActiveTextureRender.class.getSimpleName();
    private static final int BYTES_PER_FLOAT = 4;
    private static final int BYTES_PER_SHORT = 2;
    private static final int POSITION_COMPONENT_SIZE = 3;
    private static final int COLOR_COMPONENT_SIZE = 4;
    private final float[] mVerticesData =
            {
                    -1.0f, 0.5f, 0.0f, // v0
                    -1.0f, -0.5f, 0.0f, // v1
                    1.0f, -0.5f, 0.0f,  // v2
                    1.0f, 0.5f, 0.0f,  // v3
            };

    private final short[] mIndicesData =
            {
                    0, 1, 2,
                    0, 2, 3,
            };

    private final float[] mColorData =
            {
                    0.5f, 0.5f, 1.0f, 1.0f,   // c0
                    0.5f, 1f, 1.0f, 1.0f,   // c1
                    1.0f, 1.0f, 0.5f, 1.0f,    // c2
                    1.0f, 0.5f, 1.0f, 1.0f    // c3
            };

    private final float[] mTexturePosiontData =
            {
                    0.0f, 0.0f,
                    0.0f, 0.5f,
                    0.5f, 0.5f,
                    0.5f, 0.0f
            };

    private Context mContext;
    private int mProgramObject;
    private int mWidth, mHeight;
    private FloatBuffer mVertices;
    private FloatBuffer mColors;
    private FloatBuffer mTextureBuffer;
    private ShortBuffer mIndices;
    private int aPosition, aColor, aTexturePosition;
    private int[] mVBOIds = new int[4];
    private int mTexture;
    private int mTextureUniform;

    public GlActiveTextureRender(Context context) {
        mContext = context;
        mVertices = ByteBuffer.allocateDirect(mVerticesData.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mVertices.put(mVerticesData).position(0);

        mColors = ByteBuffer.allocateDirect(mColorData.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mColors.put(mColorData).position(0);

        mTextureBuffer = ByteBuffer.allocateDirect(mTexturePosiontData.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mTextureBuffer.put(mTexturePosiontData).position(0);

        mIndices = ByteBuffer.allocateDirect(mIndicesData.length * BYTES_PER_SHORT)
                .order(ByteOrder.nativeOrder()).asShortBuffer();
        mIndices.put(mIndicesData).position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        String vShaderStr = ESShader.readShader(mContext, "activetexture_vertexShader.glsl");
        String fShaderStr = ESShader.readShader(mContext, "activetexture_fragmentShader.glsl");

        // Load the shaders and get a linked program object
        mProgramObject = ESShader.loadProgram(vShaderStr, fShaderStr);

        glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        aPosition = glGetAttribLocation(mProgramObject, "aPosition");
        aColor = glGetAttribLocation(mProgramObject, "aColor");
        aTexturePosition = glGetAttribLocation(mProgramObject, "aTexturePosition");
        mTextureUniform = glGetUniformLocation(mProgramObject, "uTextureUnit");

        glGenBuffers(4, mVBOIds, 0);
        Log.e(TAG, "0: " + mVBOIds[0] + ", 1: " + mVBOIds[1] + ", 2: " + mVBOIds[2]);

        // mVBOIds[0] - used to store vertex position
        mVertices.position(0);
        glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
        glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mVerticesData.length,
                mVertices, GL_STATIC_DRAW);

        // mVBOIds[1] - used to store vertex color
        mColors.position(0);
        glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
        glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mColorData.length,
                mColors, GL_STATIC_DRAW);

        mTextureBuffer.position(0);
        glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[2]);
        glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mTexturePosiontData.length,
                mTextureBuffer, GL_STATIC_DRAW);

        // mVBOIds[2] - used to store element indices
        mIndices.position(0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[3]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, BYTES_PER_SHORT * mIndicesData.length, mIndices, GL_STATIC_DRAW);

        mTexture = TextureHelper.loadTexture(mContext, R.mipmap.world);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        mWidth = width;
        mHeight = height;
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        glViewport(0, 0, mWidth, mHeight);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(mProgramObject);
        drawWatermark();
    }

    private void drawWatermark() {
        glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
        glEnableVertexAttribArray(aPosition);
        glVertexAttribPointer(aPosition, POSITION_COMPONENT_SIZE,
                GL_FLOAT, false, 0, 0);

        glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
        glEnableVertexAttribArray(aColor);
        glVertexAttribPointer(aColor, COLOR_COMPONENT_SIZE,
                GL_FLOAT, false, 0, 0);

        glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[2]);
        glEnableVertexAttribArray(mTexture);
        glVertexAttribPointer(mTexture, 2,
                GL_FLOAT, false, 0, 0);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture);
        glUniform1i(mTextureUniform, 0);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[3]);

        glDrawElements(GL_TRIANGLES, mIndicesData.length, GL_UNSIGNED_SHORT, 0);

        glDisableVertexAttribArray(aPosition);
        glDisableVertexAttribArray(aColor);
        glDisableVertexAttribArray(mTexture);

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }
}
           

     几个顶点属性和上一节一样,还是使用VBO来实现的,既然我们要画纹理,也就需要有纹理坐标,所以我们把mVBOIds的长度增加为4,通过如下的绑定顺序,我们就可以看出来,四个缓存的顺序是:顶点坐标、顶点颜色、纹理坐标、索引属性。

mVertices.position(0);
        glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
        glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mVerticesData.length,
                mVertices, GL_STATIC_DRAW);

        // mVBOIds[1] - used to store vertex color
        mColors.position(0);
        glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
        glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mColorData.length,
                mColors, GL_STATIC_DRAW);

        mTextureBuffer.position(0);
        glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[2]);
        glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mTexturePosiontData.length,
                mTextureBuffer, GL_STATIC_DRAW);

        // mVBOIds[2] - used to store element indices
        mIndices.position(0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[3]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, BYTES_PER_SHORT * mIndicesData.length, mIndices, GL_STATIC_DRAW);

        mTexture = TextureHelper.loadTexture(mContext, R.mipmap.world);
           

     要使用glActiveTexture激活纹理,那么和纹理相关的工作我们都必须作好:1、在片段着色器中定义纹理采样器;2、传入纹理坐标到片段着色器中,要用纹理坐标和采样器进行采样,以确定片段颜色。这是两个和纹理相关最根本的目标,为了完成它们,我们就需要定义纹理坐标,并通过Opengl API赋值传递到顶点着色器,并通过varying再传递给片段着色器;我们还要解析纹理图片,并把解析好的Bitmap与申请的缓冲区绑定,设置采样方式,生成Mipmap纹理等等工作,不过这些都是流程性的东西,代码中的TextureHelper工具类已经很好的完成了这些,大家可以直接使用。

     有了这样的思路,剩下的就是一步步把值正确的传入到顶点着色器和片段着色器就可以了。下面我们来仔细看一下顶点坐标和纹理坐标的定义,这些是我们正确画图的保障。

private final float[] mVerticesData =
            {
                    -1.0f, 0.5f, 0.0f, // v0
                    -1.0f, -0.5f, 0.0f, // v1
                    1.0f, -0.5f, 0.0f,  // v2
                    1.0f, 0.5f, 0.0f,  // v3
            };

    private final short[] mIndicesData =
            {
                    0, 1, 2,
                    0, 2, 3,
            };

    private final float[] mColorData =
            {
                    0.5f, 0.5f, 1.0f, 1.0f,   // c0
                    0.5f, 1f, 1.0f, 1.0f,   // c1
                    1.0f, 1.0f, 0.5f, 1.0f,    // c2
                    1.0f, 0.5f, 1.0f, 1.0f    // c3
            };

    private final float[] mTexturePosiontData =
            {
                    0.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 1.0f,
                    1.0f, 0.0f
            };
           

     我们使用索引缓冲区来绘制,所以要画出矩形,只需要定义出四个顶点就可以了,本节我们使用GL_TRIANGLES方式绘制矩形,所以两个三角形一共需要六个顶点坐标,所以mIndicesData顶点索引中元素个数是六个;颜色我们上一节已经说过了,需要和顶点对应,四个顶点对应四个颜色,不过本节我们其实是不需要颜色的,大家可以看下片段着色中,最后是把颜色直接注释掉的。好,最重要的来看一下纹理坐标,它指示我们的纹理应该如何放置,来和顶点坐标对应。

     纹理坐标的原点(0,0)是在屏幕的左上角,水平向右为X轴正方向,竖直向下为Y轴正方向,而顶点坐标是在屏幕中心点,这个区别一定要清楚。那么我们要把世界地图正确的铺在界面上,就和顶点对应,顶点左上角对应纹理左上角(0,0),顶点左下角对应纹理左下角(0,1),顶点右下角对应纹理右下角(1,1),顶点右上角对应纹理右上角(1,0)。这样纹理就可以正确的画出来了。

     还需要说一点,纹理本身是有大小的,它就是加载进来的纹理图片的像素值,所以大家在工作细节中,千万不能像我们这样直接使用,纹理坐标还必须要精确计算,并和顶点对应,才能保证纹理不变形。

     正确画完之后,我们再看一下,当前设置的纹理坐标都是1,如果不是1会出现什么现象?我们把纹理坐标改成如下:

private final float[] mTexturePosiontData =
            {
                    0.0f, 0.0f,
                    0.0f, 0.5f,
                    0.5f, 0.5f,
                    0.5f, 0.0f
            };
           

     这样绘制出来的效果如下:

Opengl ES系列学习--glActiveTexture API使用

    看到了吧,左上角还是一样,右下角不一样,说明纹理是从左上角开始采样的。我们再把纹理坐标改为1.5,绘制出来的效果如下:

private final float[] mTexturePosiontData =
            {
                    0.0f, 0.0f,
                    0.0f, 1.5f,
                    1.5f, 1.5f,
                    1.5f, 0.0f
            };
           
Opengl ES系列学习--glActiveTexture API使用

     通过这两个实验,我们就知道了,纹理都是以顶点区域为目标填满的,那么如何填充就看纹理坐标怎么设置了,当然还要参考纹理采样方式。如果我们设置纹理长度为0.5,说明顶点为1的地方就采样纹理的0.5的区域,相当于把纹理放大两倍,然后取1的地方,所以看到的就如上图0.5那样,整个放大了;如果设置为1.5,就是说顶点为1的地方要对应纹理1.5的地方,那相当于再增加0.5倍的纹理,也就是我们看到的样子了。

     好了,本节关于glActiveTexture API的内容就介绍到这里,当中还有很多其他相关的API,也需要我们一起掌握。