OpenGL数据可视化(4)中英 (C++ string 单双字节) 混合字符串渲染

在数据可视化时,渲染字符是必不可少的,如果只渲染0-9数字和26个英文字母,那么 LearnOpenGL CN 的文字渲染教程就足够了,但涉及到中文字符渲染就有些麻烦了。

解决思路:统一使用 std::string 初始化需要渲染的单双字节混合字符串,创建两个 std::map<wchar_t, Character>,分别储存需要渲染的单字节字符和双字节字符,创建map的方法与教程类似。

string 单双字节混合字符串逐字分割

c++ 的 std::string 是 GBK 编码的(在我电脑上是这样的),GBK 编码中,单字节字符的第一位为0,双字节字符第一个字节的第一位为1,由此可进行单双字节字符判断及分割:

void splitGBK(std::string chars, std::vector<std::string>& words, std::vector<int>& sizes){
    int size, i = 0, len = chars.length();
    while (i < len) {
        if ((chars[i] & 0x80) == 0x00)
            size = 1;
        else
            size = 2;
        words.push_back(chars.substr(i, size));
        sizes.push_back(size);
        i += size;
    }
}

示例代码:

setlocale(LC_ALL, "chs");
std::string str = "文abc字";
std::vector <std::string> words;
std::vector <int> sizes;
splitGBK(str, words, sizes);

wchar_t* w = (wchar_t*)calloc(2, sizeof(wchar_t));
for (int i = 0;i < words.size();i++) {
    MultiByteToWideChar(CP_ACP, 0, (LPCSTR)words[i].c_str(), -1, (LPWSTR)w, sizes[i]);
    wprintf(L"%d: %s\tsize: %d \n", i, w, sizes[i]);
}

运行结果:

0: 文   size: 2
1: a    size: 1
2: b    size: 1
3: c    size: 1
4: 字   size: 2

创建两个分别储存单双字节的 map

// 单双字节字体
FT_Face face_cn;
FT_Face face_en;
FT_New_Face(ft, font_cn_path, 0, &face_cn);
FT_New_Face(ft, font_en_path, 0, &face_en);

// 单双字节map
std::map<wchar_t, Character> Characters;
std::map<wchar_t, Character> wCharacters;

// 将单双字节混合字符串str逐字添加进相应map
splitGBK(str, words, sizes);
wchar_t* ch = (wchar_t*)calloc(1, sizeof(wchar_t));
for (int i = 0;i < words.size();i++) {
    MultiByteToWideChar(CP_ACP, 0, (LPCSTR)words[i].c_str(), -1, (LPWSTR)ch, sizes[i]);
    if (sizes[i] == 1)
        FT_Load_Char(face_en, *ch, FT_LOAD_RENDER);
    else
        FT_Load_Char(face_cn, *ch, FT_LOAD_RENDER);
}

多纹理同时渲染

由于字符串占用一个纹理,可视化数据占用一个纹理,至少需要渲染两个纹理,此时需要在片段着色器中使用 uniform 变量决定渲染哪一个纹理,片段着色器:

#version 330 core
out vec4 FragColor;

in vec2 TexCoord;

uniform sampler2D texture1;
uniform sampler2D text1;
uniform vec3 textColor;
uniform bool istext;
void main(){
	if (istext)
		FragColor = vec4(textColor, texture(text1, TexCoord).r);
	else
		FragColor = texture(texture1, TexCoord);
}

在程序中,设置该 uniform 变量的值来实现多纹理同时渲染,类似这样:

// 渲染循环
while (!glfwWindowShouldClose(window)){
    // 省略------
    // 渲染可视化数据的纹理
    ourShader.setBool("istext", false);
    /* ---省略--- */

    // 渲染字符串的纹理
    ourShader.setBool("istext", true);
    /* ---省略--- */
}

设置渲染图像保持比例、保持居中

改变窗口大小,渲染图像的比例也会随之变化,文字就会变形

解决方法在于修改 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); 中的 framebuffer_size_callback 函数:

const unsigned int SCR_WIDTH = 600;
const unsigned int SCR_HEIGHT = 300;
const float ratio = SCR_HEIGHT / (float)SCR_WIDTH;

void framebuffer_size_callback(GLFWwindow* window, int width, int height){
    float new_ratio = height / (float)width;
    if (new_ratio > ratio)
        glViewport(0, abs(height - width * ratio)/2, width, width * ratio);
    else if (new_ratio < ratio)
        glViewport(abs(width - height / ratio) / 2, 0, height/ratio, height);
    else
        glViewport(0, 0, width, height);
}

"有趣"的事

有时候会遇到这样的问题:将 PDF 中的文字复制进 word 时,英文字母很宽,给它设置西文字体(如 Times)也设置不了,令人头疼。

问题的原因:复制的是全角字符,Times 字体中没有全角字符,故无法设置。

GBK 编码是从 0x81 开始的,不含ASCII码,但是 GBK 码中也有英文字母和阿拉伯数字,即全角英文字母和全角阿拉伯数字。对于英文字母和阿拉伯数字,全角字符的宽度大约为半角字符的 2 倍,下图可以看出它们的宽度不同(没有打空格哦)。或者试着复制:全角abc半角abc,也能发现这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值