基于OpenGL编写一个简易的2D渲染框架-03 渲染基本几何图形

  阅读文章前需要了解的知识,你好,三角形:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/

  要渲染出几何图形来,首先需要变换矩阵,那么自然就需要一个数学库了。本来想用 glm 库的,但用不惯这个库,只好编写一个简单的数据库了。这个库暂不需要复杂的功能,就几个向量类和 4x4 的矩阵类,矩阵类有一个重要的函数,用于创建正交矩阵(由于这是一个简单的项目,就不需要 LookAt(视图) 矩阵了)。

    Matrix4 Matrix4::ortho(GLfloat fLeft, GLfloat fRight, GLfloat fBottom, GLfloat fTop, GLfloat fNear, GLfloat fFar)
    {
        Matrix4 mat4 = Matrix4::ZERO;

        mat4.m[0][0] = 2 / (fRight - fLeft);
        mat4.m[1][1] = 2 / (fTop - fBottom);
        mat4.m[2][2] = 2 / (fNear - fFar);
        mat4.m[3][3] = 1;

        mat4.m[0][3] = -(fRight + fLeft) / (fRight - fLeft);
        mat4.m[1][3] = -(fTop + fBottom) / (fTop - fBottom);
        mat4.m[2][3] = (fNear + fFar) / (fNear - fFar);

        return mat4;
    }

对于这个矩阵的推导感兴趣的话,这里推荐一篇文章:http://blog.csdn.net/popy007/article/details/4126809

接着需要着色器程序,可以渲染几何图形就行了

const GLchar *shader_vs = "#version 330 core
"
"layout (location = 0) in vec3 position;
"
"layout (location = 1) in vec4 color;
"
"uniform mat4 projection;
"
"out vec4 Vcolor;
"
"void main(){
"
"gl_Position = vec4(position, 1.0f);
"
"Vcolor = color;
"
"}";

const GLchar *shader_frag = "#version 330 core
"
"out vec4 color;
"
"in vec4 Vcolor;
"
"void main(){
"
"color = Vcolor;
"
"}";

创建着色程序并绑定

    void GraphicsContext::createShaderProgram()
    {
        /* 创建顶点作色器  */
        vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, 1, &shader_vs, NULL);
        glCompileShader(vertexShader);

        GLint success;
        GLchar infoLog[512];
        glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
        if ( !success ) {
            glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
            return;
        }

        /* 创建片段着色器 */
        fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, 1, &shader_frag, NULL);
        glCompileShader(fragmentShader);

        glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
        if ( !success ) {
            glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
            return;
        }

        /* 创建着色程序 */
        shaderProgram = glCreateProgram();
        glAttachShader(shaderProgram, vertexShader);
        glAttachShader(shaderProgram, fragmentShader);
        glLinkProgram(shaderProgram);

        glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
        if ( !success ) {
            glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
            return;
        }
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        /* 使用着色程序 */
        glUseProgram(shaderProgram);
    }

准备阶段已经完成,接下来开始渲染几何图形,新建一个渲染器类 Renderer,渲染顶点数据。此外,还有一个储存顶点数据的类

        class VertexData
        {
        public:
            std::vector<Vec3> positions;
            std::vector<Vec2> texcoords;
            std::vector<Color> colors;
            std::vector<GLuint> indices;

            int nPositionCount;
            int nIndexCount;
            bool bHasTexcoord;

            RenderType renderType;

            void clear()
            {
                nPositionCount = 0;
                nIndexCount = 0;
            }

            void resize(int positionCount, int indexCount)
            {
                if ( positions.size() - nPositionCount < positionCount ) {
                    positions.resize(positions.size() + positionCount);
                    colors.resize(colors.size() + positionCount);

                    if ( bHasTexcoord ) texcoords.resize(texcoords.size() + positionCount);
                }
                if ( indices.size() - nIndexCount < indexCount ) {
                    indices.resize(indices.size() + indexCount);
                }
            }

            void pushData(const Vec3& pos, const Color& color)
            {
                positions[nPositionCount] = pos;
                colors[nPositionCount++] = color;
            }

            void pushData(const Vec3& pos, const Vec2& texcoord, const Color& color)
            {
                positions[nPositionCount] = pos;
                texcoords[nPositionCount] = texcoord;
                colors[nPositionCount++] = color;
            }

            void pushIndex(GLuint index)
            {
                indices[nIndexCount++] = index;
            }
        };

主要储存顶点位置,顶点颜色,纹理坐标(现在先不用)和顶点的索引。

渲染类型

    enum RenderType
    {
        RENDER_TYPE_LINES,
        RENDER_TYPE_TRIANGLES,
        RENDER_TYPE_TEXTURE
    };

渲染单元的结构

    struct RenderUnit
    {
        Vec3* pPositions;
        int nPositionCount;

        Color* pColors;
        bool bSameColor;

        GLuint* pIndices;
        int nIndexCount;

        RenderType renderType;
    };

