Chapter 102 · 离屏渲染与 GPU 管线

自定义 Shader

Shader 是 GPU 端的渲染程序,XGE 支持自定义顶点/片元着色器,通过 Uniform 传递参数。

102

概念

Shader 由顶点着色器(Vertex)和片元着色器(Fragment)组成。 XGE 使用 GLSL ES 语法。创建后可通过 Uniform 函数传递标量、向量参数。 Shader 使用引用计数管理,可被多个 Material 共享。

API 速查

函数说明
int xgeShaderCreate(xge_shader pShader, const char* sVertexSource, const char* sFragmentSource)编译着色器
int xgeShaderAddRef(xge_shader pShader)增加引用
void xgeShaderFree(xge_shader pShader)释放
int xgeShaderUniform1i(xge_shader pShader, const char* sName, int iX)设置 int uniform
int xgeShaderUniform1f(xge_shader pShader, const char* sName, float fX)设置 float uniform
int xgeShaderUniform2f(xge_shader pShader, const char* sName, float fX, float fY)设置 vec2 uniform
int xgeShaderUniform3f(xge_shader pShader, const char* sName, float fX, float fY, float fZ)设置 vec3 uniform
int xgeShaderUniform4f(xge_shader pShader, const char* sName, float fX, float fY, float fZ, float fW)设置 vec4 uniform

编译本章示例

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

示例:灰度 Shader

const char* 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";

const char* fsSrc =
    "precision mediump float;\n"
    "varying vec2 vUV;\n"
    "uniform sampler2D uTex;\n"
    "uniform float uGray;\n"
    "void main() {\n"
    "  vec4 c = texture2D(uTex, vUV);\n"
    "  float g = dot(c.rgb, vec3(0.299,0.587,0.114));\n"
    "  gl_FragColor = mix(c, vec4(g,g,g,c.a), uGray);\n"
    "}\n";

xge_shader_t shader;
xgeShaderCreate(&shader, vsSrc, fsSrc);

// 设置灰度强度
xgeShaderUniform1f(&shader, "uGray", 0.8f);

完整代码

将以下代码保存为 ch102_main.c,编译后运行即可看到灰度 Shader 动画效果:

#include "xge.h"
#include <math.h>

static xge_shader_t g_shader;
static xge_font g_font = NULL;
static float g_time = 0.0f;

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"
    "uniform float uGray;\n"
    "void main() {\n"
    "  vec4 c = texture2D(uTex, vUV);\n"
    "  float g = dot(c.rgb, vec3(0.299,0.587,0.114));\n"
    "  gl_FragColor = mix(c, vec4(g,g,g,c.a), uGray);\n"
    "}\n";

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x081114FF);
    g_time += 0.016f;

    // 动态灰度强度(0~1 循环)
    float gray = 0.5f + 0.5f * sinf(g_time * 2.0f);
    xgeShaderUniform1f(&g_shader, "uGray", gray);

    xgeTextDraw(g_font, "Grayscale Shader (animated)",
        50.0f, 50.0f, 0xFFFFFFFF)

    return XGE_OK;
}

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

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

    // 创建 Shader
    xgeShaderCreate(&g_shader, g_vsSrc, g_fsSrc);

    xgeRun(onFrame, NULL);

    xgeShaderFree(&g_shader);
    xgeFontFree(g_font);
    xgeUnit();
    return 0;
}

代码讲解

  • ShaderCreate:从顶点/片元着色器源码编译 GLSL 程序。
  • 灰度算法:用 ITU-R BT.601 系数 (0.299, 0.587, 0.114) 计算亮度。
  • Uniform1f:每帧设置 uGray 值,实现彩色↔灰度平滑过渡。
  • GLSL ES:使用 OpenGL ES 2.0 语法,precision 声明必须。

要点与陷阱

  • GLSL ES:使用 OpenGL ES 2.0 语法,precision 声明必须。
  • 编译错误:Create 返回非 0 表示编译失败,查看日志。
  • Uniform 名称:必须与 GLSL 中声明完全一致。
  • 引用计数:多个 Material 可共享同一 Shader。

进阶练习

  1. 实现高斯模糊 Shader:两 Pass 分离式模糊。
  2. 实现色彩偏移特效:RGB 通道分别采样偏移。
自定义 Shader效果
图:自定义 Shader渲染效果