Android中使用OpenGL ES实现纹理贴图以及实现多重纹理混合

OpenGL ES中最常用的纹理是2D纹理,也就是一个图像的二维数组,当我们使用纹理时,需要使用纹理坐标作为纹理图像中的索引。纹理坐标用(s, t)指定,或者(U, V)。纹理坐标如下图所示,纹理原点在左下角,往右为s轴,往上为t轴。而屏幕的方向是屏幕左上角为原点,往右为x轴,往下为y轴。所以纹理坐标方向和屏幕坐标方向是上下颠倒的,这点需要注意。

1. 贴一张纹理

纹理贴图首先需要把纹理加载进OpenGL ES中

    //  context用户解析纹理资源时使用,resourceId为纹理资源的ID
    public static int loadTexture(Context context, int resourceId) {
        //textureObjectIds用于存储OpenGL生成纹理对象的ID,我们只需要一个纹理
        final int[] textureObjectIds = new int[1];
        //1代表生成一个纹理
        glGenTextures(1, textureObjectIds, 0);
        //判断是否生成成功
        if(textureObjectIds[0] == 0) {
            Log.w(TAG, "generate a texture object failed!");
            return 0;
        }
        //加载纹理资源,解码成bitmap形式
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = false;
        final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);

        if (bitmap == null) {
            Log.w(TAG, "Resource ID: " + resourceId + " decoded failed");
            //删除指定的纹理对象
            glDeleteTextures(1,textureObjectIds, 0);
            return 0;
        }
        //第一个参数代表这是一个2D纹理,第二个参数就是OpenGL要绑定的纹理对象ID,也就是让OpenGL后面的纹理调用都使用此纹理对象
        glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);
        //设置纹理过滤参数,GL_TEXTURE_MIN_FILTER代表纹理缩写的情况,GL_LINEAR_MIPMAP_LINEAR代表缩小时使用三线性过滤的方式,至于过滤方式以后再详解
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        //GL_TEXTURE_MAG_FILTER代表纹理放大,GL_LINEAR代表双线性过滤
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        //加载实际纹理图像数据到OpenGL ES的纹理对象中,这个函数是Android封装好的,可以直接加载bitmap格式,
        GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
        //bitmap已经被加载到OpenGL了,所以bitmap可释放掉了,防止内存泄露
        bitmap.recycle();
        //我们为纹理生成MIP贴图,提高渲染性能,但是可占用较多的内存
        glGenerateMipmap(GL_TEXTURE_2D);
        //现在OpenGL已经完成了纹理的加载,不需要再绑定此纹理了,后面使用此纹理时通过纹理对象的ID即可
        glBindTexture(GL_TEXTURE_2D, 0);
        //返回OpenGL生成的纹理对象ID
        return textureObjectIds[0];
    }

更新着色器,使其可以绘制纹理

  • 顶点着色器

a_TextureCoordinates代表我们设置的纹理坐标,然后通过v_TextureCoordinates传给片段着色器

attribute vec4 a_Position;

attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;

void main()
{
    v_TextureCoordinates = a_TextureCoordinates;
    gl_Position = a_Position;
}
  • 片段着色器

u_TextureUnit代表实际纹理数据,v_TextureCoordinates是我们传过来的纹理坐标,通过texture2D函数获取纹理对象上指定坐标位置的颜色,这个颜色建设此片段最终的颜色

precision mediump float;

uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;

void main()
{
    gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
}

不知道现在大家有没有理解纹理是怎么使用的,我总结一下,首先把一个纹理图像加载进OpenGL中生成一个纹理对象,这个纹理对象是在绘制图元(比如绘制矩形)时,将纹理指定位置的颜色作为此图元对应的颜色,然后这个矩形最终绘制出来就跟我们加载的纹理一样了,也就是相当于把纹理图像贴在这个矩形上了。

定义顶点数据

//每行的前两个是矩形的(x, y)坐标,后来两个为纹理(s, t)坐标。因为屏幕方向和纹理方向是上下颠倒的,所以矩形左下角(-0.5f,-0.8f)取的是纹理左上角(0f,1f)的颜色,矩形右下角取纹理右上角的颜色
private static final float[] vertexData = {
               0f,    0f, 0.5f, 0.5f,
            -0.5f, -0.8f,   0f,   1f,
             0.5f, -0.8f,   1f,   1f,
             0.5f,  0.8f,   1f,   0f,
            -0.5f,  0.8f,   0f,   0f,
            -0.5f, -0.8f,   0f,   1f
    };

