OpenGLES小结

2020-01-17  本文已影响0人  Magic11
image.png

OpenGLES首先需要创建顶点着色器和片元着色器
代码采用gles语言编写
顶点着色器的代码如下:

//顶点着色器glsl
#define GET_STR(x) #x
static const char *s_vertexCode = GET_STR(
        attribute vec4 aPosition;    //顶点坐标
        attribute vec2 aTexCoord;    //纹理顶点坐标(和顶点坐标位置对应)
        varying   vec2 vTexCoord;    //输出的材质坐标(输出给片元着色器)
        void main() {
            vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
            gl_Position = aPosition;
        }
);

OpenGL的所有图形都是有顶点、直线、三角形组成,顶点坐标用于确定这些顶点、直线、三角形,然后再分别绘制这些顶点、直线和三角形
纹理坐标用于告诉OpenGL每个顶点坐标从纹理的哪个位置开始取值
图像的形状是一个长方形,需要使用两个三角形,所以顶点坐标需要四个(两个三角形有两个顶点重合),纹理坐标也需要四个顶点(类似顶点坐标)。
片元着色器的代码如下:

//片元着色器,软解码和部分x86硬解码
static const char *s_fragCodeYUV420P = GET_STR(
        precision mediump float;       //精度
        varying vec2 vTexCoord;        //顶点着色器传递的坐标
        uniform sampler2D yTexture;    //输入的材质(不透明灰度,单像素)
        uniform sampler2D uTexture;
        uniform sampler2D vTexture;
        void main() {
            vec3 yuv;
            vec3 rgb;
            yuv.r = texture2D(yTexture, vTexCoord).r;
            yuv.g = texture2D(uTexture, vTexCoord).r - 0.5;
            yuv.b = texture2D(vTexture, vTexCoord).r - 0.5;
            rgb = mat3(1.0,     1.0,      1.0,
                       0.0,     -0.39465, 2.03211,
                       1.13983, -0.58060, 0.0) * yuv;
            //输出像素颜色, 通过三层纹理叠加求出最终的rgb颜色值
            gl_FragColor = vec4(rgb, 1.0);
        }
);

OpenGL会将直线、三角形光栅化成一个个片段,然后分别对这些片段着色,片元着色器的作用就是对这些片段分别着色
如上述代码,片元着色器用了三层纹理,通过将yuv转化为rgb,最终求出每个片元的颜色值

着色器代码需要先进行编译,其过程如下:

static GLuint InitShader(const char *code, GLint type)
{
    //创建shader
    GLuint shader = glCreateShader(type);
    if (shader == 0)
    {
        XLOGE("glCreateShader %d failed!", type);
        return 0;
    }
    //加载shader
    glShaderSource(shader,
                   1,    //shader数量
                   &code, //shader代码
                   0);   //代码长度
    //编译shader
    glCompileShader(shader);

    //获取编译情况
    GLint status;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == 0)
    {
        XLOGE("glCompileShader failed!");
        return 0;
    }
    XLOGE("glCompileShader success!");
    return shader;
}

然后需要创建渲染程序,并把编译后的着色器附加到渲染程序,代码如下:

