卢莹洁 发表于 6 天前

【OpenGL ES】在Windows上手撕一个mini版的渲染框架

1 前言

1.1 开发该框架的动机

​    OpenGL ES 是一个渲染指令接口集合,每渲染一帧图像都是一系列渲染指令的排列组合。常用的渲染指令约有 70 个,记住这些渲染指令及其排列组合方式,是一件痛苦的事情。另外,在图形开发中,经常因为功耗、丢帧等问题需要性能优化,如何从框架层面进行性能优化是一件有挑战的问题。
​    基于上述原因,笔者手撕了一个 nimi 版的渲染框架,将这些常用的渲染指令有条理地封装、组织、归类,方便愉快并高效地进行 OpenGL ES 渲染开发。笔者在 OpenGL ES 领域从业也有些时日,对现有碎片化的知识进行归纳凝练,形成系统的认知,是件势在必行的事。
1.2 一个 mini 版的渲染框架应该具备哪些能力

​    一个 mini 版的渲染框架需要对 OpenGL ES 的常用指令进行归类(如下图),封装 EGL、error check、Shader Program、Mesh、VAO、VBO、IBO、Texture、FBO 等类,方便开发者快速开发渲染程序,将更多的注意力聚焦在业务上,而不是如何去组织 OpenGL ES 指令上。

1.3 为什么强调 mini 版渲染框架

​    从渲染指令的角度来看,OpenGL ES 3.0 约有 300 个渲染指令,本文框架只封装其中最常用的 70 个,指令覆盖程度仍有较大提升空间。
​    从功能的角度来看,笔者深知一个成熟完备的渲染框架应该具备相机、光源、光照模型(Lambert、Phong、PBR 等)、阴影、射线拾取、重力、碰撞检测、粒子系统等功能。
​    鉴于上述原因,笔者审慎地保留了 "mini" 前缀。
1.4 本框架的优势

​    本框架具有以下优势。

[*]封装友好:对常用的 EGL 和 GL 指令(约 70 个)进行封装,提供了 EGL 环境搭建、着色器程序生成、网格构建、纹理贴图、离屏渲染、异常检测等基础能力,方便开发者快速开发渲染程序,将精力从繁杂的渲染指令中解放出来,将更多的注意力聚焦到业务上。
[*]代码规整:框架中多处设计了 bind 和 unbind 接口,用于绑定和解绑 OpenGL ES 状态机相关 “插槽”,如:VBO、IBO、VAO 中都设计了 bind 和 unbind 接口,ShaderProgram、Texture、FBO、TextureAction 中都设计了 bind 接口;另外,在 FBO 中设计了 begin 和 end 接口,很直观地告诉用户夹在这中间的内容将渲染到 FBO。接口规整简洁,方便用户记忆。
[*]易于扩展:定义了 TextureAction 接口,并提供 bind 函数,GLTexture、FBO 都继承了 TextureAction,用户自定义的渲染器或特效类也可以继承 TextureAction,将它们统一视为纹理活动(可绑定),这在特效叠加(或后处理)中非常有用,方便管理多渲染目标图层,易于扩展。
[*]性能高效:封装了 VBO、IBO、VAO,用于缓存顶点数据、索引、格式等信息到显存,减少 CPU 到 GPU 的数据传输,提高渲染效率;缓存了 attribute 和 uniform 变量的 location,避免 CPU 频繁向 GPU 查询 location,进一步提高渲染效率;基于 C++ 语言实现渲染框架,代码执行效率较高。
[*]跨平台:基于 C++ 语言实现,具有更好的跨平台特性;封装了 core_lib,使得平台相关头文件可以轻松替换;封装了 Application,使得平台相关 api 可以轻松替换。
[*]方便调试:设计了 EGL_CALL 和 GL_CALL 两个宏,对每个 EGL 和 GL 指令进行异常检测,方便调试渲染指令,并且通过预编译宏 DEBUG 开关动态控制是否生成异常检测的代码,Release 版本会自动屏蔽异常检测代码,避免带来额外功耗。
2 渲染框架

​    经过深思熟虑,笔者给该渲染框架命名为 glcore,命名空间也是 glcore。Windows 上 OpenGL 环境搭建主要有 GLFW / freeglut + Glad / GLEW 方案,详见 → Windows上OpenGL环境搭建,本文采用目前广泛使用的 GLFW + Glad 方案。Android 版本的 glcore 实现详见 → 在Android上手撕一个mini版的渲染框架。
​    本文完整资源(包含 glcore 框架和第 4 节的应用)详见 →【OpenGL ES】一个mini版的Windows渲染框架。
2.1 框架结构


2.2 CMakeLists

​    CMakeLists.txt
# 设置库名
set(LIB_GL_CORE_NAME "glcore")

# 递归添加源文件列表
file(GLOB_RECURSE GL_CORE_SOURCES ./src/ *.cpp)

# 添加预构建库
add_library(${LIB_GL_CORE_NAME} ${GL_CORE_SOURCES})

# 将当前目录设为公共头文件目录 (任何链接glcore库的目标都能自动获得这个头文件路径)
target_include_directories(${LIB_GL_CORE_NAME} PUBLIC ./)2.3 核心头文件

​    核心头文件分为对内和对外的,即内部依赖 core_lib,外部开放 core。
​    core_lib.h
#pragma once

/**
* glcore 依赖的核心 GL 库, 便于将 glcore 移植到其他平台
* Android: EGL + GLESv3
* Windows: glfw / freeglut + glad / glew
*
* @author little fat sheep
*/

#include <glad/glad.h>
#include <GLFW/glfw3.h>​    之所以要单独拎出 core_lib.h,是为了方便将该框架迁移到其他平台,如 Android 上依赖的三方渲染库是 EGL + GLESv3,如果不抽出 core_lib.h,就需要将很多地方的 glfw3.h + glad.h 改为 egl.h + gl3.h ,工作量大,也容易漏改。另外,还可以很方便地替换渲染环境,如将 glfw3.h + glad.h 替换为 freeglut.h + glew.h。
​    core.h
#pragma once

/**
* glcore核心头文件
* 该头文件是留给外部使用的, glcore内部不能使用, 避免自己包含自己
* @author little fat sheep
*/

// OpenGL ES API
#include "core_lib.h"

// glcore 核心头文件
#include "application.h"
#include "elg_surface_view.h"
#include "format.h"
#include "frame_buffer_object.h"
#include "gl_inspector.h"
#include "gl_texture.h"
#include "mesh.h"
#include "mesh_utils.h"
#include "shader_program.h"
#include "texture_action.h"
#include "vertex_attribute.h"​   core.h 只提供给外部使用,方便外部只需要包含一个头文件,就能获取 glcore 的基础能力。
2.4 Application

​    Application 主要用于管理全局环境,使用单例模式,方便获取一些全局的变量。它也是 glcore 中唯一一个依赖平台相关的接口(除日志 log 接口外),如:m_window 是 Windows 特有的,如果将 glcore 迁移到 Android 中,就需要将该变量替换为 ANativeWindow* 类型,将这些平台相关变量都集中在 Application 中,迁移平台时修改起来也比较容易,避免太分散容易漏掉。另外,还可以很方便地替换渲染环境,如渲染平台替换为 freeglut 时,需要将 GLFWwindow 替换为 void*,因为 freeglut 未提供类似 window 的数据结构。
​    application.h
#pragma once

#include "core_lib.h"

#define app Application::getInstance()

namespace glcore
{
/**
* 应用程序, 存储全局的参数, 便于访问
* @author little fat sheep
*/
class Application {
private:
    static Application* sInstance;

public:
    int width = 0;
    int height = 0;
    float aspect = 1.0f;

private:
    GLFWwindow* m_window = nullptr;

public:
    static Application* getInstance();
    ~Application();
    void resize(int width, int height);
    GLFWwindow* getWindow() { return m_window; }
    void setWindow(GLFWwindow* window);
    void releaseWindow();

private:
    Application() {};
};
} // namespace glcore​    application.cpp
#include "glcore/application.h"

namespace glcore
{
Application* Application::sInstance = nullptr;

Application *Application::getInstance()
{
    if (sInstance == nullptr)
    {
      sInstance = new Application();
    }
    return sInstance;
}

Application::~Application()
{
   
}

void Application::resize(int width, int height)
{
    this->width = width;
    this->height = height;
    this->aspect = (float) width / (float) height;
}

void Application::setWindow(GLFWwindow* window)
{
    m_window = window;
    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    resize(width, height);
}

void Application::releaseWindow()
{
    if (m_window)
    {
      m_window = nullptr;
    }
}
} // namespace glcore2.5 GLInspector

​    GLInspector 主要用于异常信息检测,另外设计了 EGL_CALL 和 GL_CALL 两个宏,分别对 EGL 和 GL 指令进行装饰。如果定义了 DEBUG 宏,就会对每个 EGL 和 GL 指令进行异常检测,方便调试代码;如果未定义了 DEBUG 宏,就不会进行异常检测。
​    用户可以在 CMakeLists.txt 中添加预编译宏 DEBUG,这样就可以根据 Release 和 Debug 版本自动构建不同的版本。
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    # 添加预编译宏
    add_definitions(-DDEBUG)
endif ()​    gl_inspector.h
#pragma once

#include "core_lib.h"

#ifdef DEBUG
#define EGL_CALL(func) func;GLInspector::checkEGLError();
#define GL_CALL(func) func;GLInspector::checkGLError();
#else
#define EGL_CALL(func) func;
#define GL_CALL(func) func;
#endif

namespace glcore
{
/**
* OpenGL ES命令报错监视器
* @author little fat sheep
*/
class GLInspector
{
public:
    static void checkEGLError(const char* tag); // 检查EGL配置
    static void checkEGLError(); // 通用检查EGL错误
    static void printShaderInfoLog(GLuint shader, const char* tag); // 打印Shader错误日志
    static void printProgramInfoLog(GLuint program, const char* tag); // 打印Program错误日志
    static void checkGLError(const char* tag); // 检查GL报错信息
    static void checkGLError(); // 通用检查GL错误
};
} // namespace glcore​    gl_inspector.cpp
#include <iostream>
#include
#include <string>

#include "glcore/core_lib.h"
#include "glcore/gl_inspector.h"

