OpenGL.Shader: 6-glDrawArraysInstanced / built-in variable gl_VertexID

OpenGL.Shader: 6-glDrawArraysInstanced + built-in variable gl_VertexID to draw self-tracking "three billboards"

 

First of all, you can see the effect. This effect can be observed from the current super hot "Catch the Demon Together". Now we come to learn the knowledge of the shader.

First of all, the code organization of the model and shader program has been slightly changed this time to facilitate better understanding and learning. Let's first define the model code GreeneryMgr.hpp for that pile of green grass

#pragma once
#ifndef GREENERY_MGR_HPP
#define GREENERY_MGR_HPP

#include <GLES3/gl3.h>
#include <vector>
#include "../common/CELLMath.hpp"
#include "../common/Camera3D.hpp"
#include "../program/GreeneryShaderProgram.hpp"

class GreeneryMgr {

private:
    std::vector<CELL::float3>   mGrassesPos;
    GLuint                      mTexGrasses;
    GLuint                      vbo;
    GreeneryShaderProgram       _program;

public:
    GreeneryMgr() {}
    ~GreeneryMgr() {}

    void    init(GLuint tex)
    {
        mTexGrasses   =   tex;
        // 初始化着色器程序
        _program.initialize();
        //  每一棵小草堆的位置索引
        for (float x = -3 ; x < 6 ; x += 3)
        {
            for (float z = -3 ; z < 6 ; z += 3)
            {
                if(x==0&&z==0) continue;// 留位置给中间的正方体
                mGrassesPos.push_back(CELL::float3(x,-1,z));
            }
        }
        // 把草堆实例的位置索引寄存到vbo
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(mGrassesPos.size()*sizeof(CELL::float3)),
                     &mGrassesPos.front(), GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER,0);
    }


    void    render(Camera3D& camera)
    {
        CELL::matrix4   MVP     =   camera._matProj * camera._matView;

        _program.begin();
        {
            glActiveTexture(GL_TEXTURE0);
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D,  mTexGrasses );
            glUniform1i(_program._texture, 0);

            glUniform3f(_program._rightDir,
                        static_cast<GLfloat>(camera._right.x),
                        static_cast<GLfloat>(camera._right.y),
                        static_cast<GLfloat>(camera._right.z));
            glUniform3f(_program._upDir, 0,1,0);
            //glUniform3f(_program._upDir,
            //            static_cast<GLfloat>(camera._up.x),
            //            static_cast<GLfloat>(camera._up.y),
            //            static_cast<GLfloat>(camera._up.z));

            glUniformMatrix4fv(_program._mvp, 1, GL_FALSE, MVP.data());

            /// 这个将vbo点数据传递给shader
            glBindBuffer(GL_ARRAY_BUFFER, vbo);
            glVertexAttribPointer(_program._position, 3, GL_FLOAT, GL_FALSE, sizeof(CELL::float3), 0);
            /// 启用_position顶点属性的多实例特性
            glVertexAttribDivisor(_program._position, 1);
            /// 绘制函数,完成绘制
            glDrawArraysInstanced(GL_TRIANGLES, 0, 6, mGrassesPos.size());
            /// 禁用_position顶点属性的多实例特性
            glVertexAttribDivisor(_program._position, 0);
            /// 解绑vbo
            glBindBuffer(GL_ARRAY_BUFFER,0);
        }
        _program.end();
    }

};

#endif // GREENERY_MGR_HPP

The initialization function init(GLuint tex) of GreeneryMgr actually only does one thing, prepares the location attribute points of 8 instances (Instance) in the haystack, among which we use float3(x,y,z) to represent a green grass instance Key location point. Then create a VBO to store the location key point array of the instance in the GPU memory cache (for VBO knowledge, please refer to here ) and then save the externally passed in rendering texture ID.

Then enter the render rendering function. The input is the reference of the Camera3D camera type introduced in the previous chapter, the texture ID is passed in to bind the texture, and the _rightDir and _upDir of Camera3D, and the 8 haystack instance locations bound to vbo are key The points are passed into the shader program. Calling the new drawing function glDrawArraysInstanced, before calling, you must first enable the multi-instance feature of the _position vertex attribute glVertexAttribDivisor(_program._position, 1); (1 is enabled and 0 is disabled)

What is the working principle of these two functions? Let's finish the main content of this chapter first: shader program GreeneryShaderProgram within version 300 es; combine the shader and then go back and elaborate. Next look at the shader program GreeneryShaderProgram

 

 

 

class GreeneryShaderProgram : public ShaderProgram
{
public:
    GLint _position;
    GLint _mvp     ;
    GLint _rightDir;
    GLint _upDir   ;
    GLint _texture ;

public:
    GreeneryShaderProgram() {}
    ~GreeneryShaderProgram() {}

