Simple2D-21(重构)渲染部分

  以前 Simple2D 的渲染方法是先设置 Pass,然后添加顶点数据,相同 Pass 的顶点数据会合并在一起。当设置新的 Pass 时,将旧的 Pass 和对应的顶点数据添加到渲染数组中。最后在帧结束时遍历渲染数组,根据 Pass 设置 OpenGL 状态,绘制对应的顶点数据。

  这次改为更加简单的方法,类似状态机。设置混合状态(Blend)、着色程序(Shader Program)。渲染部分由 Graphics ContextRendererShader Program 组成:

  Graphics Context:图形上下文,设置渲染的混合状态(Blend)和使用的着色程序(Shader Program)。所有顶点数据的渲染都会使用 Graphics Context 当前使用的 Blend 和 Shader Program,直到 Graphics Context 设置新的 Blend 或 Shader Program。当 Blend 或 Shader Program 改变时会将上一个状态的顶点数据立即进行渲染,这是和以前 Simple2D 渲染方法的区别。

  这次去掉了裁剪测试、深度测试、模板测试,这些是非必须使用的功能。保留了混合是因为要渲染透明纹理。

  Renderer:渲染器,对顶点数据进行合并和管理。需要渲染顶点数据时,会将数据传递给 Graphics Context,Graphics Context 然后根据当前设置的 Blend 和 Program 绘制数据。

  Shader Program:着色程序,使用着色器可以实现炫酷的效果。这次重构的 Simple2D 可以使用标准的 Program 和自定义的 Program 渲染顶点数据,通过自定义 Program 实现标准 Program 不能实现的效果。

 

  因此 Simple2D 则可以去掉 Pass 类和 BlockAllocator 类,以前使用 Simple2D 渲染顶点数据时要为顶点分配空间,渲染后又要释放空间,中间的步骤十分麻烦。为什么要使用如此麻烦的方法,纯粹是我脑袋瓦特了。

 

  实现


 

  Renderer

  Renderer 内部有两个一定大小缓冲区,用于存储顶点数据和索引数据:

 

        static const int vertex_buffer_size = 1024 * 1024;
        static const int index_buffer_size = 40000;
        char vVertexBuffer[vertex_buffer_size];        /* 用于合并顶点的缓冲区 */
        uint32 vIndexBuffer[index_buffer_size];        /* 用于合并索引的缓冲区 */

 

  渲染一个正方形,需要 4 个顶点和 6 个索引。通过 AppendRenderData( ) 函数将顶点数据和索引数据传递给 Renderer,然后 Renderer 将顶点数据和索引数据拷贝到缓冲区中,当缓冲区的空间不足时,会调用 Flush( ) 函数将渲染数据提交给 Graphics Context 进行渲染。AppendRenderData( ) 是一个模板函数,通过模板的特性可以知道顶点结构的大小,从而进行拷贝操作:

     template<class Type>
        void AppendRenderData(Type* vertex_data, int vertex_count, uint32* index_data, int index_count, PrimType type)
        {
            int total_vertex_count = vertex_buffer_size / sizeof(Type);
            if ( total_vertex_count - nVertexCount < vertex_count || index_buffer_size - nIndexCount < index_count ) {
                this->Flush();
            }

            for ( int i = 0; i < index_count; i++ ) {
                vIndexBuffer[nIndexCount + i] = nVertexCount + index_data[i];
            }

            char* data_header = vVertexBuffer + nVertexCount * sizeof(Type);
            memcpy(data_header, ( char* ) vertex_data, vertex_count * sizeof(Type));

            nVertexCount += vertex_count;
            nIndexCount += index_count;
            primType = type;
        }

  当然也可以通过函数参数传递顶点结构的大小,但这样太麻烦了。

  如果要渲染纹理,同时希望减少 drawcall。因为当时局限于一个着色程序绑定一张纹理的想法,所以以前 Simple2D 通过合并相同纹理的顶点数据以达到一张纹理一个 drawcall,可以减小切换纹理而带来的开销。但是着色程序是可以绑定多张纹理的,可以在顶点数据中添加一个索引的数据,指定使用哪一个绑定的纹理,这样可以达到多张纹理一个 drawcall 了。下面是 Simple2D 定义的纹理渲染标准着色程序:

    const char* Sprite_Vertex = R"(
        #version 330 core

        layout(location = 0) in vec3 Position;
        layout(location = 1) in vec2 Texcoord;
        layout(location = 2) in vec4 Color;
        layout(location = 3) in float Texindex;

        uniform mat4x4 MVPMatrix;

        out vec2 texcoord;
        out vec4 color;
        flat out int texindex;

        void main()
        {
            gl_Position =  MVPMatrix * vec4(Position, 1.0f);
            color = Color;
            texcoord = Texcoord;
            texindex = int(Texindex);
        }
    )";

 

    const char* Sprite_Fragment = R"(
        #version 330 core

        in vec2 texcoord;
        in vec4 color;
        flat in int texindex;

        uniform sampler2D Texture0;
        uniform sampler2D Texture1;
        uniform sampler2D Texture2;
        uniform sampler2D Texture3;
        uniform sampler2D Texture4;
        uniform sampler2D Texture5;
        uniform sampler2D Texture6;
        uniform sampler2D Texture7;
        uniform sampler2D Texture8;
        uniform sampler2D Texture9;
        uniform sampler2D Texture10;
        uniform sampler2D Texture11;
        uniform sampler2D Texture12;
        uniform sampler2D Texture13;
        uniform sampler2D Texture14;
        uniform sampler2D Texture15;

        vec4 SampleTexture(int index)
        {
            switch( index )
            {
            case 0: return texture(Texture0, texcoord);
            case 1: return texture(Texture1, texcoord);
            case 2: return texture(Texture2, texcoord);
            case 3: return texture(Texture3, texcoord);
            case 4: return texture(Texture4, texcoord);
            case 5: return texture(Texture5, texcoord);
            case 6: return texture(Texture6, texcoord);
            case 7: return texture(Texture7, texcoord);
            case 8: return texture(Texture8, texcoord);
            case 9: return texture(Texture9, texcoord);
            case 10: return texture(Texture10, texcoord);
            case 11: return texture(Texture11, texcoord);
            case 12: return texture(Texture12, texcoord);
            case 13: return texture(Texture13, texcoord);
            case 14: return texture(Texture14, texcoord);
            case 15: return texture(Texture15, texcoord);
            default: return vec4(1.0, 1.0, 1.0, 1.0);
            }
        }

        void main()
        {
            gl_FragColor = SampleTexture(texindex) * color;
        }
    )";

  这个着色程序一次可以绑定 16 张纹理,这意味着你可以一个 drawcall 渲染 16 张纹理。所以在 Renderer 中设置一个纹理数组:

        int nCurrentTextureCount;
        static const int nMaxNumberOfTexture = 16;
        GLuint vTextures[nMaxNumberOfTexture];

  储存渲染纹理,在渲染前绑定纹理到着色程序即可:

    void Renderer::BindTexture()
    {
        for ( int i = 0; i < nCurrentTextureCount; i++ ) {
            glActiveTexture(GL_TEXTURE0 + i);
            glBindTexture(GL_TEXTURE_2D, vTextures[i]);
        }
    }

  其中纹理存储在数组中的位置就是纹理在着色程序中的纹理索引。

 

  Shader Program

  Simple2D 内置有两个标准着色程序  Standard Program,分别用于渲染纹理和几何图形。如果有特别需要的话,可以自定义着色程序,为此需要处理 Uniform 数据,所以定义一个类 ProgramEffect 来管理 Uniform 数据。

 

  Graphics Context

  Graphics Context 用于设置 Blend 和 Shader Program 以及渲染顶点数据,实现较为简单。

 

  源码下载:Simple2D-20.rar

 

posted @ 2017-09-01 16:14  为了邮箱5  阅读(714)  评论(0编辑  收藏  举报