// 以下内容为了不报错临时添加的 (glfw/freeglut已经创建了egl环境)
#define EGL_SUCCESS             0x3000
#define EGL_NOT_INITIALIZED   0x3001
#define EGL_BAD_ACCESS          0x3002
#define EGL_BAD_ALLOC         0x3003
#define EGL_BAD_ATTRIBUTE       0x3004
#define EGL_BAD_CONFIG          0x3005
#define EGL_BAD_CONTEXT         0x3006
#define EGL_BAD_SURFACE         0x3007
#define EGL_BAD_DISPLAY         0x3008
#define EGL_BAD_CURRENT_SURFACE 0x3009
#define EGL_BAD_NATIVE_PIXMAP   0x300A
#define EGL_BAD_NATIVE_WINDOW   0x300D
#define EGL_BAD_PARAMETER       0x300C
#define EGL_BAD_MATCH         0x3010
#define EGL_CONTEXT_LOST      0x300E
// 以上内容为了不报错临时添加的

#define TAG "GLInspector"

int eglGetError() { return 0; }

using namespace std;

namespace glcore
{
void GLInspector::checkEGLError(const char *tag)
{
    int error = eglGetError();
    if (error != EGL_SUCCESS) {
      printf("%s: %s failed: 0x%x\n", TAG, tag, error);
    }
}

void GLInspector::checkEGLError()
{
    GLenum errorCode = eglGetError();
    if (errorCode != EGL_SUCCESS) {
      string error;
      switch (errorCode)
      {
      case EGL_BAD_DISPLAY:
            error = "EGL_BAD_DISPLAY";
            break;
      case EGL_NOT_INITIALIZED:
            error = "EGL_NOT_INITIALIZED";
            break;
      case EGL_BAD_CONFIG:
            error = "EGL_BAD_CONFIG";
            break;
      case EGL_BAD_CONTEXT:
            error = "EGL_BAD_CONTEXT";
            break;
      case EGL_BAD_NATIVE_WINDOW:
            error = "EGL_BAD_NATIVE_WINDOW";
            break;
      case EGL_BAD_SURFACE:
            error = "EGL_BAD_SURFACE";
            break;
      case EGL_BAD_CURRENT_SURFACE:
            error = "EGL_BAD_CURRENT_SURFACE";
            break;
      case EGL_BAD_ACCESS:
            error = "EGL_BAD_ACCESS";
            break;
      case EGL_BAD_ALLOC:
            error = "EGL_BAD_ALLOC";
            break;
      case EGL_BAD_ATTRIBUTE:
            error = "EGL_BAD_ATTRIBUTE";
            break;
      case EGL_BAD_PARAMETER:
            error = "EGL_BAD_PARAMETER";
            break;
      case EGL_BAD_NATIVE_PIXMAP:
            error = "EGL_BAD_NATIVE_PIXMAP";
            break;
      case EGL_BAD_MATCH:
            error = "EGL_BAD_MATCH";
            break;
      case EGL_CONTEXT_LOST:
            error = "EGL_CONTEXT_LOST";
            break;
      default:
            error = "UNKNOW";
            break;
      }
      printf("checkEGLError failed: %s, 0x%x", error.c_str(), errorCode);
      assert(false);
    }
}

void GLInspector::printShaderInfoLog(GLuint shader, const char* tag)
{
    char infoLog;
    glGetShaderInfoLog(shader, 512, nullptr, infoLog);
    printf("%s: %s failed: %s\n", TAG, tag, infoLog);
}

void GLInspector::printProgramInfoLog(GLuint program, const char* tag)
{
    char infoLog;
    glGetProgramInfoLog(program, 512, nullptr, infoLog);
    printf("%s: %s failed: %s\n", TAG, tag, infoLog);
}

void GLInspector::checkGLError(const char *tag) {
    GLenum error = glGetError();
    if(error != GL_NO_ERROR) {
      printf("%s: %s failed: 0x%x\n", TAG, tag, error);
    }
}

void GLInspector::checkGLError()
{
    GLenum errorCode = glGetError();
    if (errorCode != GL_NO_ERROR)
    {
      string error;
      switch (errorCode)
      {
      case GL_INVALID_ENUM:
            error = "GL_INVALID_ENUM";
            break;
      case GL_INVALID_VALUE:
            error = "GL_INVALID_VALUE";
            break;
      case GL_INVALID_OPERATION:
            error = "GL_INVALID_OPERATION";
            break;
      case GL_INVALID_INDEX:
            error = "GL_INVALID_INDEX";
            break;
      case GL_INVALID_FRAMEBUFFER_OPERATION:
            error = "GL_INVALID_FRAMEBUFFER_OPERATION";
            break;
      case GL_OUT_OF_MEMORY:
            error = "GL_OUT_OF_MEMORY";
            break;
      default:
            error = "UNKNOW";
            break;
      }
      printf("checkError failed: %s, 0x%x\n", error.c_str(), errorCode);
      assert(false);
    }
}
} // namespace glcore2.6 EGLSurfaceView

​    EGLSurfaceView 主要承载了 EGL 环境搭建。EGL 详细介绍见 → 【OpenGL ES】EGL+FBO离屏渲染。
​    由于 GLFW 中已经创建了 EGL 环境,EGLSurfaceView 实际上是多余的,为了保持 glcore 在各个平台上的代码具有高度一致性,并且方便随时自主创建 EGL 环境,这里仍然保留了 EGLSurfaceView。本文中主要使用到 EGLSurfaceView 的内部类 Renderer,起到定制渲染流程规范的作用。
​    elg_surface_view.h
#pragma once

#include "core_lib.h"

// 以下内容为了不报错临时添加的 (glfw/freeglut创建了egl环境)
typedef void* EGLDisplay;
typedef void* EGLConfig;
typedef void* EGLContext;
typedef void* EGLSurface;
// 以上内容为了不报错临时添加的

namespace glcore
{
/**
* EGL环境封装类
* glfw和freeglut中创建了egl环境, 因此该类可以去掉,
* 但是为了方便将glcore迁移到需要用户搭建egl环境的平台中,
* 暂时保留该类.
*
* @author little fat sheep
*/
class EGLSurfaceView
{
public:
    class Renderer;

private:
    Renderer* m_renderer = nullptr;
    EGLDisplay m_eglDisplay;
    EGLConfig m_eglConfig;
    EGLContext m_eglContext;
    EGLSurface m_eglSurface;
    bool m_firstCreateSurface = true;

public:
    EGLSurfaceView();
    ~EGLSurfaceView();
    void setRenderer(Renderer* renderer);
    bool surfaceCreated();
    void surfaceChanged(int width, int height);
    void drawFrame();
    void surfaceDestroy();

private:
    void createDisplay();
    void createConfig();
    void createContext();
    void createSurface();
    void makeCurrent();

public:
    /**
      * 渲染器接口, 类比GLSurfaceView.Renderer
      * @author little fat sheep
      */
    class Renderer {
    public:
      virtual ~Renderer() {};
      virtual void onSurfaceCreated() = 0;
      virtual void onSurfaceChanged(int width, int height) = 0;
      virtual void onDrawFrame() = 0;
    };
};
} // namespace glcore​    elg_surface_view.cpp
#include <iostream>

#include "glcore/application.h"
#include "glcore/elg_surface_view.h"
#include "glcore/gl_inspector.h"

#define TAG "EGLSurfaceView"

// 以下内容为了不报错临时添加的 (glfw/freeglut已经创建了egl环境)
#define EGL_RED_SIZE               0x3024
#define EGL_GREEN_SIZE             0x3023
#define EGL_BLUE_SIZE            0x3022
#define EGL_ALPHA_SIZE             0x3021
#define EGL_DEPTH_SIZE             0x3025
#define EGL_RENDERABLE_TYPE      0x3040
#define EGL_OPENGL_ES3_BIT         0x0040
#define EGL_SURFACE_TYPE         0x3033
#define EGL_WINDOW_BIT             0x0004
#define EGL_NONE                   0x3038
#define EGL_CONTEXT_CLIENT_VERSION 0x3098

#define EGL_DEFAULT_DISPLAY ((EGLDisplay)0)
#define EGL_NO_DISPLAY ((EGLDisplay)0)
#define EGL_NO_CONTEXT ((EGLContext)0)
#define EGL_NO_SURFACE ((EGLSurface)0)

typedef int32_t EGLint;

EGLDisplay eglGetDisplay(EGLDisplay a1) { return nullptr; }
void eglInitialize(EGLDisplay a1, int* a3, int* a4) {}
void eglChooseConfig(EGLDisplay a1, const EGLint* a2, EGLConfig* a3, EGLint a4, EGLint* a5) {}
EGLContext eglCreateContext(EGLDisplay a1, EGLConfig a2, EGLContext a3, const EGLint* a4) { return nullptr; }
EGLSurface eglCreateWindowSurface(EGLDisplay a1, EGLConfig a2, void* a3, const EGLint* a4) { return nullptr; }
void eglMakeCurrent(EGLDisplay a1, EGLSurface a2, EGLSurface a3, EGLContext a4) {}
void eglSwapBuffers(EGLDisplay a1, EGLSurface a2) {}
void eglDestroySurface(EGLDisplay a1, EGLSurface a2) {}
void eglDestroyContext(EGLDisplay a1, EGLContext a2) {}
void eglTerminate(EGLDisplay a1) {}
// 以上内容为了不报错临时添加的