渲染器头文件

#pragma once
#include "Common.h"
#include "Math.h"

#include <vector>

namespace Simple2D
{
    enum RenderType
    {
        RENDER_TYPE_LINES,
        RENDER_TYPE_TRIANGLES,
        RENDER_TYPE_TEXTURE
    };

    struct RenderUnit
    {
        Vec3* pPositions;
        int nPositionCount;

        Color* pColors;
        bool bSameColor;

        GLuint* pIndices;
        int nIndexCount;

        RenderType renderType;
    };

    class DLL_export Renderer
    {
        class VertexData
        {
        public:
            std::vector<Vec3> positions;
            std::vector<Vec2> texcoords;
            std::vector<Color> colors;
            std::vector<GLuint> indices;

            int nPositionCount;
            int nIndexCount;
            bool bHasTexcoord;

            RenderType renderType;

            void clear()
            {
                nPositionCount = 0;
                nIndexCount = 0;
            }

            void resize(int positionCount, int indexCount)
            {
                if ( positions.size() - nPositionCount < positionCount ) {
                    positions.resize(positions.size() + positionCount);
                    colors.resize(colors.size() + positionCount);

                    if ( bHasTexcoord ) texcoords.resize(texcoords.size() + positionCount);
                }
                if ( indices.size() - nIndexCount < indexCount ) {
                    indices.resize(indices.size() + indexCount);
                }
            }

            void pushData(const Vec3& pos, const Color& color)
            {
                positions[nPositionCount] = pos;
                colors[nPositionCount++] = color;
            }

            void pushData(const Vec3& pos, const Vec2& texcoord, const Color& color)
            {
                positions[nPositionCount] = pos;
                texcoords[nPositionCount] = texcoord;
                colors[nPositionCount++] = color;
            }

            void pushIndex(GLuint index)
            {
                indices[nIndexCount++] = index;
            }
        };

    public:
        Renderer();
        ~Renderer();

        void render();
        void renderVertexData(VertexData& vertexData);

        void pushRenderUnit(const RenderUnit& unit);

    private:
        void initBuffers();
        Vec3 tranformPosition(Vec3& pos);

    private:
        VertexData triangleData;
        VertexData lineData;

        GLuint positionBuffer;
        GLuint colorBuffer;
        GLuint indexBuffer;
        GLuint VAO;

        Matrix4 mTransformMatrix;
    };
}

triangleData,储存绘制三角形图元的顶点数据

lineData,储存绘制线段的顶点数据

RenderUnit类,通过 pushRenderUnit 函数, 将顶点数据传到渲染器,然后渲染器将相同渲染类型的 RenderUnit 数据储存到同一个顶点数据缓冲区(VertexData)。

    void Renderer::pushRenderUnit(const RenderUnit& unit)
    {
        VertexData* vertexData = nullptr;
        if ( unit.renderType == RENDER_TYPE_TRIANGLES ) {
            vertexData = &triangleData;
        }
        else if ( unit.renderType == RENDER_TYPE_LINES ) {
            vertexData = &lineData;
        }

        /* 填充数据 */
        vertexData->resize(unit.nPositionCount, unit.nIndexCount);

        int baseIndex = vertexData->nPositionCount;
        for ( int i = 0; i < unit.nPositionCount; i++ ) {
            if ( unit.bSameColor ) {
                vertexData->pushData(tranformPosition(unit.pPositions[i]), unit.pColors[0]);
            }
            else {
                vertexData->pushData(tranformPosition(unit.pPositions[i]), unit.pColors[i]);
            }
        }
        for ( int i = 0; i < unit.nIndexCount; i++ ) {
            vertexData->pushIndex(baseIndex + unit.pIndices[i]);
        }
    }

在函数中,对顶点位置向量进行了矩阵变换

    Vec3 Renderer::tranformPosition(Vec3& pos)
    {
        return mTransformMatrix * pos;
    }

这个变换矩阵主要一个正交矩阵和一个变换矩阵组成

        Matrix4 ortho = Matrix4::ortho(0, 800, 600, 0, -1, 1);
        Matrix4 tranform = Matrix4::makeTransform(Vec3(0, 600, 0), Vec3(1, -1, 1));
        mTransformMatrix = ortho * tranform;

因为坐标原点在左上角,并且 Y 轴朝下为正。所以通过一个变换矩阵 transform 变换到左下角,Y 轴朝上为正。

