OpenGLES:多纹理贴图,文字水印

一.概述

上一篇博客讲解了OpenGLES怎么实现单纹理贴图

仅仅只绘制一张图片是不过瘾的

本篇博客讲解如何通过多纹理贴图实现图片和文本水印效果

在单纹理贴图基础上,多纹理贴图的区别主要有两点:

  • 纹理的生成、绑定等由单个变成多个
  • 文本内容先转换为Bitmap后再进行纹理贴图

知道这两点之后,实现起来就容易了

这篇博客我会使用多纹理实现一张背景大图,一张水印小图和一串文字水印的效果。

最终效果在最后会展示。

废话不多说,正篇开始!

二.Render类代码

public class ImgTxtRender implements GLSurfaceView.Renderer {
    private final String TAG = ImgTxtRender.class.getSimpleName();

    private final Context mContext;

    //背景图片顶点
    private float vertexData[] = {
            -1f, -1f,  //左下
            1f, -1f,   //右下
            -1f, 1f,   //左上
            1f, 1f,    //右上
    };

    //图片水印顶点
    private float vertexData1[] = {
            -1f, 0.5f,  //左下
            0f, 0.5f,   //右下
            -1f, 1f,    //左上
            0f, 1f,     //右上
    };

    //文本水印顶点
    private float vertexData2[] = {
            0f, -0.85f,   //左下
            1f, -0.85f,   //右下
            0f, -0.75f,  //左上
            1f, -0.75f    //右上
    };

    //Android 纹理原点在左上角
    private float textureData[] = {
            0.0f, 1.0f,  //左上
            1.0f, 1.0f,  //右上
            0.0f, 0.0f,  //左下
            1.0f, 0.0f,  //右下
    };

    //shader程序/渲染器
    private int shaderProgram;
    //纹理id数组
    private int[] textureId = new int[3];
    //顶点坐标
    private int aPosition;
    //纹理坐标
    private int aTexCoord;
    //采样器
    private int sampler;
    //顶点数据缓存
    private FloatBuffer vertexBuffer;
    private FloatBuffer vertexBuffer1;
    private FloatBuffer vertexBuffer2;

    //纹理数据缓存
    private FloatBuffer textureBuffer;
    //一个顶点有几个数据
    private int VERTEX_POSITION_SIZE = 2;
    //一个纹理点有几个数据
    private int TEXTURE_POSITION_SIZE = 2;

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

    public void initData(Size size) {
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        Log.v(TAG, "onSurfaceCreated()");
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        initGLES();
    }

    public void initGLES() {
        Log.v(TAG, "initGLES!");

        /************** 着色器程序/渲染器 **************/
        //创建并连接 着色器程序
        shaderProgram = ShaderUtils.createAndLinkProgram(mContext,
                "img_vertex_shader.glsl",
                "img_fragtment_shader.glsl");
        if (shaderProgram == 0) {
            Log.v(TAG, "create And Link ShaderProgram Fail!");
            return;
        }
        //使用着色器源程序
        glUseProgram(shaderProgram);

        /************** 着色器变量 **************/
        //从着色器程序中获取各个变量
        aPosition = glGetAttribLocation(shaderProgram, "aPosition");
        aTexCoord = glGetAttribLocation(shaderProgram, "aTexCoord");
        sampler = glGetUniformLocation(shaderProgram, "sampler");
        //将片段着色器的采样器(纹理属性:sampler)设置为0号单元
        glUniform1i(sampler, 0);

        vertexBuffer = ShaderUtils.getFloatBuffer(vertexData);
        vertexBuffer1 = ShaderUtils.getFloatBuffer(vertexData1);
        vertexBuffer2 = ShaderUtils.getFloatBuffer(vertexData2);
        textureBuffer = ShaderUtils.getFloatBuffer(textureData);

        //设置文字支持透明
        GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);

        /************* 纹理 **************/
        //创建纹理对象1
        glGenTextures(textureId.length, textureId, 0);
        //激活纹理0:默认0号纹理单元,一般最多能绑16个,视GPU而定
        glActiveTexture(GL_TEXTURE);

        TextureUtils.LoadTexture(mContext,textureId[0], R.drawable.bg4);
        TextureUtils.LoadTexture(mContext,textureId[1], R.drawable.watermark);
        glBindTextureLoadTxt(textureId[2], "OpenGLES: Shawn.Xiao!");
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        Log.v(TAG, "onSurfaceChanged(): " + width + " x " + height);

        glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //Log.v(TAG, "onDrawFrame()");
        glClear(GL_COLOR_BUFFER_BIT);

