Chapter 103 · 离屏渲染与 GPU 管线

Shader 变体集

VariantSet 管理同一 Shader 的多个编译变体(通过宏定义区分),按需编译并缓存。

103

概念

同一渲染效果可能需要多个 Shader 变体(如:有/无法线贴图、有/无雾效)。 VariantSet 以源码模板 + 宏定义组合为 Key,按需编译并缓存变体, 避免手动管理大量 Shader 对象。

API 速查

函数说明
int xgeShaderVariantSetInit(xge_shader_variant_set pSet, const char* sVertexSource, const char* sFragmentSource)初始化变体集
void xgeShaderVariantSetFree(xge_shader_variant_set pSet)释放变体集
int xgeShaderVariantGet(xge_shader_variant_set pSet, uint32_t iKey, const xge_shader_define_t* pDefines, int iDefineCount, xge_shader* ppShader)获取/编译变体

编译本章示例

gcc -O2 -I. -o ch103.exe ch103_main.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch103.exe

示例:多特性 Shader

// 片元着色器模板(含条件编译)
const char* fsSrc =
    "precision mediump float;\n"
    "varying vec2 vUV;\n"
    "uniform sampler2D uTex;\n"
    "#ifdef ENABLE_FOG\n"
    "uniform float uFogDensity;\n"
    "#endif\n"
    "void main() {\n"
    "  vec4 c = texture2D(uTex, vUV);\n"
    "#ifdef ENABLE_FOG\n"
    "  c.rgb = mix(c.rgb, vec3(0.8), uFogDensity);\n"
    "#endif\n"
    "  gl_FragColor = c;\n"
    "}\n";

xge_shader_variant_set_t vs;
xgeShaderVariantSetInit(&vs, vsSrc, fsSrc);

// 获取无雾变体
xge_shader shaderPlain;
xgeShaderVariantGet(&vs, 0, NULL, 0, &shaderPlain);

// 获取有雾变体
xge_shader_define_t defs[1] = {
    { "ENABLE_FOG", "1" }
};
xge_shader shaderFog;
xgeShaderVariantGet(&vs, 1, defs, 1, &shaderFog);

完整代码

将以下代码保存为 ch103_main.c,编译后运行即可看到 Shader 变体集与条件编译效果:

#include "xge.h"

static xge_shader_variant_set_t g_vs;
static xge_shader g_shaderPlain;
static xge_shader g_shaderFog;
static xge_font g_font = NULL;

static const char* g_vsSrc =
    "attribute vec2 aPos;\n"
    "attribute vec2 aUV;\n"
    "varying vec2 vUV;\n"
    "void main() {\n"
    "  vUV = aUV;\n"
    "  gl_Position = vec4(aPos, 0.0, 1.0);\n"
    "}\n";

static const char* g_fsSrc =
    "precision mediump float;\n"
    "varying vec2 vUV;\n"
    "uniform sampler2D uTex;\n"
    "#ifdef ENABLE_FOG\n"
    "uniform float uFogDensity;\n"
    "#endif\n"
    "void main() {\n"
    "  vec4 c = texture2D(uTex, vUV);\n"
    "#ifdef ENABLE_FOG\n"
    "  c.rgb = mix(c.rgb, vec3(0.8), uFogDensity);\n"
    "#endif\n"
    "  gl_FragColor = c;\n"
    "}\n";

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x081114FF);

    xgeTextDraw(g_font, "Variant 0: Plain (no fog)",
        50.0f, 50.0f, 0xFFFFFFFF)
    xgeTextDraw(g_font, "Variant 1: ENABLE_FOG",
        50.0f, 100.0f, 0x88CCFFFF)
    xgeTextDraw(g_font,
        "Same source, different #define combinations",
        50.0f, 150.0f, 0xAAAAAAFF)

    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth  = 800;
    d.iHeight = 600;
    d.sTitle  = "ch103 - Shader Variants";
    d.iFlags  = XGE_INIT_VSYNC;
    xgeInit(&d);

    xgeFontLoad(&g_font, "Roboto.ttf", 18.0f);

    // 初始化变体集
    xgeShaderVariantSetInit(&g_vs, g_vsSrc, g_fsSrc);

    // 获取无雾变体
    xgeShaderVariantGet(&g_vs, 0, NULL, 0,
        &g_shaderPlain);

    // 获取有雾变体
    xge_shader_define_t defs[1] = {
        { "ENABLE_FOG", "1" }
    };
    xgeShaderVariantGet(&g_vs, 1, defs, 1,
        &g_shaderFog);

    xgeRun(onFrame, NULL);

    xgeShaderVariantSetFree(&g_vs);
    xgeFontFree(g_font);
    xgeUnit();
    return 0;
}

代码讲解

  • VariantSetInit:从模板源码初始化变体集,支持 #ifdef 条件编译。
  • VariantGet:按 Key + Defines 获取/编译变体,相同组合只编译一次。
  • Defines:对应 GLSL 的 #define 宏,控制特性开关(如雾效、光照)。
  • 缓存机制:已编译的变体自动缓存,避免重复编译开销。

要点与陷阱

  • 缓存:相同 Key + Defines 只编译一次,后续直接返回缓存。
  • Key:用户自定义的变体标识,用于快速查找。
  • Defines:对应 GLSL 的 #define 宏,控制条件编译分支。
  • Free:释放变体集会释放所有已编译的 Shader。

进阶练习

  1. 实现质量分级:LOW/MEDIUM/HIGH 三个变体(不同光照复杂度)。
  2. 动态切换:运行时根据 GPU 能力选择变体。
Shader 变体集效果
图:Shader 变体集渲染效果