namespace glcore
{
EGLSurfaceView::EGLSurfaceView()
{
    printf("%s: init\n", TAG);
    createDisplay();
    createConfig();
    createContext();
}

EGLSurfaceView::~EGLSurfaceView()
{
    printf("%s: destroy\n", TAG);
    if (m_renderer)
    {
      delete m_renderer;
      m_renderer = nullptr;
    }
    if (m_eglDisplay && m_eglDisplay != EGL_NO_DISPLAY)
    {
      // 与显示设备解绑
      EGL_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
      // 销毁 EGLSurface
      if (m_eglSurface && m_eglSurface != EGL_NO_SURFACE)
      {
            EGL_CALL(eglDestroySurface(m_eglDisplay, m_eglSurface));
            delete &m_eglSurface;
      }
      // 销毁 EGLContext
      if (m_eglContext && m_eglContext != EGL_NO_CONTEXT)
      {
            EGL_CALL(eglDestroyContext(m_eglDisplay, m_eglContext));
            delete &m_eglContext;
      }
      // 销毁 EGLDisplay (显示设备)
      EGL_CALL(eglTerminate(m_eglDisplay));
      delete &m_eglDisplay;
    }
    app->releaseWindow();
    delete app;
}

void EGLSurfaceView::setRenderer(Renderer *renderer)
{
    printf("%s: setRenderer\n", TAG);
    m_renderer = renderer;
}

bool EGLSurfaceView::surfaceCreated()
{
    printf("%s: surfaceCreated\n", TAG);
    app->resize(app->width, app->height);
    createSurface();
    makeCurrent();
    if (m_renderer && m_firstCreateSurface)
    {
      m_renderer->onSurfaceCreated();
      m_firstCreateSurface = false;
    }
    return true;
}

void EGLSurfaceView::surfaceChanged(int width, int height)
{
    printf("%s: surfaceChanged, width: %d, height: %d\n", TAG, width, height);
    app->resize(width, height);
    if (m_renderer)
    {
      m_renderer->onSurfaceChanged(width, height);
    }
}

void EGLSurfaceView::drawFrame()
{
    if (!m_eglSurface || m_eglSurface == EGL_NO_SURFACE || !m_renderer)
    {
      return;
    }
    m_renderer->onDrawFrame();
    EGL_CALL(eglSwapBuffers(m_eglDisplay, m_eglSurface));
}

void EGLSurfaceView::surfaceDestroy()
{
    printf("%s: surfaceDestroy\n", TAG);
    if (m_eglDisplay && m_eglDisplay != EGL_NO_DISPLAY)
    {
      // 与显示设备解绑
      EGL_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
      // 销毁 EGLSurface
      if (m_eglSurface && m_eglSurface != EGL_NO_SURFACE)
      {
            EGL_CALL(eglDestroySurface(m_eglDisplay, m_eglSurface));
            m_eglSurface = nullptr;
      }
    }
    app->releaseWindow();
}

// 1.创建EGLDisplay
void EGLSurfaceView::createDisplay()
{
    EGL_CALL(m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY));
    EGL_CALL(eglInitialize(m_eglDisplay, nullptr, nullptr));
}

// 2.创建EGLConfig
void EGLSurfaceView::createConfig()
{
    if (m_eglDisplay && m_eglDisplay != EGL_NO_DISPLAY)
    {
      const EGLint configAttrs[] = {
                EGL_RED_SIZE, 8,
                EGL_GREEN_SIZE, 8,
                EGL_BLUE_SIZE, 8,
                EGL_ALPHA_SIZE, 8,
                EGL_DEPTH_SIZE, 8,
                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
                EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                EGL_NONE
      };
      EGLint numConfigs;
      EGL_CALL(eglChooseConfig(m_eglDisplay, configAttrs, &m_eglConfig, 1, &numConfigs));
    }
}

// 3.创建EGLContext
void EGLSurfaceView::createContext()
{
    if (m_eglConfig)
    {
      const EGLint contextAttrs[] = {
                EGL_CONTEXT_CLIENT_VERSION, 3,
                EGL_NONE
      };
      EGL_CALL(m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttrs));
    }
}

// 4.创建EGLSurface
void EGLSurfaceView::createSurface()
{
    if (m_eglContext && m_eglContext != EGL_NO_CONTEXT) {
      EGL_CALL(m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, app->getWindow(), nullptr));
    }
}

// 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay)
void EGLSurfaceView::makeCurrent()
{
    if (m_eglSurface && m_eglSurface != EGL_NO_SURFACE)
    {
      EGL_CALL(eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext));
    }
}
} // namespace glcore2.7 ShaderProgram

​    ShaderProgram 主要用于编译 Shader、链接 Program、设置 attribute 属性、更新 uniform 属性。
​    glGetAttribLocation、glGetUniformLocation 两个接口需要 CPU 向 GPU 查询 location 信息,并且会频繁调用,为提高性能,笔者设计了 m_attributes 和 m_uniforms 两个 map 存储 name 到 location 的映射,方便快速获取 location,避免 CPU 频繁与 GPU 交互,以提高渲染性能。
​    shader_program.h
#pragma once

#include <map>

#include "core_lib.h"

using namespace std;

namespace glcore
{
/**
* 着色器程序
* @author little fat sheep
*/
class ShaderProgram
{
public:
    static constexpr char* ATTRIBUTE_POSITION = "a_position"; // 着色器中位置属性名
    static constexpr char* ATTRIBUTE_NORMAL = "a_normal"; // 着色器中位法线性名
    static constexpr char* ATTRIBUTE_COLOR = "a_color"; // 着色器中颜色属性名
    static constexpr char* ATTRIBUTE_TEXCOORD = "a_texCoord"; // 着色器中纹理坐标属性名
    static constexpr char* ATTRIBUTE_TANGENT = "a_tangent"; // 着色器中切线属性名
    static constexpr char* ATTRIBUTE_BINORMAL = "a_binormal"; // 着色器中副切线属性名

    static constexpr char* UNIFORM_TEXTURE = "u_texture"; // 着色器中纹理名
    static constexpr char* UNIFORM_VP = "u_projectionViewMatrix"; // 着色器中VP名

private:
    GLuint m_program;
    map<const char*, int> m_attributes;
    map<const char*, int> m_uniforms;

public:
    ShaderProgram(const char* vertexCode, const char* fragmentCode);
    ~ShaderProgram();
    void bind();
    GLuint getHandle() { return m_program; }

    // 操作attribute属性
    void enableVertexAttribArray(const char* name);
    void enableVertexAttribArray(int location);
    void setVertexAttribPointer(const char* name, int size, int type, bool normalize, int stride, int offset);
    void setVertexAttribPointer(int location, int size, int type, bool normalize, int stride, int offset);
    void disableVertexAttribArray(const char* name);
    void disableVertexAttribArray(int location);

    // 操作uniform属性
    void setUniformi(const char* name, int value);
    void setUniformi(int location, int value);
    void setUniformi(const char* name, int value1, int value2);
    void setUniformi(int location, int value1, int value2);
    void setUniformi(const char* name, int value1, int value2, int value3);
    void setUniformi(int location, int value1, int value2, int value3);
    void setUniformi(const char* name, int value1, int value2, int value3, int value4);
    void setUniformi(int location, int value1, int value2, int value3, int value4);
    void setUniformf(const char* name, float value);
    void setUniformf(int location, float value);
    void setUniformf(const char* name, float value1, float value2);
    void setUniformf(int location, float value1, float value2);
    void setUniformf(const char* name, float value1, float value2, int value3);
    void setUniformf(int location, float value1, float value2, int value3);
    void setUniformf(const char* name, float value1, float value2, int value3, int value4);
    void setUniformf(int location, float value1, float value2, int value3, int value4);
    void setUniform1fv(const char* name, int length, const float values[]);
    void setUniform1fv(int location, int count, float const values[]);
    void setUniform2fv(const char* name, int count, const float values[]);
    void setUniform2fv(int location, int count, const float values[]);
    void setUniform3fv(const char* name, int count, const float values[]);
    void setUniform3fv(int location, int count, const float values[]);
    void setUniform4fv(const char* name, int count, const float values[]);
    void setUniform4fv(int location, int count, const float values[]);
    void setUniformMatrix2fv(const char* name, int count, bool transpose, const float *value);
    void setUniformMatrix2fv(int location, int count, bool transpose, const float *value);
    void setUniformMatrix3fv(const char* name, int count, bool transpose, const float *value);
    void setUniformMatrix3fv(int location, int count, bool transpose, const float *value);
    void setUniformMatrix4fv(const char* name, int count, bool transpose, const float *value);
    void setUniformMatrix4fv(int location, int count, bool transpose, const float *value);

    int fetchAttributeLocation(const char* name);
    int fetchUniformLocation(const char* name);

private:
    void compileShaders(const char* vertexCode, const char* fragmentCode);
    GLuint loadShader(GLenum type, const char* source);
    GLuint linkProgram(GLuint vertexShader, GLuint fragmentShader);
};
} // namespace glcore​    shader_program.cpp
#include <iostream>

#include "glcore/gl_inspector.h"
#include "glcore/shader_program.h"

#define TAG "ShaderProgram"

namespace glcore
{
ShaderProgram::ShaderProgram(const char* vertexCode, const char* fragmentCode)
{
    compileShaders(vertexCode, fragmentCode);
}

ShaderProgram::~ShaderProgram()
{
    if (m_program)
    {
      GL_CALL(glUseProgram(0));
      GL_CALL(glDeleteProgram(m_program));
      m_program = 0;
    }
    m_attributes.clear();
    m_uniforms.clear();
}

void ShaderProgram::bind()
{
    GL_CALL(glUseProgram(m_program));
}

void ShaderProgram::enableVertexAttribArray(const char* name)
{
    int location = fetchAttributeLocation(name);
    enableVertexAttribArray(location);
}

void ShaderProgram::enableVertexAttribArray(int location)
{
    GL_CALL(glEnableVertexAttribArray(location));
}

void ShaderProgram::setVertexAttribPointer(const char *name, int size, int type, bool normalize, int stride, int offset)
{
    int location = fetchAttributeLocation(name);
    setVertexAttribPointer(location, size, type, normalize, stride, offset);
}

void ShaderProgram::setVertexAttribPointer(int location, int size, int type, bool normalize, int stride, int offset)
{
    GL_CALL(glVertexAttribPointer(location, size, type, normalize, stride, (void*) offset));
}

void ShaderProgram::disableVertexAttribArray(const char* name)
{
    int location = fetchAttributeLocation(name);
    disableVertexAttribArray(location);
}

void ShaderProgram::disableVertexAttribArray(int location)
{
    GL_CALL(glDisableVertexAttribArray(location));
}

void ShaderProgram::setUniformi(const char* name, int value)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform1i(location, value));
}

void ShaderProgram::setUniformi(int location, int value)
{
    GL_CALL(glUniform1i(location, value));
}

void ShaderProgram::setUniformi(const char* name, int value1, int value2)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform2i(location, value1, value2));
}

void ShaderProgram::setUniformi(int location, int value1, int value2)
{
    GL_CALL(glUniform2i(location, value1, value2));
}

void ShaderProgram::setUniformi(const char* name, int value1, int value2, int value3)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform3i(location, value1, value2, value3));
}

void ShaderProgram::setUniformi(int location, int value1, int value2, int value3)
{
    GL_CALL(glUniform3i(location, value1, value2, value3));
}

void ShaderProgram::setUniformi(const char* name, int value1, int value2, int value3, int value4)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform4i(location, value1, value2, value3, value4));
}