    /// 初始化函数
    virtual void    initialize()
    {
        const char * vertexShaderResourceStr =
        {
               "#version 320 es\n\
                uniform     vec3     _rightDir; \n\
                uniform     vec3     _upDir; \n\
                uniform     mat4     _mvp; \n\
                in          vec3     _pos; \n\
                out         vec2     _texcoord;\n\
                void main() \n\
                {\n\
                    const vec2 uvData[6] = vec2[6]( \n\
                                            vec2(0.0, 0.0),\n\
                                            vec2(0.0, 1.0),\n\
                                            vec2(1.0, 1.0),\n\
                                            vec2(0.0, 0.0),\n\
                                            vec2(1.0, 1.0),\n\
                                            vec2(1.0, 0.0) );\n\
                    _texcoord               =   uvData[gl_VertexID];\n\
                    float _texcoord_x       =   _texcoord.x;\n\
                    float _texcoord_y       =   _texcoord.y;\n\
                    vec3 newPs      =   _pos + ((_texcoord_x - 0.5)* 4.0)* _rightDir + (_texcoord_y * 4.0) * _upDir;\n\
                    gl_Position     =   _mvp * vec4(newPs.x,newPs.y,newPs.z,1);\n\
                }\n"
        };

        const char * fragmentShaderResourceStr =
        {
                "#version 320 es\n\
                precision mediump float;\n\
                uniform   sampler2D  _texture;\n\
                in        vec2       _texcoord;\n\
                //use your own output instead of gl_FragColor \n\
                out vec4 fragColor;\n\
                void main()\n\
                {\n\
                    vec4   color   =   texture(_texture, vec2(_texcoord.x, 1.0-_texcoord.y)); \n\
                    if(color.a < 0.2) discard;\n\
                    fragColor   =   color;\n\
                }\n"
        };

        programId = ShaderHelper::buildProgram(vertexShaderResourceStr, fragmentShaderResourceStr);

        _position   =   glGetAttribLocation (programId, "_pos");
        _mvp        =   glGetUniformLocation(programId, "_mvp");
        _rightDir   =   glGetUniformLocation(programId, "_rightDir");
        _upDir      =   glGetUniformLocation(programId, "_upDir");
        _texture    =   glGetUniformLocation(programId, "_texture");

    }

    /**
    *   使用程序
    */
    virtual void    begin()
    {
        glUseProgram(programId);
        glEnableVertexAttribArray(static_cast<GLuint>(_position));
    }
    /**
    *   使用完成
    */
    virtual void    end()
    {
        glDisableVertexAttribArray(static_cast<GLuint>(_position));
        glUseProgram(0);
    }
};

Begin to focus on the analysis of the vertex shader program, first come to the vertex shader

#version 320 es
uniform     vec3     _rightDir; 
uniform     vec3     _upDir; 
uniform     mat4     _mvp; 
in              vec3     _pos; 
out            vec2     _texcoord;
void main() 
{
    const vec2 uvData[6] = vec2[6]( 
                            vec2(0.0, 0.0),
                            vec2(0.0, 1.0),
                            vec2(1.0, 1.0),
                            vec2(0.0, 0.0),
                            vec2(1.0, 1.0),
                            vec2(1.0, 0.0) );
    _texcoord               =   uvData[gl_VertexID];
    float _texcoord_x       =   _texcoord.x;
    float _texcoord_y       =   _texcoord.y;
    vec3 newPs      =   _pos + ((_texcoord_x - 0.5)* 4.0)* _rightDir + (_texcoord_y * 4.0) * _upDir;
    gl_Position     =   _mvp * vec4(newPs.x,newPs.y,newPs.z,1);
}

1. The first line of code declares that the current shader version is 320 es. A simple version statement but it took nearly 2 days. Because in the traditional information, the version number is generally #version 300 normal mode or #version 300 core core mode, and then in the process of transplanting to the mobile phone, when I fill in these two versions, it will appear when compiling: Could not compile vertex shader : ERROR: Invalid #version Finally, it is found that the glsl corresponding to opengles on the mobile device also carries es, so the version is also es mode. You can also check the current ShaderLanguage version through the following GLAPI.

const GLubyte * glslVersionStr = glGetString(GL_SHADING_LANGUAGE_VERSION);
LOGW("GL_SHADING_LANGUAGE_VERSION = %s", glslVersionStr);
// Test machine Nexus5X output result: GL_SHADING_LANGUAGE_VERSION = OpenGL ES GLSL ES 3.20
// Since we have used GLSL to create E320ES When upgrading to 3, that is, new EglCore(NULL, FLAG_TRY_GLES3);

2. In the GLSL 3 version, the input variable attribute of the vertex shader is uniformly changed to in, the varying output is changed to out, and the varying of the corresponding fragment shader is in. This is easy to understand.

3. Also in the GLSL 3 version, you can define an array in the main function, and the syntax is the same as C. Through the use of arrays, combined with built-in variables, we can do a lot of cpu for loop operations on the gpu. For example, we used gl_VertexID this time. This is also a new built-in variable in the GLSL 3 version, which represents the ID of the painting texture.