        drawBg();
        drawWaterMarkImg();
        drawWaterMarkTxt();
    }

    private void drawBg() {
        /********* 顶点操作 **********/
        glEnableVertexAttribArray(aPosition);
        glEnableVertexAttribArray(aTexCoord);

        glVertexAttribPointer(aPosition, VERTEX_POSITION_SIZE, GL_FLOAT, false, 2 * 4, vertexBuffer);
        glVertexAttribPointer(aTexCoord, TEXTURE_POSITION_SIZE, GL_FLOAT, false, 2 * 4, textureBuffer);

        /********* 纹理操作:激活、绑定 **********/
        glActiveTexture(GL_TEXTURE);
        glBindTexture(GL_TEXTURE_2D, textureId[0]);
        /********* 绘制 **********/
        //绘制vertexData.length/2即4个点
        glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexData.length / 2);
        /********* 纹理操作:解除绑定 **********/
        glBindTexture(GL_TEXTURE_2D, 0);

        //关闭顶点数组的句柄
        glDisableVertexAttribArray(aPosition);
        glDisableVertexAttribArray(aTexCoord);
    }

    private void drawWaterMarkImg() {
        /********* 顶点操作 **********/
        glEnableVertexAttribArray(aPosition);
        glEnableVertexAttribArray(aTexCoord);

        glVertexAttribPointer(aPosition, VERTEX_POSITION_SIZE, GL_FLOAT, false, 2 * 4, vertexBuffer1);
        glVertexAttribPointer(aTexCoord, TEXTURE_POSITION_SIZE, GL_FLOAT, false, 2 * 4, textureBuffer);

        /********* 纹理操作:激活、绑定 **********/
        //glActiveTexture(GL_TEXTURE1);   //花了129元
        glBindTexture(GL_TEXTURE_2D, textureId[1]);
        /********* 绘制 **********/
        //绘制vertexData.length/2即4个点
        glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexData1.length / 2);
        /********* 纹理操作:解除绑定 **********/
        glBindTexture(GL_TEXTURE_2D, 0);

        //关闭顶点数组的句柄
        glDisableVertexAttribArray(aPosition);
        glDisableVertexAttribArray(aTexCoord);
    }

    private void drawWaterMarkTxt() {
        /********* 顶点操作 **********/
        glEnableVertexAttribArray(aPosition);
        glEnableVertexAttribArray(aTexCoord);

        glVertexAttribPointer(aPosition, VERTEX_POSITION_SIZE, GL_FLOAT, false, 2 * 4, vertexBuffer2);
        glVertexAttribPointer(aTexCoord, TEXTURE_POSITION_SIZE, GL_FLOAT, false, 2 * 4, textureBuffer);

        /********* 纹理操作:激活、绑定 **********/
        glActiveTexture(GL_TEXTURE);
        glBindTexture(GL_TEXTURE_2D, textureId[2]);
        /********* 绘制 **********/
        //绘制vertexData.length/2即4个点
        glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexData.length / 2);
        /********* 纹理操作:解除绑定 **********/
        glBindTexture(GL_TEXTURE_2D, 0);

        //关闭顶点数组的句柄
        glDisableVertexAttribArray(aPosition);
        glDisableVertexAttribArray(aTexCoord);
    }

    private void glBindTextureLoadTxt(int textureId, String txt) {
        //绑定纹理:将纹理放到当前单元的 GL_TEXTURE_BINDING_EXTERNAL_OES 目标对象中
        glBindTexture(GL_TEXTURE_2D, textureId);
        //配置纹理:过滤方式
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        /************* bitmap **************/
        //定义变量
        int textSize = 36;
        String textColor = "#ffff00";
        String bgColor = "#00000000";
        int padding = 0;
        //txt转换成bitmap
        Bitmap bitmap = BitmapUtils.createTextImage(txt, textSize, textColor, bgColor, padding);
        //绑定 bitmap 到textureIds[1]纹理
        GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();//用完及时回收

        //解绑纹理 指的是离开对 纹理的配置,进入下一个状态
        glBindTexture(GL_TEXTURE_2D, 0);
    }
}

三.ShaderUtils相关代码:

ShaderUtils的函数与单纹理贴图的博文中是一样的

3.1 createAndLinkProgram()

    /*
     * 创建和链接着色器程序
     * 参数:顶点着色器、片段着色器程序ResId
     * 返回:成功创建、链接了顶点和片段着色器的着色器程序Id
     */
    public static int createAndLinkProgram(Context context, String vertexShaderFN, String fragShaderFN) {
        //创建着色器程序
        int shaderProgram = glCreateProgram();
        if (shaderProgram == 0) {
            Log.e(TAG, "Failed to create shaderProgram ");
            return 0;
        }

        //获取顶点着色器对象
        int vertexShader = loadShader(GL_VERTEX_SHADER, loadShaderSource(context, vertexShaderFN));
        if (0 == vertexShader) {
            Log.e(TAG, "Failed to load vertexShader");
            return 0;
        }

        //获取片段着色器对象
        int fragmentShader = loadShader(GL_FRAGMENT_SHADER, loadShaderSource(context, fragShaderFN));
        if (0 == fragmentShader) {
            Log.e(TAG, "Failed to load fragmentShader");
            return 0;
        }

        //绑定顶点着色器到着色器程序
        glAttachShader(shaderProgram, vertexShader);
        //绑定片段着色器到着色器程序
        glAttachShader(shaderProgram, fragmentShader);

        //链接着色器程序
        glLinkProgram(shaderProgram);
        //检查着色器链接状态
        int[] linked = new int[1];
        glGetProgramiv(shaderProgram, GL_LINK_STATUS, linked, 0);
        if (linked[0] == 0) {
            glDeleteProgram(shaderProgram);
            Log.e(TAG, "Failed to link shaderProgram");
            return 0;
        }

        return shaderProgram;
    }