之前我们画单个三角形是使用GL_TRIANGLES这个参数,但是这里我们需要画四个三角形来组成矩形,所以这里我们定义了六组顶点数据,然后绘制三角形的时候需要使用GL_TRIANGLE_FAN这个参数,它是用来画三角形扇的。我们定义了6个顶点1,2,3,4,5,6,通过这个参数最后会绘制四个三角形,分别是(1,2,3),(1,3,4),(1,4,5),(1,5,6)这四个三角形,如下图所示

生成顶点数据缓冲区

mFloatBuffer = ByteBuffer
                .allocateDirect(vertexData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertexData);

在Renderer的onSurfaceCreated方法中需要获取OpenGL里面得到纹理对象的ID

texture = TextureHelper.loadTexture(context, R.drawable.TextureImage);

然后在onDrawFrame方法中设置纹理单元

//激活纹理单元,GL_TEXTURE0代表纹理单元0,GL_TEXTURE1代表纹理单元1,以此类推。OpenGL使用纹理单元来表示被绘制的纹理
glActiveTexture(GL_TEXTURE0);
//绑定纹理到这个纹理单元
glBindTexture(GL_TEXTURE_2D, textureId);
//把选定的纹理单元传给片段着色器中的u_TextureUnit,
glUniform1i(uTextureUnitLocation, 0);

把顶点数传给顶点着色器

//传递矩形顶点坐标
mFloatBuffer.position(0);
glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 4, mFloatBuffer);
glEnableVertexAttribArray(aPositionLocation);
//传递纹理顶点坐标
mFloatBuffer.position(2);
glVertexAttribPointer(aTextureCoordinatesLocation, 2, GL_FLOAT, false, 4, mFloatBuffer);
glEnableVertexAttribArray(aTextureCoordinatesLocation);

绘制矩形

在onDrawFrame方法中绘制矩形,前面已经讲过了,这个矩形使用四个三角形画出来的,GL_TRIANGLE_FAN用来画三角扇

glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

2.多重纹理混合

如果理解了纹理贴图,那么多重纹理混合其实很简单了,比如双层纹理混合就是传入两张纹理图像,然后在OpenGL ES中生成纹理,我们在片段着色器中对两个纹理的颜色就是混合(比如相加、相乘等),这样最后显示的效果就是双层纹理混合的效果。

首先更新着色器

  • 顶点着色器
//多了a_TextureCoordinates2和v_TextureCoordinates2,代表第二个纹理的坐标
uniform mat4 u_Matrix;

attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;
attribute vec2 a_TextureCoordinates2;

varying vec2 v_TextureCoordinates;
varying vec2 v_TextureCoordinates2;

void main()
{
    v_TextureCoordinates = a_TextureCoordinates;
    v_TextureCoordinates2 = a_TextureCoordinates2;
    gl_Position = u_Matrix * a_Position;
    gl_PointSize = 10.0;
}
  • 片段着色器
//多了一个纹理单元和纹理坐标,我这里的混合方式就是把两张纹理的颜色相加,这只是做演示,实际混合根据你们自己的需求来定
precision mediump float;

uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;

uniform sampler2D u_TextureUnit2;
varying vec2 v_TextureCoordinates2;

void main()
{
    gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates) + texture2D(u_TextureUnit2, v_TextureCoordinates2);
}

更新纹理和顶点数据等

在onSurfaceCreated获取第二张纹理

textureBlend = TextureHelper.loadTexture(context, R.drawable.TextureImage2);

在onDrawFrame方法中激活纹理2单元

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textureBlend);
glUniform1i(uTextureUnit2Location, 1);

传递纹理2的顶点坐标给顶点着色器,纹理1 和纹理2的坐标相同,所以顶点坐标就不用更新了

mFloatBuffer.position(2);
glVertexAttribPointer(aTextureCoordinates2Location, 2, GL_FLOAT, false, 4, mFloatBuffer);
glEnableVertexAttribArray(aTextureCoordinates2Location);

到这里纹理2的顶点和纹理单元分别传给了顶点着色器和片段着色器,下一步直接绘制就行了,不需要改别的代码了

猜你喜欢

转载自blog.csdn.net/lb377463323/article/details/64452714