OpenGLES: multi-texture maps, text watermarks

I. Overview

The previous blog explained how OpenGLES implements single texture mapping

Just drawing a picture is not enough

This blog explains how to achieve image and text watermark effects through multi-texture mapping

On the basis of single texture mapping, there are two main differences between multi-texture mapping:

  • The generation and binding of textures are changed from single to multiple
  • The text content is first converted to Bitmap and then textured.

Once you know these two points, it will be easier to implement

In this blog, I will use multi-texture to achieve the effect of a large background image, a small watermark image and a string of text watermarks.

The final effect will be shown at the end.

Without further ado, let’s start the main story!

2.Render class code

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);
    }
}

3.ShaderUtils related code:

The function of ShaderUtils is the same as that in the single texture map blog post.

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;
    }

4.TextureUtils related code

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);
    }

5.BitmapUtils related code

Convert text to 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;
    }

6. Shader code

The shader code is the same as the single texture map in the previous blog post:

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_fragment_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);
}

7. UI related code

Please implement the implementation logic between Render, GLSurfaceView, Activity, Fragment, etc. according to the actual situation of your project.

Only the code set by Render in GLSurfaceView is posted here:

    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);

8. Final effect:

A large background image, a small watermark image, and a string of watermark text:

Guess you like

Origin blog.csdn.net/geyichongchujianghu/article/details/133172420