最后在函数 renderVertexData 中渲染出来

    void Renderer::renderVertexData(VertexData& vertexData)
    {
        /* 填充顶点数据  */
        glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof( Vec3 ) * vertexData.nPositionCount, &vertexData.positions[0], GL_STATIC_DRAW);

        glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof( Color ) * vertexData.nPositionCount, &vertexData.colors[0], GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof( GLuint ) * vertexData.nIndexCount, &vertexData.indices[0], GL_STATIC_DRAW);
        glBindVertexArray(0);

        glBindVertexArray(VAO);
        switch ( vertexData.renderType ) {
        case RENDER_TYPE_TRIANGLES:
            glDrawElements(GL_TRIANGLES, vertexData.nIndexCount, GL_UNSIGNED_INT, 0);
            break;
        case RENDER_TYPE_LINES:
            glDrawElements(GL_LINES, vertexData.nIndexCount, GL_UNSIGNED_INT, 0);
            break;
        }
        glBindVertexArray(0);

        vertexData.clear();
    }

为了方便绘制几何图形,创建一个画布类 Canvas2D

#pragma once
#include "Common.h"
#include "Math.h"

#include <vector>

namespace Simple2D
{
    class Renderer;

    class DLL_export Canvas2D
    {
    public:
        Canvas2D(Renderer* renderer);
        ~Canvas2D();

        void drawLine(int x1, int y1, int z1, int x2, int y2, int z2, Color& color);

        void drawCircle(const Vec3& center, int radius, Color& color, int nSlice = 36);
        void fillCircle(const Vec3& center, int radius, int degrees, Color& color);
        void fillCircle(const Vec3& center, int in_radius, int out_radius, int beginAngle, int endAngle, Color& color);

        void drawRect(float x, float y, float w, float h, Color color);
        void fillRect(float x, float y, float w, float h, Color color);

        void drawTriangles(Vec3* positions, Color* colors, int positionCount, GLuint* indices, int indexCount, bool sameColor = true);
        void drawLines(Vec3* positions, Color* colors, int positionCount, bool sameColor = true);

    private:
        void resizeVector(int positionCount, int indexCount);

        Renderer* pRenderer;

        std::vector<Vec3> vPositions;
        int nPositionCount;

        std::vector<GLuint> vIndices;
        int nIndexCount;
    };
}

如果要绘制一个矩形,需要准备顶点数据

    void Canvas2D::fillRect(float x, float y, float w, float h, Color color)
    {
        this->resizeVector(4, 6);

        vPositions[0].set(x + 0, y + 0, 0);
        vPositions[1].set(x + 0, y + h, 0);
        vPositions[2].set(x + w, y + h, 0);
        vPositions[3].set(x + w, y + 0, 0);

        vIndices[0] = 0;
        vIndices[1] = 2;
        vIndices[2] = 1;
        vIndices[3] = 0;
        vIndices[4] = 3;
        vIndices[5] = 2;

        this->drawTriangles(&vPositions[0], &color, 4, &vIndices[0], 6);
    }

然后在函数 drawTriangle 中将数据传递到 渲染器

    void Canvas2D::drawTriangles(Vec3* positions, Color* colors, int positionCount, GLuint* indices, int indexCount, bool sameColor)
    {
        static RenderUnit unit;
        unit.pPositions = positions;
        unit.nPositionCount = positionCount;
        unit.pIndices = indices;
        unit.nIndexCount = indexCount;
        unit.pColors = colors;
        unit.bSameColor = sameColor;
        unit.renderType = RENDER_TYPE_TRIANGLES;

        pRenderer->pushRenderUnit(unit);
    }

最后在主函数中绘制几个图形

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    RenderWindow window(DEFAULT_WIN_W, DEFAULT_WIN_H);
    GraphicsContext graphicsContext(&window);

    
    Canvas2D canvas(graphicsContext.getRenderer());

    MSG msg = { 0 };
    while ( msg.message != WM_QUIT ) {
        if ( PeekMessage(&msg, 0, 0, 0, PM_REMOVE) ) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else {
            float n = 29;
            for ( int i = 0; i < n; i++ ) {
                for ( int j = 0; j < n; j++ ) {
                    if ( i % 2 == 0 && j % 2 == 0 ) {
                        canvas.fillRect(
                            10 + i * 20, 10 + j * 20, 19, 19,
                            Color(i / n, i / n, 1, 1));
                    }
                    else {
                        canvas.drawRect(
                            10 + i * 20, 10 + j * 20, 19, 19,
                            Color(i / n, i / n, 1, 1));
                    }
                }
            }
            
            canvas.drawLine(600, 10, 0, 700, 300, 0, Color(0, 0, 1, 1));
            canvas.drawCircle(Vec3(400, 300, 0), 200, Color(0, 0, 0, 1));

            canvas.fillCircle(Vec3(200, 400, 0), 0, 80, 0, 360, Color(1, 0, 0, 1));
            canvas.fillCircle(Vec3(400, 400, 0), 0, 80, 0, 270, Color(0, 1, 0, 1));
            canvas.fillCircle(Vec3(600, 400, 0), 60, 80, 0, 270, Color(0, 0, 1, 1));

            graphicsContext.flip();
        }
    }
    return 0;
}

运行程序的结果

源码地址:http://files.cnblogs.com/files/ForEmail5/Simple2D-03.rar

原文地址:https://www.cnblogs.com/ForEmail5/p/6793239.html