阅读文章前需要了解的知识,你好,三角形: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