We return to the render function above, glVertexAttribDivisor enables the multi-instance feature of the _position vertex attribute, and then calls glDrawArraysInstanced(GL_TRIANGLES, 0, 6, mGrassesPos.size()); the drawing function to complete the drawing. The passed-in parameters look the same as the previous glDrawArrays (GL_TRIANGLES, 0, 6) , but our glDrawArrays is for one drawing instance, glDrawArrays only draws one object at a time; glDrawArraysInstanced is for multiple (parameter four Number) drawing examples, multiple objects can be drawn at one time.

So what is the relationship between glDrawArraysInstanced and gl_VertexID? In fact, not only glDrawArraysInstanced, glDrawArrays and glDrawElements are related. In our previous learning, every time we draw a texture, we have to draw 2 triangles * 3 vertices. The previous learning is to prepare the texture vertex data in the application layer code. This time glDrawArraysInstanced obviously did not preprocess the vertex data. We just pass in the position of each instance, and each instance triggers the vertex shader 6 times. The vertex shader triggered for these 6 times is the value of gl_VertexID, and the value range is 0~5; glDrawArraysInstanced(GL_TRIANGLES, 1, 6, 20); Then 20 instances are drawn, each instance corresponds to triggering the vertex shader 6 times, and the corresponding gl_VertexID value is (1~6) each time

4. After understanding the relationship between render's glDrawArraysInstanced and the corresponding gl_VertexID, let's continue to look at the vertex shader code content. First define a static array to store the texture coordinates of 6 vertices. Then analyze the logic based on the schematic diagram at the bottom left: the red dots represent the position coordinates of the grass, and the yellow ones are the four texture points of the grass image.

 

float  _texcoord_x       =   _texcoord.x;
float  _texcoord_y       =   _texcoord.y;
vec3 newPs      =   _pos + ((_texcoord_x - 0.5)* 4.0)* _rightDir + (_texcoord_y * 4.0) * _upDir;
gl_Position        =   _mvp * vec4(newPs.x,newPs.y,newPs.z,1);

Take a red position coordinate point as an example (-3, 0, -3). Under the texture, retrieve the uv array according to gl_VertexID. Take the upper right corner (1, 1) as an example. In the original _pos (-3, 0, -3) ) Extension along the right direction of Camera3D's _rightDir, when the Camera3D lens changes, the vertices can be changed immediately; the abscissa of the texture -0.5 is used because the original _pos position is the middle point; the 4.0 array serves as the image The size is the total length of the extension. This value can be defined at the application layer. Similarly in the vertical direction, extend 4.0 lengths along the _upDir head of Camera3D.

After this calculation, the coordinate point of the upper right corner of the texture is (-3, 0, -3)+_rightDir*(2)+_upDir*(4), if _rightDir is 45° along the xz plane, it is (1 , 0, 1) _upDir is vertically upward (0, 1, 0) then the result is (-3, 0, -3) + (2, 0, 2) + (0, 4, 0) = (-1, 4, -1); The dynamic calculation of vertex coordinates is perfectly realized on the GPU.

 

5. The next step is to analyze the fragment shader, as shown below. Note that the built-in gl_FragColor is cancelled, we need to manually set the output variable fragColor. The texture sampling function texture2d was renamed texture. Why the texture coordinate y needs to be reversed? This is caused by the Android system. If you have any questions, please refer to here . According to the transparency channel of the sampled color points, we filter all those less than 0.2 without rendering, so that we can see the scene behind.

#version 320 es
precision mediump float;
uniform   sampler2D  _texture;
in        vec2       _texcoord;
//use your own output instead of gl_FragColor
out vec4 fragColor;
void main()
{
    vec4   color   =   texture(_texture, vec2(_texcoord.x, 1.0-_texcoord.y));
    if(color.a < 0.2) discard;
    fragColor   =   color;
}

 

Finally, briefly mention the usage method:

void GL3DRender::surfaceCreated(ANativeWindow *window)
{
    // 加载纹理
    sprintf(res_name, "%s%s", res_path, "grass.png");
    texture_greenery_id = TextureHelper::createTextureFromImage(res_name);
    // 初始化构造
    greeneryMgr.init(texture_greenery_id);
}

void GL3DRender::renderOnDraw(double elpasedInMilliSec)
{
    // 传入Camera3D摄像头进行渲染
    greeneryMgr.render(mCamera3D);
    mWindowSurface->swapBuffers();
}

CMakeList upgrade GLES2->GLES3 support

target_link_libraries(
        native-egl
        EGL
#       GLESv2    
        GLESv3
        android
        zip
        log)

Project address: https://github.com/MrZhaozhirong/NativeCppApp  reference GreeneryMgr.hpp & GreeneryShaderProgram.hpp

Guess you like

Origin blog.csdn.net/a360940265a/article/details/90183347