void ShaderProgram::setUniformi(int location, int value1, int value2, int value3, int value4)
{
    GL_CALL(glUniform4i(location, value1, value2, value3, value4));
}

void ShaderProgram::setUniformf(const char* name, float value)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform1f(location, value));
}

void ShaderProgram::setUniformf(int location, float value)
{
    GL_CALL(glUniform1f(location, value));
}

void ShaderProgram::setUniformf(const char* name, float value1, float value2)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform2f(location, value1, value2));
}

void ShaderProgram::setUniformf(int location, float value1, float value2)
{
    GL_CALL(glUniform2f(location, value1, value2));
}

void ShaderProgram::setUniformf(const char* name, float value1, float value2, int value3)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform3f(location, value1, value2, value3));
}

void ShaderProgram::setUniformf(int location, float value1, float value2, int value3)
{
    GL_CALL(glUniform3f(location, value1, value2, value3));
}

void ShaderProgram::setUniformf(const char* name, float value1, float value2, int value3, int value4)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform4f(location, value1, value2, value3, value4));
}

void ShaderProgram::setUniformf(int location, float value1, float value2, int value3, int value4)
{
    GL_CALL(glUniform4f(location, value1, value2, value3, value4));
}

void ShaderProgram::setUniform1fv(const char* name, int count, const float values[])
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform1fv(location, count, values));
}

void ShaderProgram::setUniform1fv(int location, int count, const float values[])
{
    GL_CALL(glUniform1fv(location, count, values));
}

void ShaderProgram::setUniform2fv(const char* name, int count, const float values[])
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform2fv(location, count / 2, values));
}

void ShaderProgram::setUniform2fv(int location, int count, const float values[])
{
    GL_CALL(glUniform2fv(location, count / 2, values));
}

void ShaderProgram::setUniform3fv(const char* name, int count, const float values[])
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform3fv(location, count / 3, values));
}

void ShaderProgram::setUniform3fv(int location, int count, const float values[])
{
    GL_CALL(glUniform3fv(location, count / 3, values));
}

void ShaderProgram::setUniform4fv(const char* name, int count, const float values[])
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniform4fv(location, count / 4, values));
}

void ShaderProgram::setUniform4fv(int location, int count, const float values[])
{
    GL_CALL(glUniform4fv(location, count / 4, values));
}

void ShaderProgram::setUniformMatrix2fv(const char* name, int count, bool transpose, const float *value)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniformMatrix2fv(location, count, transpose, value));
}

void ShaderProgram::setUniformMatrix2fv(int location, int count, bool transpose, const float *value)
{
    GL_CALL(glUniformMatrix2fv(location, count, transpose, value));
}

void ShaderProgram::setUniformMatrix3fv(const char* name, int count, bool transpose, const float *value)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniformMatrix3fv(location, count, transpose, value));
}

void ShaderProgram::setUniformMatrix3fv(int location, int count, bool transpose, const float *value)
{
    GL_CALL(glUniformMatrix3fv(location, count, transpose, value));
}

void ShaderProgram::setUniformMatrix4fv(const char* name, int count, bool transpose, const float *value)
{
    int location = fetchUniformLocation(name);
    GL_CALL(glUniformMatrix4fv(location, count, transpose, value));
}

void ShaderProgram::setUniformMatrix4fv(int location, int count, bool transpose, const float *value)
{
    GL_CALL(glUniformMatrix4fv(location, count, transpose, value));
}

int ShaderProgram::fetchAttributeLocation(const char* name)
{
    int location;
    auto it = m_attributes.find(name);
    if (it == m_attributes.end())
    {
      GL_CALL(location = glGetAttribLocation(m_program, name));
      if (location == -1) {
            printf("%s: no attribute: %s\n", TAG, name);
            //GLInspector::printProgramInfoLog(m_program, "fetchAttributeLocation");
            return -1;
      }
      m_attributes = location;
    }
    else
    {
      location = it->second;
    }
    return location;
}

int ShaderProgram::fetchUniformLocation(const char* name)
{
    int location;
    auto it = m_uniforms.find(name);
    if (it == m_uniforms.end())
    {
      GL_CALL(location = glGetUniformLocation(m_program, name));
      if (location == -1) {
            printf("%s: no uniform: %s\n", TAG, name);
            //GLInspector::printProgramInfoLog(m_program, "fetchUniformLocation");
            return -1;
      }
      m_uniforms = location;
    }
    else
    {
      location = it->second;
    }
    return location;
}

void ShaderProgram::compileShaders(const char* vertexCode, const char* fragmentCode)
{
    GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexCode);
    GLuint fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentCode);
    m_program = linkProgram(vertexShader, fragmentShader);
}

GLuint ShaderProgram::loadShader(GLenum type, const char* source)
{
    GL_CALL(GLuint shader = glCreateShader(type));
    GL_CALL(glShaderSource(shader, 1, &source, nullptr));
    GL_CALL(glCompileShader(shader));
    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
      GLInspector::printShaderInfoLog(shader, "loadShader");
      return 0;
    }
    return shader;
}

GLuint ShaderProgram::linkProgram(GLuint vertexShader, GLuint fragmentShader)
{
    GL_CALL(GLuint program = glCreateProgram());
    GL_CALL(glAttachShader(program, vertexShader));
    GL_CALL(glAttachShader(program, fragmentShader));
    GL_CALL(glLinkProgram(program));
    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
      GLInspector::printProgramInfoLog(m_program, "linkProgram");
    }
    GL_CALL(glDeleteShader(vertexShader));
    GL_CALL(glDeleteShader(fragmentShader));
    return program;
}
} // namespace glcore2.8 VBO

​    VBO 是 Vertex Buffer Object 的简称,即顶点缓冲对象,作用是缓存顶点数据到显存中,避免频繁调用 glVertexAttribPointer 传输顶点数据,减少 CPU 到 GPU 的数据传输,提高渲染效率。
​    顶点属性主要有位置、颜色、纹理坐标、法线、切线、副切线等,每个属性又有属性标识、维数、是否已标准化、数据类型、偏移、别名、纹理单元等。
​    由于 VBO 中有多个属性数据,每个属性有多个字段,笔者除了封装 VertexBufferObject 类,还封装了 VertexAttributes 和 VertexAttribute 两个类。VertexAttribute 是属性描述类,VertexAttributes 是属性描述集合。
​    vertex_buffer_object.h
#pragma once

#include <initializer_list>
#include <vector>

#include "core_lib.h"
#include "shader_program.h"
#include "vertex_attributes.h"
#include "vertex_attribute.h"
#include "vertex_attributes.h"

using namespace std;

namespace glcore
{
/**
* 顶点属性缓冲对象 (简称VBO)
* @author little fat sheep
*/
class VertexBufferObject
{
protected:
    bool m_isBound = false; // 是否已绑定到VBO (或VAO)
    bool m_isDirty = false; // 是否有脏数据 (缓存的数据需要更新)

private:
    GLuint m_vboHandle; // VBO句柄
    VertexAttributes* m_attributes; // 顶点属性
    GLuint m_usage; // GL_STATIC_DRAW 或 GL_DYNAMIC_DRAW
    const float* m_vertices; // 顶点属性数据
    int m_vertexNum = 0; // 顶点个数
    int m_bytes = 0; // 顶点属性字节数

public:
    VertexBufferObject(bool isStatic, initializer_list<VertexAttribute*> attributes);
    VertexBufferObject(bool isStatic, VertexAttributes* attributes);
    virtual ~VertexBufferObject();
    void setVertices(float* vertices, int bytes);
    void bind(ShaderProgram* shader);
    virtual void bind(ShaderProgram* shader, int* locations);
    void unbind(ShaderProgram* shader);
    virtual void unbind(ShaderProgram* shader, int* locations);
    int getNumVertices() { return m_vertexNum; }

private:
    void applyBufferData(); // 缓存数据
};
} // namespace glcore​    vertex_buffer_object.cpp
#include <iostream>

#include "glcore/gl_inspector.h"
#include "glcore/vertex_buffer_object.h"

#define TAG "VertexBufferObject"

namespace glcore
{
VertexBufferObject::VertexBufferObject(bool isStatic, initializer_list<VertexAttribute*> attributes):
    VertexBufferObject(isStatic, new VertexAttributes(attributes))
{
}

VertexBufferObject::VertexBufferObject(bool isStatic, VertexAttributes* attributes):
    m_attributes(attributes)
{
    m_usage = isStatic ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW;
    GL_CALL(glGenBuffers(1, &m_vboHandle));
    printf("%s: init: %d\n", TAG, m_vboHandle);
}

VertexBufferObject::~VertexBufferObject()
{
    printf("%s: destroy\n", TAG);
    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
    GL_CALL(glDeleteBuffers(1, &m_vboHandle));
    m_vboHandle = 0;
    delete m_attributes;
    delete[] m_vertices;
}

void VertexBufferObject::setVertices(float* vertices, int bytes)
{
    m_vertices = vertices;
    m_vertexNum = bytes / m_attributes->vertexSize;
    m_bytes = bytes;
    m_isDirty = true;
    if (m_isBound)
    {
      applyBufferData();
    }
}

void VertexBufferObject::bind(ShaderProgram* shader)
{
    bind(shader, nullptr);
}

void VertexBufferObject::bind(ShaderProgram* shader, int* locations)
{
    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_vboHandle));
    if (m_isDirty)
    {
      applyBufferData();
    }
    if (locations == nullptr)
    {
      for (int i = 0; i < m_attributes->size(); i++)
      {
            VertexAttribute* attribute = m_attributes->get(i);
            shader->enableVertexAttribArray(attribute->alias);
            shader->setVertexAttribPointer(attribute->alias, attribute->numComponents,
               attribute->type, attribute->normalized, m_attributes->vertexSize,
               attribute->offset);
      }
    }
    else
    {
      for (int i = 0; i < m_attributes->size(); i++)
      {
            VertexAttribute* attribute = m_attributes->get(i);
            shader->enableVertexAttribArray(locations);
            shader->setVertexAttribPointer(locations, attribute->numComponents,
               attribute->type, attribute->normalized, m_attributes->vertexSize,
               attribute->offset);
      }
    }
    m_isBound = true;
}

void VertexBufferObject::unbind(ShaderProgram* shader)
{
    unbind(shader, nullptr);
}