3.2 getFloatBuffer()

    public static FloatBuffer getFloatBuffer(float[] array) {
        //将顶点数据拷贝映射到 native 内存中,以便opengl能够访问
        FloatBuffer buffer = ByteBuffer
                .allocateDirect(array.length * BYTES_PER_FLOAT)//直接分配 native 内存,不会被gc
                .order(ByteOrder.nativeOrder())//和本地平台保持一致的字节序(大/小头)
                .asFloatBuffer();//将底层字节映射到FloatBuffer实例,方便使用
 
        buffer.put(array)//将顶点拷贝到 native 内存中
                .position(0);//每次 put position 都会 + 1,需要在绘制前重置为0
 
        return buffer;
    }

四.TextureUtils相关代码

4.1 LoadTexture()

    //纹理Id由外部传入
    public static void LoadTexture(Context context, int textureId, int bitmapResId) {
        //绑定纹理:将纹理放到当前单元的 GL_TEXTURE_BINDING_EXTERNAL_OES 目标对象中
        glBindTexture(GL_TEXTURE_2D, textureId);
        //配置纹理:过滤方式
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        /************* bitmap **************/
        //获取图片的 bitmap
        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bitmapResId);
        //绑定 bitmap 到textureIds[1]纹理
        GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();//用完及时回收

        //解绑纹理 指的是离开对 纹理的配置,进入下一个状态
        glBindTexture(GL_TEXTURE_2D, 0);
    }

五.BitmapUtils相关代码

文字转换成Bitmap

    /**
     * 设置文字水印
     *
     * @param text      文本内容
     * @param textSize  文字大小
     * @param textColor 文字颜色
     * @param bgColor   文字背景颜色 #00000000
     * @param padding   文字与边距距离
     * @return 文字水印的 bitmap
     */
    public static Bitmap createTextImage(String text, int textSize, String textColor, String bgColor, int padding) {

        Paint paint = new Paint();
        paint.setColor(Color.parseColor(textColor));
        paint.setTextSize(textSize);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);

        float width = paint.measureText(text, 0, text.length());
        float top = paint.getFontMetrics().top;
        float bottom = paint.getFontMetrics().bottom;

        Bitmap bm = Bitmap.createBitmap((int) (width + padding * 2), (int) ((bottom - top) + padding * 2), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bm);
        canvas.drawColor(Color.parseColor(bgColor));
        canvas.drawText(text, padding, -top + padding, paint);
        return bm;
    }

六.着色器代码

着色器代码与上一篇博文中的单纹理贴图是一样的:

1.img_vertex_shader.glsl

#version 300 es
 
layout (location = 0) in vec4 aPosition;         //把顶点坐标给这个变量, 确定要画画的形状
layout (location = 1) in vec4 aTexCoord;         //接收纹理坐标,接收采样器采样图片的坐标
 
//传给片元着色器 像素点
out vec2 vTexCoord;
 
void main()
{
    //内置变量 gl_Position ,我们把顶点数据赋值给这个变量 opengl就知道它要画什么形状了
    gl_Position = aPosition;
    vTexCoord = aTexCoord.xy;
}

2.img_fragtment_shader.glsl

#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
 
in vec2 vTexCoord; //纹理坐标,图片当中的坐标点
 
uniform sampler2D sampler;  //图片,采样器
 
out vec4 outColor;
 
void main(){
    outColor = texture(sampler, vTexCoord);
}

七.UI相关代码

Render、GLSurfaceView与Activity、Fragment等之间的实现逻辑请根据自己项目的实际情况去实现

这里只贴出Render在GLSurfaceView中设置的代码:

    mGLSurfaceView = rootView.findViewById(R.id.Img_Txt_GLSurfaceView);
    //设置GLES版本
    mGLSurfaceView.setEGLContextClientVersion(3);
    //创建Render对象,并将其设置到GLSurfaceView
    mImgTxtRender = new ImgTxtRender(getActivity());
    mGLSurfaceView.setRenderer(mImgTxtRender);
    mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

八.最终效果:

一张背景大图,一张水印小图,和一串水印文字:

OpenGLES:多纹理贴图,文字水印_第1张图片

你可能感兴趣的:(OpenGL/OpenGLES,android,图像处理,算法)