bool XShader::Init(XShaderType type)
{
    Close();
    //顶点和片元shader初始化
    //顶点shader初始化
    m_mutex.lock();
    m_vertexShader = InitShader(s_vertexCode, GL_VERTEX_SHADER);
    if (m_vertexShader == 0)
    {
        m_mutex.unlock();
        XLOGE("InitShader GL_VERTEX_SHADER failed!");
        return false;
    }
    XLOGE("InitShader GL_VERTEX_SHADER success! %d", type);

    //片元yuv420 shader初始化
    switch (type)
    {
        case XSHADER_YUV420P:
            m_fragmentShader = InitShader(s_fragCodeYUV420P, GL_FRAGMENT_SHADER);
            break;
        case XSHADER_NV12:
            m_fragmentShader = InitShader(s_fragCodeNV12, GL_FRAGMENT_SHADER);
            break;
        case XSHADER_NV21:
            m_fragmentShader = InitShader(s_fragCodeNV21, GL_FRAGMENT_SHADER);
            break;
        default:
            m_mutex.unlock();
            XLOGE("XSHADER format is error");
            return false;
    }

    if (m_fragmentShader == 0)
    {
        m_mutex.unlock();
        XLOGE("InitShader GL_FRAGMENT_SHADER failed!");
        return false;
    }
    XLOGE("InitShader GL_FRAGMENT_SHADER success!");


    /////////////////////////////////////////////////////////////
    //创建渲染程序
    m_program = glCreateProgram();
    if (m_program == 0)
    {
        m_mutex.unlock();
        XLOGE("glCreateProgram failed!");
        return false;
    }

    //渲染程序中加入着色器代码
    glAttachShader(m_program, m_vertexShader);
    glAttachShader(m_program, m_fragmentShader);

    //链接程序
    glLinkProgram(m_program);

    GLint status = 0;
    glGetProgramiv(m_program, GL_LINK_STATUS, &status);
    if (status != GL_TRUE)
    {
        m_mutex.unlock();
        XLOGE("glLinkProgram failed!");
        return false;
    }
    XLOGE("glLinkProgram success!");

    glUseProgram(m_program);

    //加入三维顶点数据 两个三角形组成正方形
    static float vertexArray[] = {
            1.0f,  -1.0f, 0.0f,
            -1.0f, -1.0f, 0.0f,
            1.0f,  1.0f,  0.0f,
            -1.0f, 1.0f,   0.0f,
    };
    //下面的代码主要用于向gles代码中的变量传值,首先要获取变量的位置,必须在编译程序后获取
    GLuint aPositionPos = (GLuint)glGetAttribLocation(m_program, "aPosition");
    glEnableVertexAttribArray(aPositionPos);
    //第一个参数表示该变量的位置,第二个参数表示该变量由几个分量组成,
    //倒数第二参数表示步长间隔是多少个字节,最后一个参数表示从哪个缓冲区或者数组中读取
    glVertexAttribPointer(aPositionPos, 3, GL_FLOAT, GL_FALSE, 12, vertexArray);

    //加入材质坐标数据
    static float textureArray[] = {
            1.0f, 0.0f, //右下
            0.0f, 0.0f,
            1.0f, 1.0f,
            0.0,  1.0
    };
    GLuint aTexCoordPos = (GLuint)glGetAttribLocation(m_program, "aTexCoord");
    glEnableVertexAttribArray(aTexCoordPos );
    glVertexAttribPointer(aTexCoordPos, 2, GL_FLOAT, GL_FALSE, 8, textureArray);


    //材质纹理初始化
    //设置纹理层
    glUniform1i(glGetUniformLocation(m_program, "yTexture"), 0); //对于纹理第1层
    switch (type) {
        case XSHADER_YUV420P:
            glUniform1i(glGetUniformLocation(m_program, "uTexture"), 1); //对于纹理第2层
            glUniform1i(glGetUniformLocation(m_program, "vTexture"), 2); //对于纹理第3层
            break;
        case XSHADER_NV21:
        case XSHADER_NV12:
            glUniform1i(glGetUniformLocation(m_program, "uvTexture"), 1); //对于纹理第2层
            break;
    }
    m_mutex.unlock();
    XLOGI("初始化Shader成功!");

    return true;
}

每次绘制需要传递纹理数据

virtual void Draw(unsigned char *data[], int width, int height)
{
    m_mtx.lock();
    shader.GetTexture(0, width, height, data[0]);  // Y

    if (type == XTEXTURE_YUV420P)
    {
        shader.GetTexture(1, width / 2, height / 2, data[1]);  // U
        shader.GetTexture(2, width / 2, height / 2, data[2]);  // V
    }
    else
    {
        shader.GetTexture(1, width / 2, height / 2, data[1], true);  // UV
    }
    shader.Draw();
    XEGL::Get()->Draw();
    m_mtx.unlock();
}

传递纹理数据的过程如下:

void XShader::GetTexture(unsigned int index, int width, int height, unsigned char *buf, bool isa)
{
    unsigned int format = GL_LUMINANCE;
    if (isa)
        format = GL_LUMINANCE_ALPHA;

    m_mutex.lock();
    //如下代码,每层纹理只会调用一次
    if (m_textureArray[index] == 0)
    {
        //材质初始化
        glGenTextures(1, &m_textureArray[index]);

        //设置纹理属性
        glBindTexture(GL_TEXTURE_2D, m_textureArray[index]);
        //缩小的过滤器
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        //设置纹理的格式和大小
        glTexImage2D(GL_TEXTURE_2D,
                     0,                         //细节基本 0默认
                     format,                    //gpu内部格式 亮度,灰度图
                     width,  height,           //拉升到全屏
                     0,                        //边框
                     format,                   //数据的像素格式 亮度,灰度图 要与上面一致
                     GL_UNSIGNED_BYTE,       //像素的数据类型
                     NULL                     //纹理的数据
        );
    }


    //激活第1层纹理,绑定到创建的opengl纹理
    glActiveTexture(GL_TEXTURE0 + index);
    glBindTexture(GL_TEXTURE_2D, m_textureArray[index]);
    //替换纹理内容
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, GL_UNSIGNED_BYTE, buf);
    m_mutex.unlock();
}

绘制的过程如下:

void XShader::Draw()
{
    m_mutex.lock();
    if (!m_program)
    {
        m_mutex.unlock();
        return;
    }
    //三维绘制
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    m_mutex.unlock();
}

显示的过程如下:

virtual void Draw()
{
    m_mutex.lock();
    if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE)
    {
        m_mutex.unlock();
        return;
    }
    eglSwapBuffers(m_eglDisplay, m_eglSurface);
    m_mutex.unlock();
}

参考:OpenGL ES应用开发实践指南 Android卷
https://learnopengl-cn.github.io/

上一篇下一篇

猜你喜欢

热点阅读