void VertexBufferObject::unbind(ShaderProgram* shader, int* locations)
{
    if (locations == nullptr)
    {
      for (int i = 0; i < m_attributes->size(); i++)
      {
            shader->disableVertexAttribArray(m_attributes->get(i)->alias);
      }
    }
    else
    {
      for (int i = 0; i < m_attributes->size(); i++)
      {
            shader->disableVertexAttribArray(locations);
      }
    }
    m_isBound = false;
}

void VertexBufferObject::applyBufferData()
{
    GL_CALL(glBufferData(GL_ARRAY_BUFFER, m_bytes, m_vertices, m_usage));
    m_isDirty = false;
}
} // namespace glcore​    vertex_attributes.h
#pragma once

#include <initializer_list>
#include <vector>

#include "vertex_attribute.h"

using namespace std;

namespace glcore
{
/**
* 顶点属性集(位置、颜色、纹理坐标、法线、切线、副切线等中的一部分)
* 每个顶点属性可以看作一个通道, 这个通道可能是多维的, 每个维度可能是多字节的
* @author little fat sheep
*/
class VertexAttributes
{
public:
    int vertexSize; // 所有顶点属性的字节数

private:
    vector<VertexAttribute*> m_attributes; // 顶点属性列表

public:
    VertexAttributes(initializer_list<VertexAttribute*> attributes);
    ~VertexAttributes();
    VertexAttribute* get(int index); // 根据索引获取属性
    int size(); // 获取属性个数

private:
    int calculateOffsets(); // 计算偏移
};

/**
* 顶点属性标识
* @author little fat sheep
*/
class Usage {
public:
    static const int Position = 1;
    static const int ColorUnpacked = 2;
    static const int ColorPacked = 4;
    static const int Normal = 8;
    static const int TextureCoordinates = 16;
    static const int Tangent = 32;
    static const int BiNormal = 64;
};
} // namespace glcore​    vertex_attributes.cpp
#include "glcore/vertex_attributes.h"

namespace glcore
{
VertexAttributes::VertexAttributes(initializer_list<VertexAttribute*> attributes):
      m_attributes(attributes)
{
    vertexSize = calculateOffsets();
}

VertexAttributes::~VertexAttributes()
{
    m_attributes.clear();
}

VertexAttribute* VertexAttributes::get(int index)
{
    if (index >= 0 && index < m_attributes.size())
    {
      return m_attributes;
    }
    return nullptr;
}

int VertexAttributes::size()
{
    return m_attributes.size();
}

int VertexAttributes::calculateOffsets() {
    int count = 0;
    for (VertexAttribute* attribute : m_attributes) {
      attribute->offset = count;
      count += attribute->getSizeInBytes();
    }
    return count;
}
} // namespace glcore​    vertex_attribute.h
#pragma once

namespace glcore
{
/**
* 单个顶点属性(位置、颜色、纹理坐标、法线、切线、副切线等中的一个)
* 每个顶点属性可以看作一个通道, 这个通道可能是多维的, 每个维度可能是多字节的
* @author little fat sheep
*/
class VertexAttribute
{
public:
    int usage; // 顶点属性标识
    int numComponents; // 顶点属性维数 (如顶点坐标属性是3维的, 纹理坐标是2维的)
    bool normalized; // 顶点属性是否已经标准化 (有符号: -1~1, 无符号: 0~1)
    int type; // 顶点属性的变量类型 (GL_FLOAT、GL_UNSIGNED_BYTE等)
    int offset; // 顶点属性在字节上的偏移
    const char* alias; // 顶点属性别名 (着色器中变量名)
    int unit; // 纹理单元 (可能有多个纹理, 可选)

public:
    VertexAttribute(int usage, int numComponents, const char* alias);
    VertexAttribute(int usage, int numComponents, const char* alias, int unit);
    VertexAttribute(int usage, int numComponents, int type, bool normalized, const char* alias);
    VertexAttribute(int usage, int numComponents, int type, bool normalized, const char* alias, int unit);
    ~VertexAttribute();

    static VertexAttribute* Position(); // 位置参数信息
    static VertexAttribute* TexCoords(int unit); // 纹理坐标参数信息
    static VertexAttribute* Normal(); // 法线参数信息
    static VertexAttribute* ColorPacked(); // 颜色参数信息
    static VertexAttribute* ColorUnpacked(); // 颜色参数信息
    static VertexAttribute* Tangent(); // 切线参数信息
    static VertexAttribute* Binormal(); // 副切线参数信息

    int getSizeInBytes(); // 属性对应的字节数

private:
    void create(int usage, int numComponents, int type, bool normalized, const char* alias, int unit);
};
} // namespace glcore​    vertex_attribute.cpp
#include <iostream>
#include <string>

#include "glcore/core_lib.h"
#include "glcore/shader_program.h"
#include "glcore/vertex_attribute.h"
#include "glcore/vertex_attributes.h"

#define TAG "VertexAttribute"

using namespace std;

namespace glcore
{
VertexAttribute::VertexAttribute(int usage, int numComponents, const char* alias):
      VertexAttribute(usage, numComponents, alias, 0)
{
}

VertexAttribute::VertexAttribute(int usage, int numComponents, const char* alias, int unit)
{
    int type = usage == Usage::ColorPacked ? GL_UNSIGNED_BYTE : GL_FLOAT;
    bool normalized = usage == Usage::ColorPacked;
    create(usage, numComponents, type, normalized, alias, unit);
}

VertexAttribute::VertexAttribute(int usage, int numComponents, int type, bool normalized, const char* alias)
{
    create(usage, numComponents, type, normalized, alias, 0);
}

VertexAttribute::VertexAttribute(int usage, int numComponents, int type, bool normalized, const char* alias, int unit)
{
    create(usage, numComponents, type, normalized, alias, unit);
}

VertexAttribute::~VertexAttribute()
{
    free((void*)alias);
}

VertexAttribute* VertexAttribute::Position() {
    return new VertexAttribute(Usage::Position, 3, ShaderProgram::ATTRIBUTE_POSITION);
}

VertexAttribute* VertexAttribute::TexCoords(int unit) {
    string str = string(ShaderProgram::ATTRIBUTE_TEXCOORD) + to_string(unit);
    // 复制字符串, 避免str被回收导致悬垂指针问题, 通过free((void*)alias)释放内存
    const char* combined = strdup(str.c_str());
    return new VertexAttribute(Usage::TextureCoordinates, 2, combined, unit);
}

VertexAttribute* VertexAttribute::Normal() {
    return new VertexAttribute(Usage::Normal, 3, ShaderProgram::ATTRIBUTE_NORMAL);
}

VertexAttribute* VertexAttribute::ColorPacked() {
    return new VertexAttribute(Usage::ColorPacked, 4, GL_UNSIGNED_BYTE, true, ShaderProgram::ATTRIBUTE_COLOR);
}

VertexAttribute* VertexAttribute::ColorUnpacked() {
    return new VertexAttribute(Usage::ColorUnpacked, 4, GL_FLOAT, false, ShaderProgram::ATTRIBUTE_COLOR);
}

VertexAttribute* VertexAttribute::Tangent() {
    return new VertexAttribute(Usage::Tangent, 3, ShaderProgram::ATTRIBUTE_TANGENT);
}

VertexAttribute* VertexAttribute::Binormal() {
    return new VertexAttribute(Usage::BiNormal, 3, ShaderProgram::ATTRIBUTE_BINORMAL);
}

int VertexAttribute::getSizeInBytes()
{
    switch (type) {
      case GL_FLOAT:
      case GL_FIXED:
            return 4 * numComponents;
      case GL_UNSIGNED_BYTE:
      case GL_BYTE:
            return numComponents;
      case GL_UNSIGNED_SHORT:
      case GL_SHORT:
            return 2 * numComponents;
    }
    return 0;
}

void VertexAttribute::create(int usage, int numComponents, int type, bool normalized, const char* alias, int unit)
{
    this->usage = usage;
    this->numComponents = numComponents;
    this->type = type;
    this->normalized = normalized;
    this->alias = alias;
    this->unit = unit;
    printf("%s: create, alias: %s\n", TAG, alias);
}
} // namespace glcore2.9 VAO

​    VAO 是 Vertex Array Object 的简称,即顶点数组对象,作用是缓存顶点属性的指针和描述(或格式)信息,简化顶点属性设置的流程,避免频繁调用 glVertexAttribPointer 设置属性描述(或格式)信息,减少 CPU 与 GPU 的交互,提高渲染效率。
​    vertex_buffer_object_with_vao.h
#pragma once

#include <initializer_list>

#include "core_lib.h"
#include "vertex_buffer_object.h"

namespace glcore
{
/**
* 携带VAO的顶点属性缓冲对象
* @author little fat sheep
*/
class VertexBufferObjectWithVAO : public VertexBufferObject
{
private:
    GLuint m_vaoHandle; // VAO句柄

public:
    VertexBufferObjectWithVAO(bool isStatic, initializer_list<VertexAttribute*> attributes);
    VertexBufferObjectWithVAO(bool isStatic, VertexAttributes* attributes);
    ~VertexBufferObjectWithVAO() override;
    void bind(ShaderProgram* shader, int* locations) override;
    void unbind(ShaderProgram* shader, int* locations) override;
};
} // namespace glcore​    vertex_buffer_object_with_vao.cpp
#include <iostream>

#include "glcore/gl_inspector.h"
#include "glcore/vertex_buffer_object_with_vao.h"

#define TAG "VertexBufferObjectWithVAO"

namespace glcore
{
VertexBufferObjectWithVAO::VertexBufferObjectWithVAO(bool isStatic,
    initializer_list<VertexAttribute*> attributes):
      VertexBufferObjectWithVAO(isStatic, new VertexAttributes(attributes))
{
}

VertexBufferObjectWithVAO::VertexBufferObjectWithVAO(bool isStatic, VertexAttributes* attributes):
    VertexBufferObject(isStatic, attributes)
{
    GL_CALL(glGenVertexArrays(1, &m_vaoHandle));
    printf("%s: init: %d\n", TAG, m_vaoHandle);
}

VertexBufferObjectWithVAO::~VertexBufferObjectWithVAO()
{
    printf("%s: destroy\n", TAG);
    GL_CALL(glDeleteVertexArrays(1, &m_vaoHandle));
}

void VertexBufferObjectWithVAO::bind(ShaderProgram* shader, int* locations)
{
    GL_CALL(glBindVertexArray(m_vaoHandle));
    if (m_isDirty)
    {
      VertexBufferObject::bind(shader, locations);
    }
    m_isBound = true;
}

void VertexBufferObjectWithVAO::unbind(ShaderProgram* shader, int* locations)
{
    GL_CALL(glBindVertexArray(0));
    m_isBound = false;
}
} // namespace glcore2.10 IBO

