OpenGLES: single texture map

I. Overview

I have been neglecting blogging recently, so I will update some of the development process of OpenGLES during this period.

The first two OpenGLES blogs explained how to use OpenGLES to implement camera normal preview and multi-grid filters.

In the camera implementation process, although textures are used, they are only used to create a SurfaceTexture after generating a texture, and then use the SurfaceTexture to create a Surface. The texture is not used for image rendering, that is, texture mapping.

In the previous blog post "OpenGL: Texture", OpenGL was used to implement the texture mapping of the square box, and the texture generation, texture coordinates, filtering settings, wrapping methods, etc. were also introduced in detail.

This blog will explain the texture mapping of OpenGLES. The principles of the two are actually the same, but this blog is more focused on practical applications.

The following is mainly a code demonstration. Follow the code comments to understand the implementation process.

2.Render class:

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

    private final Context mContext;

    private float vertexData[] = {
            -1f, -1f,  //左下
            1f, -1f,   //右下
            -1f, 1f,   //左上
            1f, 1f,    //右上
    };

    //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[1];
    //顶点坐标
    private int aPosition;
    //纹理坐标
    private int aTexCoord;
    //采样器
    private int sampler;
    //顶点数据缓存
    private FloatBuffer vertexBuffer;
    //纹理数据缓存
    private FloatBuffer textureBuffer;
    //一个顶点有几个数据
    private int VERTEX_POSITION_SIZE = 2;
    //一个纹理点有几个数据
    private int TEXTURE_POSITION_SIZE = 2;

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

    @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);
        textureBuffer = ShaderUtils.getFloatBuffer(textureData);

        //创建纹理对象
        glGenTextures(textureId.length, textureId, 0);

        TextureUtils.LoadTexture(mContext, textureId[0], R.drawable.bg2);
    }

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

        /********* 顶点操作 **********/
        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);
    }
}

3. ShaderUtils related functions:

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 functions

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. Shader code

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

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

6. Implementation process of Render, GLSurfaceView, etc.

GLRender and its settings in GLSurfaceView:

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

There is also the loading of GLSurfeaceView in Activity or Fragment. The code for these regular operations will not be demonstrated in detail. Just implement it according to your actual development process.

7. Points to note

1. Use of samplers in fragment shaders

One thing to note is that in the previous fragment shader code that implemented the camera preview, the sampler used was:

uniform samplerExternalOES sCameraTexture;

The sampler used when rendering images is now:

uniform sampler2D sampler; 

In the process of implementing OpenGLES texture mapping, the sampler samplerExternalOES used to implement camera preview was initially directly used, but the image could never be rendered successfully.

This is because "sampler: samplerExternalOES" is specially used to sample YUV data in OpenGLES, so it must be used when implementing camera preview, but when rendering pictures, the regular "sampler Sampler2D" must be used.

8. Final effect

A cosmic starry sky map is used, please ignore the process of "open the app and click the button" .

The final presentation of the cosmic starry sky map is the final effect of the texture map to be achieved in this OpenGLES blog post :

Guess you like

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