跳转至

基于纹理缓存来传递字体的轮廓信息


title: [GPU矢量纹理] 基于纹理缓存来传递字体的轮廓信息

在“GPU矢量纹理“算法中,我们会将 文字的轮廓信息 通过 Texture Buffer 传送到GPU当中,以便在GPU中渲染出文字。

本文将重点介绍在“GPU矢量纹理”案例中,对OpenGL Texture Buffer的使用。

着色器

先看在着色器中如何使用Texture Buffer。

#version 330 core

//字形描述
struct Glyph {
    int start, count; //Curves中的索引
};

//二次贝塞尔曲线
struct Curve {
    vec2 p0, p1, p2;
};

uniform isamplerBuffer glyphs;  //字形缓存纹理(存储所有的字形描述)
uniform samplerBuffer curves;   //曲线缓存纹理(存储所有字形的贝塞尔曲线)

// 根据索引,从glyphs中加载Glyph
Glyph loadGlyph(int index) {
    Glyph result;
    ivec2 data = texelFetch(glyphs, index).xy; 
        //texelFetch直接从缓冲区中取出像素值,不进行插值处理
    result.start = data.x;
    result.count = data.y;
    return result;
}

// 根据索引,从curves中加载Curve
Curve loadCurve(int index) {
    Curve result;
    result.p0 = texelFetch(curves, 3*index+0).xy;
    result.p1 = texelFetch(curves, 3*index+1).xy;
    result.p2 = texelFetch(curves, 3*index+2).xy;
    return result;
}

与使用2D纹理不同的是

Texture Buffer Texture 2D
申明 uniform sampler2D texture1; uniform isamplerBuffer glyphs;
取值 in vec2 TexCoord; //[0, 1]
texture(texture1, TexCoord);
int index; //没有映射到[0,1]
texelFetch(glyphs, index).xy;

在CPU中如何准备数据

接下来看,如何在CPU中准备GPU所需数据。

创建与GPU所映射的数据结构

//字形(在Buffer中的位置)
struct BufferGlyph {
    //start 开始位置
    //count 曲线个数
    int32_t start, count; // range of bezier curves belonging to this glyph
};

//曲线(即为贝塞尔曲线,由三个控制点所组成)
struct BufferCurve {
    //三个顶点坐标
    float x0, y0, x1, y1, x2, y2;
};

创建与Texture Buffer所映射的数据结构

std::vector<BufferGlyph> bufferGlyphs;  //N个字形(对bufferCurves的索引)
std::vector<BufferCurve> bufferCurves;  //N个字形的轮廓线都存储在这里

填充数据

根据需求,创建数据,并保存到bufferGlyphsbufferCurves中即可。

  1. 组织BufferGlyph,并保存到bufferGlyphs
// 构建一个字符
void buildGlyph(uint32_t charcode, FT_UInt glyphIndex) {
    //# 组织BufferGlyph
    BufferGlyph bufferGlyph;
    //...
    for (int i = 0; i < face->glyph->outline.n_contours; i++) {
        convertContour(); //组织BufferCurve
        //...
    }

    //...
    bufferGlyphs.push_back(bufferGlyph);

    //...
}
  1. 组织BufferCurve,并保存到bufferCurves
void convertContour(...)
{
    auto makeCurve = [](const glm::vec2& p0, 
        const glm::vec2& p1, 
        const glm::vec2& p2) 
    {
        BufferCurve result;
        result.x0 = p0.x;
        result.y0 = p0.y;
        result.x1 = p1.x;
        result.y1 = p1.y;
        result.x2 = p2.x;
        result.y2 = p2.y;
        return result;
    };

    //...
    curves.push_back(makeCurve(b0, c0, d));
}

将数据从CPU传递到GPU

那么,如何将CPU中的数据如何传递到GPU中呢?

创建纹理与缓存

/*
 *1. 本字体所有字形的轮廓线都存储在bufferCurves当中,用BufferGlyph来记录各个字形的索引
 *2. 对于bufferGlyphs、bufferCurves,是当成Texture传入GPU的,因此还需要两个纹理插槽
 */
GLuint glyphTexture, curveTexture;  
    //glyphBuffer、curveBuffer实际上是使用的Texture的缓冲区
GLuint glyphBuffer, curveBuffer;    
    //在GPU中,bufferGlyphs、bufferCurves的缓冲区ID

//创建纹理
glGenTextures(1, &glyphTexture);
glGenTextures(1, &curveTexture);

//创建Buffer
glGenBuffers(1, &glyphBuffer);
glGenBuffers(1, &curveBuffer);

绑定纹理与缓存

//uniform isamplerBuffer glyphs;
glBindTexture(GL_TEXTURE_BUFFER, glyphTexture);        
    //明确glyphTexture的类型是Texture Buffer
glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32I, glyphBuffer);
    //将glyphBuffer绑定到glyphTexture上
glBindTexture(GL_TEXTURE_BUFFER, 0);
    //绑定一个0句柄的纹理(即解绑glyphTexture的意思)

//uniform samplerBuffer curves; 
glBindTexture(GL_TEXTURE_BUFFER, curveTexture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, curveBuffer);
glBindTexture(GL_TEXTURE_BUFFER, 0);

将CPU中的数据更新到GPU中

void uploadBuffers() {
    glBindBuffer(GL_TEXTURE_BUFFER, glyphBuffer);
        //绑定缓存
    glBufferData(
        GL_TEXTURE_BUFFER, 
        sizeof(BufferGlyph) * bufferGlyphs.size(), 
        bufferGlyphs.data(), 
        GL_STATIC_DRAW
    );
        //将CPU中的数据更新到GPU中
    glBindBuffer(GL_TEXTURE_BUFFER, 0);
        //绑定一个0句柄的缓存(即解绑glyphBuffer的意思)

    glBindBuffer(GL_TEXTURE_BUFFER, curveBuffer);
    glBufferData(GL_TEXTURE_BUFFER, 
        sizeof(BufferCurve) * bufferCurves.size(), 
        bufferCurves.data(), 
        GL_STATIC_DRAW
    );
    glBindBuffer(GL_TEXTURE_BUFFER, 0);
}

绘制时绑定纹理

GLint location;

location = glGetUniformLocation(program, "glyphs");
glUniform1i(location, 0); //纹理插槽 0
location = glGetUniformLocation(program, "curves");
glUniform1i(location, 1); //纹理插槽 1

//0纹理插槽,绑定glyphTexture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, glyphTexture);

//1纹理插槽,绑定curveTexture
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_BUFFER, curveTexture);

glActiveTexture(GL_TEXTURE0);

释放资源

glDeleteTextures(1, &glyphTexture);
glDeleteTextures(1, &curveTexture);

glDeleteBuffers(1, &glyphBuffer);
glDeleteBuffers(1, &curveBuffer);