​    IBO 是 Index Buffer Object 的简称,即索引缓冲对象,作用是缓存顶点索引到显存中,避免频繁调用 glDrawElements 传输顶点索引,减少 CPU 到 GPU 的数据传输,提高渲染效率。由于 IBO 绑定的是 OpenGL ES 状态机的 GL_ELEMENT_ARRAY_BUFFER “插槽”,并且对应的绘制指令又是 glDrawElements (都有 Element),因此 IBO 也被称为 EBO。
​    index_buffer_object.h
#pragma once

#include "core_lib.h"

namespace glcore
{
/**
* 顶点索引缓冲对象 (简称IBO)
* @author little fat sheep
*/
class IndexBufferObject
{
private:
    GLuint m_iboHandle; // IBO句柄
    GLuint m_usage; // GL_STATIC_DRAW 或 GL_DYNAMIC_DRAW
    GLenum m_type = GL_UNSIGNED_SHORT; // 索引数据类型 (GL_UNSIGNED_SHORT 或 GL_UNSIGNED_INT)
    const void* m_indices; // 顶点索引数据(short*或int*类型)
    int m_indexNum = 0; // 索引个数
    int m_bytes = 0; // 顶点索引字节数
    bool m_isDirty = false; // 是否有脏数据 (缓存的数据需要更新)
    bool m_isBound = false; // 是否已绑定到IBO

public:
    IndexBufferObject(bool isStatic);
    IndexBufferObject(bool isStatic, GLenum type);
    ~IndexBufferObject();
    void setIndices (void* indices, int bytes);
    void setIndices (void* indices, int bytes, GLenum type);
    void bind();
    void unbind();
    int getNumIndices() { return m_indexNum; }
    GLenum getType() { return m_type; }

private:
    void applyBufferData(); // 缓存数据
    int getTypeSize(); // 获取type对应的字节数
};
} // namespace glcore​    index_buffer_object.cpp
#include <iostream>

#include "glcore/gl_inspector.h"
#include "glcore/index_buffer_object.h"

#define TAG "IndexBufferObject"

namespace glcore
{
IndexBufferObject::IndexBufferObject(bool isStatic):
      IndexBufferObject(isStatic, GL_UNSIGNED_SHORT)
{
}

IndexBufferObject::IndexBufferObject(bool isStatic, GLenum type)
{
    m_usage = isStatic ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW;
    m_type = type;
    GL_CALL(glGenBuffers(1, &m_iboHandle));
    printf("%s: init: %d\n", TAG, m_iboHandle);
}

IndexBufferObject::~IndexBufferObject()
{
    printf("%s: destroy\n", TAG);
    GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
    GL_CALL(glDeleteBuffers(1, &m_iboHandle));
    m_iboHandle = 0;
    delete[] m_indices;
}

void IndexBufferObject::setIndices(void* indices, int bytes)
{
    setIndices(indices, bytes, m_type);
}

void IndexBufferObject::setIndices(void* indices, int bytes, GLenum type)
{
    m_indices = indices;
    m_type = type;
    m_indexNum = bytes > 0 ? bytes / getTypeSize() : 0;
    m_bytes = bytes;
    m_isDirty = true;
    if (m_isBound)
    {
      applyBufferData();
    }
}

void IndexBufferObject::bind()
{
    GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_iboHandle));
    if (m_isDirty)
    {
      applyBufferData();
    }
    m_isBound = true;
}

void IndexBufferObject::unbind()
{
    GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
    m_isBound = false;
}

void IndexBufferObject::applyBufferData()
{
    GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_bytes, m_indices, m_usage));
    //GLInspector::checkGLError("ibo: glBufferData");
    m_isDirty = false;
}

int IndexBufferObject::getTypeSize() {
    switch (m_type) {
      case GL_UNSIGNED_SHORT:
            return 2;
      case GL_UNSIGNED_INT:
            return 4;
    }
    return 2;
}
} // namespace glcore2.11 Mesh

​    Mesh 是网格类,用于管理顶点数据、索引、描述(或格式)等信息,由于 VBO 管理了顶点数据、IBO 管理了顶点索引、VAO 管理了顶点描述(或格式),因此 Mesh 只需管理 VBO、IBO、VAO。另外 IBO 和 VAO 是可选的,Mesh 中需要根据用户的行为调整渲染指令。
​    为方便用户快速创建平面网格,笔者提供了 MeshUtils 类,用户也可以根据该类提供的模板创建自己的网格。
​    mesh.h
#pragma once

#include <initializer_list>

#include "core_lib.h"
#include "index_buffer_object.h"
#include "shader_program.h"
#include "vertex_buffer_object.h"
#include "vertex_attribute.h"
#include "vertex_attributes.h"

using namespace std;

namespace glcore
{
/**
* 网格
* @author little fat sheep
*/
class Mesh
{
private:
    VertexBufferObject* m_vbo; // 顶点属性缓冲对象
    IndexBufferObject* m_ibo; // 顶点索引缓冲对象

public:
    Mesh(bool isStatic, initializer_list<VertexAttribute*> attributes);
    Mesh(bool isStatic, VertexAttributes* attributes);
    Mesh(bool useVao, bool isStatic, initializer_list<VertexAttribute*> attributes);
    Mesh(bool useVao, bool isStatic, VertexAttributes* attributes);
    ~Mesh();
    void setVertices(float* vertices, int bytes); // 设置顶点属性
    void setIndices(void* indices, int bytes); // 设置顶点索引
    void setIndices(void* indices, int bytes, GLenum type); // 设置顶点索引
    void render(ShaderProgram* shader, GLenum primitiveType); // 渲染
};
} // namespace glcore​    mesh.cpp
#include "glcore/gl_inspector.h"
#include "glcore/mesh.h"
#include "glcore/vertex_buffer_object_with_vao.h"

namespace glcore
{
Mesh::Mesh(bool isStatic, initializer_list<VertexAttribute*> attributes):
    Mesh(true, isStatic, new VertexAttributes(attributes))
{
}

Mesh::Mesh(bool isStatic, VertexAttributes* attributes):
    Mesh(true, isStatic, attributes)
{
}

Mesh::Mesh(bool useVao, bool isStatic, initializer_list<VertexAttribute*> attributes):
    Mesh(useVao, isStatic, new VertexAttributes(attributes))
{
}

Mesh::Mesh(bool useVao, bool isStatic, VertexAttributes* attributes)
{
    m_vbo = useVao ? new VertexBufferObjectWithVAO(isStatic, attributes) :
            new VertexBufferObject(isStatic, attributes);
    m_ibo = new IndexBufferObject(isStatic);
}

Mesh::~Mesh()
{
    delete m_vbo;
    delete m_ibo;
}

void Mesh::setVertices(float* vertices, int bytes)
{
    m_vbo->setVertices(vertices, bytes);
}

void Mesh::setIndices(void* indices, int bytes)
{
    m_ibo->setIndices(indices, bytes);
}

void Mesh::setIndices(void* indices, int bytes, GLenum type)
{
    m_ibo->setIndices(indices, bytes, type);
}

void Mesh::render(ShaderProgram* shader, GLenum primitiveType)
{
    m_vbo->bind(shader);
    if (m_ibo->getNumIndices() > 0) {
      m_ibo->bind();
      GL_CALL(glDrawElements(primitiveType, m_ibo->getNumIndices(), m_ibo->getType(), nullptr));
      m_ibo->unbind();
    } else {
      GL_CALL(glDrawArrays(primitiveType, 0, m_vbo->getNumVertices()));
    }
    m_vbo->unbind(shader);
}
} // namespace glcore​    mesh_utils.h
#pragma once

#include "mesh.h"

namespace glcore
{
/**
* 网格工具类
* @author little fat sheep
*/
class MeshUtils
{
public:
    static Mesh* createRect(bool reverse);

private:
    static float* getRectVertices(bool reverse);
};
} // namespace glcore​    mesh_utils.cpp
#include "glcore/mesh_utils.h"

namespace glcore
{
Mesh* MeshUtils::createRect(bool reverse)
{
    Mesh* mesh = new Mesh(true, {
            VertexAttribute::Position(),
            VertexAttribute::TexCoords(0)
    });
    float* vertices = getRectVertices(reverse);
    mesh->setVertices(vertices, 4 * 5 * sizeof(float));
    void* indices = new short { 0, 1, 2, 2, 3, 0 };
    mesh->setIndices(indices, 6 * sizeof(short));
    return mesh;
}

float* MeshUtils::getRectVertices(bool reverse)
{
    if (reverse) {
      return new float { // 中间渲染(FBO)使用
                -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // 左下
                1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // 右下
                1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // 右上
                -1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上
      };
    }
    return new float { // 终端渲染使用
            -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, // 左下
            1.0f, -1.0f, 0.0f, 1.0f, 1.0f, // 右下
            1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右上
            -1.0f, 1.0f, 0.0f, 0.0f, 0.0f // 左上
    };
}
} // namespace glcore2.12 GLTexture

​    封装 GLTexture 类是了方便用户进行纹理贴图。为了方便管理多渲染目标图层,定义了 TextureAction 接口,并提供 bind 函数,GLTexture、FBO 都继承了 TextureAction,用户自定义的渲染器或特效类也可以继承 TextureAction,将它们统一视为纹理活动(可绑定),这在特效叠加(或后处理)中非常有用,易于扩展。
​    texture_action.h
#pragma once

#include "core_lib.h"
#include "shader_program.h"

namespace glcore
{
/**
* 纹理活动 (纹理绑定、FBO绑定)
* @author little fat sheep
*/
class TextureAction
{
public:
    virtual ~TextureAction() = default;
    virtual void setTexParameter(GLint filter, GLint wrap) {}
    virtual void setBindParameter(char* alias, GLenum unit) {}
    virtual void bind(ShaderProgram* shader) = 0;
};
} // namespace glcore​    gl_texture.h
#pragma once

#include "core_lib.h"

#include "shader_program.h"
#include "texture_action.h"

