Android OpenGL ES从入门到进阶(七)—— OpenGL ES 2D贴纸与Blend混合

源码链接:https://github.com/smzhldr/AGLFramework

一、前言

在美颜相机为背景的前提下,人像美颜和特效滤镜,以及各式各样的贴纸是美颜相机的核心功能,特效滤镜和人像美颜异曲同工,都是通过改变每一个像素的色值来达到的,而贴纸是在原图上覆盖了一层新的像素集合,贴纸可以做出很炫酷的动态效果,本文只演示静态效果,动态贴纸是按一定规则时时改变贴纸的位置而已。

二、先看下贴纸的效果

这是墙面的预览画面增加了一个梅花的贴着效果。

三、贴纸原理分析

前面我们学习了纹理的渲染,贴纸实际上就是讲图片转换为纹理,然后输出在屏幕上,图中所看到的景象是在帧缓冲区framebuffer中先绘制相机背景(相机预览画面),然后再将图片转化成的纹理贴在背景上,相当于输出了两个纹理,知识两张纹理的范围不一样。

图片转化为纹理的代码:

public static int loadTexture(final Bitmap img) {
        int textures[] = new int[1];
        GLES20.glGenTextures(1, textures, 0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, img, 0);
        return textures[0];
    }

shader就只有原图输出的功能:

//顶点shader
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
   	gl_Position = position;
    textureCoordinate = inputTextureCoordinate.xy;
}

//片段shader
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
    gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}

StickerFilter原理分析:
StickerFilter继承自之前封装好的AGLFilter,具有将纹理输出到帧缓冲区的功能,在此基础上,再确定2D贴纸的位置坐标,将纹理绑定到program上,上传坐标信息,调用OpenGL程序再执行一遍,就将图片贴到了屏幕上。
贴图代码如下:

		GLES20.glEnable(GLES20.GL_BLEND);
        //混合
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);

        GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
        bindTexture(stickerTexture);
        GLES20.glUniform1i(glUniformTexture, 1);

        stickerCubeBuffer.clear();
        stickerCubeBuffer.put(stickerCube).position(0);
        GLES20.glVertexAttribPointer(glAttrPosition, 2, GLES20.GL_FLOAT, false, 0, stickerCubeBuffer);
        GLES20.glEnableVertexAttribArray(glAttrPosition);

        stickerTextureBuffer.clear();
        stickerTextureBuffer.put(stickerCoordnate).position(0);
        GLES20.glVertexAttribPointer(glAttrTextureCoordinate, 2, GLES20.GL_FLOAT, false, 0, stickerTextureBuffer);
        GLES20.glEnableVertexAttribArray(glAttrTextureCoordinate);

        GLES20.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        GLES20.glDisableVertexAttribArray(glAttrPosition);
        GLES20.glDisableVertexAttribArray(glAttrTextureCoordinate);

        GLES20.glDisable(GLES20.GL_BLEND);

贴图的代码没有什么特别之处,除了上传坐标,绑定纹理,开始绘制并没有新的知识点,需要注意的是:

		GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
			.
			.
			.
		GLES20.glDisable(GLES20.GL_BLEND);

顾名思义为混合,目的就是在原有像素上绘制新像素时按照一定的规则取舍,重叠区域是保留旧像素还是用新像素代替,或者按照某一个因数求和、做差等。本例中我们是想让贴纸不透明部分覆盖原有像素,透明部分舍去的操作,使用了上述的混合规则,混合规则有很多,想了解更多混合规则的网上资料也比较多,讲的都比较详细,限于篇幅,这里就不分析诸多的混合规则了,需要提到的是GLES20.glBlendFuncSeparate()是另一个常用的混合函数,其功能也是混合。

四、位置变换

在OpenGL中,顶点坐标的范围是[-1,1],左下方为(-1,-1),右上方为(1,1), 纹理坐标的范围是[0,1],左下方是(0,0),右上方是(1,1);而手机屏幕的坐标(0,0)点在左上方。贴纸呈现在屏幕上,我们视觉效果上是屏幕坐标,但是绘制时却需要用OpenGL的坐标,这就牵扯到了坐标转换。
纹理坐标确定输出纹理的部分,即纹理是否被剪裁,翻转,镜像等,与定位没有关系,贴纸的位置与定点坐标有关。如上图所示,我们要在屏幕中心贴一张大小200px*200px的图片,也就是在背景上贴一张同样大小的图片,背景的大小我们可以获得,那么可以计算出贴纸在背景中的矩形框坐标:

		int width = frame.getTextureWidth();
        int height = frame.getTextureHeight();
        stickerCube[0] = 0 + width / 2f - 200f;
        stickerCube[1] = 0 + height / 2f + 200f;
        stickerCube[2] = 0 + width / 2f + 200f;
        stickerCube[3] = 0 + height / 2f + 200f;
        stickerCube[4] = 0 + width / 2f - 200f;
        stickerCube[5] = 0 + height / 2f - 200f;
        stickerCube[6] = 0 + width / 2f + 200f;
        stickerCube[7] = 0 + height / 2f - 200f;

这里的**0+**意识是画面从左上角开始,实际代表画面的屏幕坐标原点。得到了想要的贴纸在屏幕上的位置之后可以根据上述坐标范围的对应关系进行如下转换:

 			
 		//x坐标对应width,y坐标对应height
 		for (int i = 0; i < stickerCube.length; i += 2) {
            stickerCube[i] = stickerCube[i] * 2f / width - 1f;
            stickerCube[i + 1] = stickerCube[i + 1] * 2f / height - 1f;
        }

这样得到的就是屏幕中心200 * 200范文的对应OpenGL坐标,上传坐标直接绘制即可得到相应的结果,还不清楚的可以查看 源码或者留言交流。

源码链接

猜你喜欢

转载自blog.csdn.net/liuderong0/article/details/99351296