namespace glcore
{
/**
* 纹理贴图
* @author little fat sheep
*/
class GLTexture: public TextureAction
{
private:
    GLuint m_textureHandle = 0; // 纹理句柄
    int m_width = 0; // 纹理宽度
    int m_height = 0; // 纹理高度
    GLint m_filter = GL_LINEAR; // 滤波方式
    GLint m_wrap = GL_CLAMP_TO_EDGE; // 环绕方式
    const char* m_alias = ShaderProgram::UNIFORM_TEXTURE; // 纹理别名(着色器中变量名)
    GLenum m_unit = 0; // 纹理单元 (可能有多个纹理)
    bool m_isDirty = false; // 是否有脏数据 (纹理参数需要更新)

public:
    GLTexture(int width, int height);
    GLTexture(void *buffer, int width, int height);
    ~GLTexture() override;
    void setTexture(const void *buffer);
    void setTexParameter(GLint filter, GLint wrap) override;
    void setBindParameter(char* alias, GLenum unit) override;
    void bind(ShaderProgram* shader) override;
    int getWidth() { return m_width; }
    int getHeight() { return m_height; }

private:
    void applyTexParameter();
};
} // namespace glcore​    gl_texture.cpp
#include "glcore/gl_inspector.h"
#include "glcore/gl_texture.h"

namespace glcore
{
GLTexture::GLTexture(int width, int height):
    m_width(width),
    m_height(height)
{
}

GLTexture::GLTexture(void *buffer, int width, int height): GLTexture(width, height)
{
    setTexture(buffer);
}

GLTexture::~GLTexture()
{
    GL_CALL(glBindTexture(GL_TEXTURE_2D, 0));
    if (m_textureHandle != 0) {
      GL_CALL(glDeleteTextures(1, &m_textureHandle));
      m_textureHandle = 0;
    }
}

/**
* buffer 可以通过以下两种方式得到
* void* buffer = stbi_load(filePath, &width, &height, &channels, STBI_rgb_alpha)
*/
void GLTexture::setTexture(const void *buffer)
{
    GL_CALL(glGenTextures(1, &m_textureHandle));
    GL_CALL(glBindTexture(GL_TEXTURE_2D, m_textureHandle));
    applyTexParameter();
    GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_width, m_height, 0,
               GL_RGBA, GL_UNSIGNED_BYTE, buffer));
    GL_CALL(glGenerateMipmap(GL_TEXTURE_2D));
    GL_CALL(glBindTexture(GL_TEXTURE_2D, 0));
    //GLInspector::checkGLError("setTexture");
}

void GLTexture::setTexParameter(GLint filter, GLint wrap)
{
    m_filter = filter;
    m_wrap = wrap;
    m_isDirty = true;
}

void GLTexture::setBindParameter(char *alias, GLenum unit)
{
    m_alias = alias;
    m_unit = unit;
}

void GLTexture::bind(ShaderProgram *shader)
{
    shader->setUniformi(m_alias, m_unit);
    GL_CALL(glActiveTexture(GL_TEXTURE0 + m_unit));
    GL_CALL(glBindTexture(GL_TEXTURE_2D, m_textureHandle));
    if (m_isDirty)
    {
      applyTexParameter();
    }
}

void GLTexture::applyTexParameter()
{
    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter));
    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter));
    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_wrap));
    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_wrap));
    m_isDirty = false;
}
} // namespace glcore2.13 FBO

​    FBO 是 Frame Buffer Object 的简称,即帧缓冲对象,主要用于离屏渲染、特效叠加,
​    frame_buffer_object.h
#pragma once

#include "core_lib.h"

#include "shader_program.h"
#include "texture_action.h"
#include "format.h"

namespace glcore
{
/**
* 帧缓冲对象 (简称FBO, 用于离屏渲染)
* @author little fat sheep
*/
class FrameBufferObject: public TextureAction
{
private:
    Format* m_format; // 颜色格式
    int m_width; // 缓冲区宽度
    int m_height; // 缓冲区高度
    bool m_hasDepth; // 是否有深度缓冲区
    bool m_hasStencil; // 是否有模板缓冲区
    GLuint m_frameBufferHandle; // 帧缓冲区句柄
    GLuint m_depthBufferHandle; // 深度缓冲区句柄
    GLuint m_stencilBufferHandle; // 模板缓冲区句柄
    GLuint m_colorTextureHandle; // 颜色缓冲区句柄
    GLint m_preFramebufferHandle; // 前一个帧缓冲区句柄
    int m_preFramebufferViewPort; // 前一个帧缓冲区视口
    GLint m_filter = GL_LINEAR; // 滤波方式
    GLint m_wrap = GL_CLAMP_TO_EDGE; // 环绕方式
    const char* m_alias = ShaderProgram::UNIFORM_TEXTURE; // 纹理别名(着色器中变量名)
    GLenum m_unit = 0; // 纹理单元 (可能有多个纹理)
    bool m_isDirty = true; // 是否有脏数据 (纹理参数需要更新)

public:
    FrameBufferObject(Format* format, int width, int height, bool hasDepth, bool hasStencil);
    ~FrameBufferObject() override;
    void setTexParameter(GLint filter, GLint wrap) override;
    void setBindParameter(char* alias, GLenum unit) override;
    void begin(); // 开始将内容渲染到fbo
    void end(); // 结束将内容渲染到fbo
    void bind(ShaderProgram* shader) override;

private:
    void applyTexParameter();
};
} // namespace glcore​    frame_buffer_object.cpp
#include "glcore/frame_buffer_object.h"
#include "glcore/gl_inspector.h"

namespace glcore
{
FrameBufferObject::FrameBufferObject(Format* format, int width, int height, bool hasDepth, bool hasStencil)
{
    m_format = format;
    m_width = width;
    m_height = height;
    m_hasDepth = hasDepth;
    m_hasStencil = hasStencil;
    GL_CALL(glGenFramebuffers(1, &m_frameBufferHandle));
    begin();
    if (m_hasDepth)
    {
      GL_CALL(glGenRenderbuffers(1, &m_depthBufferHandle));
      GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_depthBufferHandle));
      GL_CALL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height));
      GL_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
            GL_RENDERBUFFER, m_depthBufferHandle));
      GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
    }
    if (m_hasStencil)
    {
      GL_CALL(glGenRenderbuffers(1, &m_stencilBufferHandle));
      GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_stencilBufferHandle));
      GL_CALL(glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height));
      GL_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
            GL_RENDERBUFFER, m_stencilBufferHandle));
      GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
    }
    GL_CALL(glGenTextures(1, &m_colorTextureHandle));
    GL_CALL(glBindTexture(GL_TEXTURE_2D, m_colorTextureHandle));
    GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, m_format->getFormat(), m_width,
    m_height, 0, m_format->getFormat(), m_format->getType(), nullptr));
    applyTexParameter();
    GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
      GL_TEXTURE_2D, m_colorTextureHandle, 0));
    end();
}

FrameBufferObject::~FrameBufferObject()
{
    GL_CALL(glBindTexture(GL_TEXTURE_2D, 0));
    GL_CALL(glDeleteTextures(1, &m_colorTextureHandle));
    if (m_hasDepth) {
      GL_CALL(glDeleteRenderbuffers(1, &m_depthBufferHandle));
    }
    if (m_hasStencil) {
      GL_CALL(glDeleteRenderbuffers(1, &m_stencilBufferHandle));
    }
    GL_CALL(glDeleteFramebuffers(1, &m_frameBufferHandle));
}

void FrameBufferObject::setTexParameter(GLint filter, GLint wrap)
{
    m_filter = filter;
    m_wrap = wrap;
    m_isDirty = true;
}

void FrameBufferObject::setBindParameter(char* alias, GLenum unit)
{
    m_alias = alias;
    m_unit = unit;
}

void FrameBufferObject::begin()
{
    GL_CALL(glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_preFramebufferHandle));
    GL_CALL(glGetIntegerv(GL_VIEWPORT, m_preFramebufferViewPort));
    GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferHandle));
    GL_CALL(glViewport(0, 0, m_width, m_height));
}

void FrameBufferObject::end()
{
    GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_preFramebufferHandle));
    GL_CALL(glViewport(m_preFramebufferViewPort, m_preFramebufferViewPort,
      m_preFramebufferViewPort, m_preFramebufferViewPort));
}

void FrameBufferObject::bind(ShaderProgram* shader)
{
    shader->setUniformi(m_alias, m_unit);
    GL_CALL(glActiveTexture(GL_TEXTURE0 + m_unit));
    GL_CALL(glBindTexture(GL_TEXTURE_2D, m_colorTextureHandle));
    if (m_isDirty)
    {
      applyTexParameter();
    }
}

void FrameBufferObject::applyTexParameter()
{
    GL_CALL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter));
    GL_CALL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter));
    GL_CALL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_wrap));
    GL_CALL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_wrap));
    m_isDirty = false;
}
} // namespace glcore​    format.h
#pragma once

#include "core_lib.h"

namespace glcore
{
/**
* 纹理格式
* @author little fat sheep
*/
class Format
{
private:
    GLint format;
    GLenum type;

public:
    Format(GLint format, GLenum type);
    GLint getFormat() { return format; }
    GLenum getType() { return type; }

    static Format* Alpha();
    static Format* LuminanceAlpha();
    static Format* RGB565();
    static Format* RGBA4444();
    static Format* RGB888();
    static Format* RGBA8888();
};
} // namespace glcore​    format.cpp
#include "glcore/format.h"

namespace glcore
{
Format::Format(GLint format, GLenum type):
    format(format),
    type(type)
{
}

Format *Format::Alpha()
{
    return new Format(GL_ALPHA, GL_UNSIGNED_BYTE);
}

Format *Format::RGB565()
{
    return new Format(GL_RGB, GL_UNSIGNED_SHORT_5_6_5);
}

Format *Format::RGBA4444()
{
    return new Format(GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4);
}

Format *Format::RGB888()
{
    return new Format(GL_RGB, GL_UNSIGNED_BYTE);
}

Format *Format::RGBA8888()
{
    return new Format(GL_RGBA, GL_UNSIGNED_BYTE);
}
} // namespace glcore3 工具相关

​    本节主要介绍 glcore 框架在初始化过程中所依附的工具类,如字符串加载工具、图片加载工具等,它们与平台相关(如 Android 平台字符串和图片加载依赖 JNI),不便于进行跨平台迁移,因此不能将它们归入 glcore 框架中。
3.1 StringUtils

​    StringUtils 用于加载顶点和片元着色器资源为字符串。
​    string_utils.h
#pragma once

/**
* String工具类
* @author little fat sheep
*/
class StringUtils
{
public:
    /**
   * 根据资源路径读取字符串
   * @param filePath 资源文件路径, 如: "assets/shaders/vertex_shader.glsl"
   */
    static const char* loadStringFromFile(const char* filePath);
};​    string_utils.cpp
#include <fstream>
#include <sstream>
#include <string>

#include "utils/string_utils.h"

using namespace std;

const char* StringUtils::loadStringFromFile(const char* filePath)
{
    ifstream file(filePath);
    if (!file.is_open())
    {
                printf("failed to open file: %s\n", filePath);
                return nullptr;
    }
    stringstream buffer;
    buffer << file.rdbuf();
    file.close();
    string content = buffer.str();
    // 复制字符串, 避免content被回收导致悬垂指针问题
    char * res = strdup(content.c_str());
    return res;
}4 应用

​    本节将基于 glcore 框架写一个色散特效叠加果冻特效的 Demo,体验一下 glcore 的便捷之处。
4.1 MyRenderer

​    my_renderer.h
#pragma once

struct BitmapData
{
    void* buffer;
    int width;
    int height;
};

/**
* Bitmap工具类
* @author little fat sheep
*/
class BitmapUtils
{
public:
    /**
    * 根据资源文件路径读取bitmap
    * @param filePath 资源路径, 如: "assets/textures/xxx.jpg"
    */
    static BitmapData* loadBitmapDataFromFile(const char* filePath);
};​    my_renderer.cpp
#define STB_IMAGE_IMPLEMENTATION
#include "stb/stb_image.h"

#include "utils/bitmap_utils.h"

BitmapData* BitmapUtils::loadBitmapDataFromFile(const char* filePath) {
    int width, height, channels;
        unsigned char* data = stbi_load(filePath, &width, &height, &channels, STBI_rgb_alpha);
        BitmapData* bitmapData = new BitmapData();
        bitmapData->buffer = data;
        bitmapData->width = width;
        bitmapData->height = height;
        return bitmapData;
}4.2 DispersionEffect

​    DispersionEffect 是色散特效。
​    dispersion_effect.h
#pragma once

#include "glcore/core.h"
#include "dispersion_effect.h"
#include "jelly_effect.h"

using namespace glcore;

/**
* 自定义渲染器
* @author little fat sheep
*/
class MyRenderer : public EGLSurfaceView::Renderer
{
private:
    DispersionEffect* m_dispersionEffect;
    JellyEffect* m_jellyEffect;
    long m_startTime = 0;
    float m_runTime = 0.0f;

public:
    MyRenderer();
    ~MyRenderer() override;
    void onSurfaceCreated() override;
    void onSurfaceChanged(int width, int height) override;
    void onDrawFrame() override;

private:
    long getTimestamp();
};​    dispersion_effect.cpp
#include <chrono>
#include <iostream>

#include "glcore/core.h"
#include "render/my_renderer.h"

#define TAG "MyRenderer"

using namespace glcore;
using namespace std::chrono;

MyRenderer::MyRenderer()
{
    printf("%s: init\n", TAG);
    m_dispersionEffect = new DispersionEffect();
    m_jellyEffect = new JellyEffect();
    m_jellyEffect->setTexAction(m_dispersionEffect);
}

MyRenderer::~MyRenderer()
{
    printf("%s: destroy\n", TAG);
    delete m_dispersionEffect;
    delete m_jellyEffect;
}

void MyRenderer::onSurfaceCreated()
{
    printf("%s: onSurfaceCreated\n", TAG);
    m_dispersionEffect->onCreate();
    m_jellyEffect->onCreate();
    GL_CALL(glClearColor(0.1f, 0.2f, 0.3f, 0.4f));
    m_startTime = getTimestamp();
}

void MyRenderer::onSurfaceChanged(int width, int height)
{
    printf("%s: onSurfaceChanged, width: %d, height: %d\n", TAG, width, height);
    glViewport(0, 0, width, height);
    m_dispersionEffect->onResize(width, height);
    m_jellyEffect->onResize(width, height);
}

void MyRenderer::onDrawFrame()
{
    m_runTime = (getTimestamp() - m_startTime) / 1000.0f;
    GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
    m_dispersionEffect->onDraw(m_runTime);
    m_jellyEffect->onDraw(m_runTime);
}

long MyRenderer::getTimestamp()
{
    auto now = std::chrono::system_clock::now(); // 获取当前时间
    auto duration = now.time_since_epoch(); // 转换为自纪元以来的时间
    return duration_cast<milliseconds>(duration).count();
}​    dispersion_vert.glsl
#pragma once

#include "glcore/core.h"

using namespace glcore;

/**
* 色散特效
* @author little fat sheep
*/
class DispersionEffect: public TextureAction
{
private:
    ShaderProgram* m_program;
    Mesh* m_mesh;
    GLTexture* m_glTexture;
    FrameBufferObject* m_fbo;

public:
    DispersionEffect();
    ~DispersionEffect() override;
    void onCreate();
    void onResize(int width, int height);
    void onDraw(float runtime);
    void bind(ShaderProgram* shader) override;

private:
    void createProgram();
    void createTexture();
};​    dispersion_frag.glsl
#include <iostream>

#include "glcore/core.h"

#include "render/dispersion_effect.h"
#include "utils/bitmap_utils.h"
#include "utils/string_utils.h"

#define TAG "DispersionEffect"

using namespace glcore;

DispersionEffect::DispersionEffect()
{
    printf("%s: init\n", TAG);
}

DispersionEffect::~DispersionEffect()
{
    printf("%s: destroy\n", TAG);
    delete m_program;
    delete m_mesh;
    delete m_glTexture;
    delete m_fbo;
}

void DispersionEffect::onCreate()
{
    printf("%s: onCreate\n", TAG);
    createProgram();
    createTexture();
    m_mesh = MeshUtils::createRect(true);
    m_fbo = new FrameBufferObject(Format::RGBA8888(), app->width, app->height, false, false);
}

void DispersionEffect::onResize(int width, int height)
{
    printf("%s: onResize, width: %d, height: %d\n", TAG, width, height);
}

void DispersionEffect::onDraw(float runtime)
{
    m_fbo->begin();
    m_program->bind();
    m_program->setUniformf("u_time", runtime);
    m_program->setUniformf("u_aspect", app->aspect);
    m_glTexture->bind(m_program);
    m_mesh->render(m_program, GL_TRIANGLES);
    m_fbo->end();
}

void DispersionEffect::bind(ShaderProgram* shader)
{
    m_fbo->bind(shader);
}

void DispersionEffect::createProgram()
{
    printf("%s: createProgram\n", TAG);
    const char* vertexCode = StringUtils::loadStringFromFile("assets/shaders/dispersion_vert.glsl");
    const char* fragmentCode = StringUtils::loadStringFromFile("assets/shaders/dispersion_frag.glsl");
    m_program = new ShaderProgram(vertexCode, fragmentCode);
}

void DispersionEffect::createTexture()
{
    printf("%s: createTexture\n", TAG);
    BitmapData* data = BitmapUtils::loadBitmapDataFromFile("assets/textures/girl.jpg");
    m_glTexture = new GLTexture(data->buffer, data->width, data->height);
}4.3 JellyEffect

​    JellyEffect 是果冻特效。
​    jelly_effect.h
attribute vec4 a_position;
attribute vec2 a_texCoord0;

varying vec2 v_texCoord;

void main() {
   gl_Position = a_position;
   v_texCoord = a_texCoord0;
}​    jelly_effect.cpp
precision highp float;

uniform float u_aspect;
uniform float u_time;
uniform sampler2D u_texture;

varying vec2 v_texCoord;

vec2 getOffset() { // 偏移函数
   float time = u_time * 1.5;
   vec2 dire = vec2(sin(time), cos(time));
   float strength = sin(u_time * 2.0) * 0.004;
   return dire * strength * vec2(1.0, 1.0 / u_aspect);
}

void main() {
   vec2 offset = getOffset();
   vec4 color = texture2D(u_texture, v_texCoord);
   color.r = texture2D(u_texture, v_texCoord + offset).r;
   color.b = texture2D(u_texture, v_texCoord - offset).b;
   gl_FragColor = color;
}​    jelly_vert.glsl
#pragma once

#include "glcore/core.h"

using namespace glcore;

/**
* 色散特效
* @author little fat sheep
*/
class DispersionEffect: public TextureAction
{
private:
    ShaderProgram* m_program;
    Mesh* m_mesh;
    GLTexture* m_glTexture;
    FrameBufferObject* m_fbo;

public:
    DispersionEffect();
    ~DispersionEffect() override;
    void onCreate();
    void onResize(int width, int height);
    void onDraw(float runtime);
    void bind(ShaderProgram* shader) override;

private:
    void createProgram();
    void createTexture();
};​    jelly_frag.glsl
#include <iostream>

#include "glcore/core.h"

#include "render/jelly_effect.h"
#include "utils/string_utils.h"

#define TAG "JellyEffect"

using namespace glcore;

JellyEffect::JellyEffect()
{
    printf("%s: init\n", TAG);
}

JellyEffect::~JellyEffect()
{
    printf("%s: destroy\n", TAG);
    delete m_program;
    delete m_mesh;
}

void JellyEffect::setTexAction(TextureAction* texAction)
{
    m_texAction = texAction;
}

void JellyEffect::onCreate()
{
    printf("%s: onCreate\n", TAG);
    createProgram();
    m_mesh = MeshUtils::createRect(false);
}

void JellyEffect::onResize(int width, int height)
{
    printf("%s: onResize, width: %d, height: %d\n", TAG, width, height);
}

void JellyEffect::onDraw(float runtime)
{
    m_program->bind();
    m_program->setUniformf("u_time", runtime);
    m_program->setUniformf("u_aspect", app->aspect);
    m_texAction->bind(m_program);
    m_mesh->render(m_program, GL_TRIANGLE_FAN);
}

void JellyEffect::createProgram()
{
    printf("%s: createProgram\n", TAG);
    const char* vertexCode = StringUtils::loadStringFromFile("assets/shaders/jelly_vert.glsl");
    const char* fragmentCode = StringUtils::loadStringFromFile("assets/shaders/jelly_frag.glsl");
    m_program = new ShaderProgram(vertexCode, fragmentCode);
}4.4 运行效果

​    运行效果如下,可以看到叠加了色散和果冻特效。

​    声明:本文转自【OpenGL ES】在Windows上手撕一个mini版的渲染框架。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 【OpenGL ES】在Windows上手撕一个mini版的渲染框架