OpenGL 绘制简单的英文字符

http://learnopengl-cn.readthedocs.io/zh/latest/06%20In%20Practice/02%20Text%20Rendering/

  1. FreeType 配置
    下载FreeType2 得到: ft271.zip 解压 freetype-2.7.1
    在CMake中,如下图 顺序
    OpenGL 绘制简单的英文字符_第1张图片

编译成功后得到 lib 文件 freetype271.lib 可自行编译成不同版本 (在此为 LIB Release)

OpenGL 绘制简单的英文字符_第2张图片

再用VS 新建一个工程,把 \freetype-2.7.1\include 目录包含进工程, 并将Lib文件复至工程下
输入如下代码,运行成功, 即 freetype271.lib 使用正常

#include   
#include FT_FREETYPE_H  
#include   
using namespace std;  


int main()  
{  
    FT_Library library;  
    FT_Init_FreeType(&library);  


    FT_Face face;  
    FT_New_Face(library, "msyh.ttf", 0, &face);  


    cout<<"num_glyphs:"<num_glyphs<cout<<"num_faces:"<num_faces<"Pause");  
    return 0;  
}  
  1. OpenGL

主程序:


#include 
#include 
#include 

#define GLEW_STATIC
#include 
// GLFW
#include 

// FreeType
#include 
#include FT_FREETYPE_H

// GL includes
#include "Shader.h"

#pragma comment (lib, "opengl32.lib")
#pragma comment (lib, "glew32s.lib")
#pragma comment (lib, "glfw3.lib") 

#pragma comment (lib, "freetype271.lib")

const GLuint WIDTH = 400, HEIGHT = 300;

// 保存字体信息
struct Character {
    GLuint     TextureID;       // 字形纹理ID
    glm::ivec2 Size;                // 字形大大小
    glm::ivec2 Bearing;     // 字形基于基线和起点的位置
    GLuint     Advance;     // 起点到下一个字形起点的距离
};

GLuint VAO, VBO;
std::map mapCharacters;

void RenderText(Shader &shader, std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color);

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* pWnd = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
    glfwMakeContextCurrent(pWnd);

    glewExperimental = GL_TRUE;
    glewInit();

    glViewport(0, 0, WIDTH, HEIGHT);

    glEnable(GL_CULL_FACE);
    glEnable(GL_BLEND);     // 开启混合
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Shacer加载编译
    Shader shader("./Shader/text.vs", "./Shader/text.frag");
    // 正交投影
    glm::mat4 projection = glm::ortho(0.0f, static_cast(WIDTH), 0.0f, static_cast(HEIGHT));
    shader.useShaderPrograme();
    glUniformMatrix4fv(glGetUniformLocation(shader.getPrograme(), "projection"),
        1, GL_FALSE, glm::value_ptr(projection));

    // FreeType
    FT_Library ft;
    if (FT_Init_FreeType(&ft))
        std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;

    // 加载字体
    FT_Face tfFace;
    if (FT_New_Face(ft, "./abc.ttf", 0, &tfFace))
        std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;

    FT_Set_Pixel_Sizes(tfFace, 0, 30);  // 字体宽高   0宽 则自动处理

                                        // 1字节对齐 即禁用自动对齐方式
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    // 只生成表示128个ASCII字符的字符表。并为每一个字符储存纹理和一些度量值
    for (GLubyte c = 0; c < 128; c++)
    {
        //if (FT_Load_Char(tfFace, c, FT_LOAD_RENDER))

        //wchar_t chinese_char = '经';

        if (FT_Load_Char(tfFace, c, FT_LOAD_RENDER))
        {
            std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
            continue;
        }
        // 生成纹理
        GLuint nTextureID;
        glGenTextures(1, &nTextureID);
        glBindTexture(GL_TEXTURE_2D, nTextureID);
        {
            //void glTexImage2D (GLenum target, GLint level, GLint internalformat, 
            // GLsizei width, GLsizei height, 
            // GLint border, GLenum format, GLenum type, const void *pixels);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED,
                tfFace->glyph->bitmap.width, tfFace->glyph->bitmap.rows,
                0, GL_RED, GL_UNSIGNED_BYTE, tfFace->glyph->bitmap.buffer);

            // 纹理设置
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

            Character character = { nTextureID,
                glm::ivec2(tfFace->glyph->bitmap.width, tfFace->glyph->bitmap.rows),
                glm::ivec2(tfFace->glyph->bitmap_left, tfFace->glyph->bitmap_top),
                tfFace->glyph->advance.x };

            mapCharacters.insert(std::pair(c, character));
        }
        glBindTexture(GL_TEXTURE_2D, 0);
    }   // for

        // 释放
    FT_Done_Face(tfFace);
    FT_Done_FreeType(ft);

    // VAO/VBO
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    {
        glGenBuffers(1, &VBO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        {
            //  GL_DyNAMIC_DRAW:表示该缓存区会被周期性更改
            glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
            glEnableVertexAttribArray(0);
            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
        }
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }
    glBindVertexArray(0);

    // 循环  事件与渲染
    while (!glfwWindowShouldClose(pWnd))
    {
        glfwPollEvents();

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        RenderText(shader, "yulinxx copy", 25.0f, 25.0f, 2.0f, glm::vec3(0.5, 0.8f, 0.2f));
        RenderText(shader, "(C) LearnOpenGL.com", 40.0f, 270.0f, 1.0f, glm::vec3(0.3, 0.7f, 0.9f));

        glfwSwapBuffers(pWnd);
    }

    glfwTerminate();
    return 0;
}

// 渲染文字
void RenderText(Shader &shader, std::string strText, GLfloat x, GLfloat y, GLfloat nScale, glm::vec3 vecColor)
{
    shader.useShaderPrograme();
    glUniform3f(glGetUniformLocation(shader.getPrograme(), "textColor"), vecColor.x, vecColor.y, vecColor.z);
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(VAO);
    {
        // 遍历绘制所有文字
        std::string::const_iterator c;
        for (c = strText.begin(); c != strText.end(); c++)
        {
            Character ch = mapCharacters[*c];

            GLfloat xpos = x + ch.Bearing.x * nScale;
            GLfloat ypos = y - (ch.Size.y - ch.Bearing.y) * nScale;

            GLfloat w = ch.Size.x * nScale;
            GLfloat h = ch.Size.y * nScale;

            // 每个2D方块需要6个顶点,每个顶点由一个4维向量(一个纹理坐标和一个顶点坐标)组成
            // 当前字符的VBO
            GLfloat vertices[6][4] =
            {
                { xpos,     ypos + h,   0.0, 0.0 },{ xpos,     ypos,       0.0, 1.0 },
                { xpos + w, ypos,       1.0, 1.0 },{ xpos,     ypos + h,   0.0, 0.0 },
                { xpos + w, ypos,       1.0, 1.0 },{ xpos + w, ypos + h,   1.0, 0.0 }
            };
            // 使用上面生成的指定纹理
            glBindTexture(GL_TEXTURE_2D, ch.TextureID);
            {
                // 更新字符VBO
                glBindBuffer(GL_ARRAY_BUFFER, VBO);
                {
                    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
                }
                glBindBuffer(GL_ARRAY_BUFFER, 0);

                glDrawArrays(GL_TRIANGLES, 0, 6);
            }
            glBindTexture(GL_TEXTURE_2D, 0);
            // 更新位置到下一个字形的原点,注意单位是1/64像素
            x += (ch.Advance >> 6) * nScale;    // //(2^6 = 64)
        }
    }
    glBindVertexArray(0);

}

Shader.h

//Shader.h 
#pragma once

#ifndef TEXTURE_SHADER_H_
#define TEXTURE_SHADER_H_

#include 
#include 
#include 
#include 

#include 

#include 
#include 
#include 
#include 

#include 

class Shader
{
public:
    Shader(const GLchar* vertexPath, const GLchar* fragmentPath);
    ~Shader();

public:
    void useShaderPrograme();

    GLuint getPrograme() {
        return this->m_nProgram;
    }

private:
    GLuint  m_nProgram;
};

Shader::Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
{
    std::string vertexCode;
    std::string fragmentCode;
    std::ifstream vertexShaderF;
    std::ifstream fragementShaderF;

    vertexShaderF.exceptions(std::ifstream::badbit);
    fragementShaderF.exceptions(std::ifstream::badbit);

    try
    {
        vertexShaderF.open(vertexPath);     // 打开文件
        fragementShaderF.open(fragmentPath);

        std::stringstream vertexShaderStream, fragementShaderStream;
        vertexShaderStream << vertexShaderF.rdbuf();    // 读取文件至stringstream中
        fragementShaderStream << fragementShaderF.rdbuf();

        vertexShaderF.close();
        fragementShaderF.close();

        vertexCode = vertexShaderStream.str();      // 转换成string类型
        fragmentCode = fragementShaderStream.str();
    }
    catch (std::ifstream::failure e)
    {
        std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ:" << std::endl;
    }

    const GLchar* pVertexCode = vertexCode.c_str(); // string 转 char*
    const GLchar* pFragementCode = fragmentCode.c_str();

    GLuint nVertexShader, nFragementShader;
    GLint nRes = 0;
    GLchar chLogInfo[512] = { '\0' };

    // 创建顶点着色器
    nVertexShader = glCreateShader(GL_VERTEX_SHADER);
    // 将顶点着色程序的源代码字符数组绑定到顶点着色器对象
    glShaderSource(nVertexShader, 1, &pVertexCode, nullptr);
    glCompileShader(nVertexShader); // compile shader 编译着色器

    // 获取编译结果
    glGetShaderiv(nVertexShader, GL_COMPILE_STATUS, &nRes);
    if (!nRes)
    {
        glGetShaderInfoLog(nVertexShader, 512, nullptr, chLogInfo);
        std::cout << "ERROR::SHADEF::VERTEX::COMPILATION_FAILED:" << chLogInfo << std::endl;
    }

    // 创建片断着色器
    nFragementShader = glCreateShader(GL_FRAGMENT_SHADER);
    // 将片段着色程序的源代码字符数组绑定到片段着色器对象
    glShaderSource(nFragementShader, 1, &pFragementCode, nullptr);
    glCompileShader(nFragementShader);
    glGetShaderiv(nFragementShader, GL_COMPILE_STATUS, &nRes);
    if (!nRes)
    {
        glGetShaderInfoLog(nFragementShader, 512, nullptr, chLogInfo);
        std::cout << "ERROR::SHADEF::FRAGEMENT::COMPILATION_FAILED:" << chLogInfo << std::endl;
    }

    this->m_nProgram = glCreateProgram();   // 创建GLSL程序
    glAttachShader(this->m_nProgram, nVertexShader);    // 绑定shader到program
    glAttachShader(this->m_nProgram, nFragementShader);

    // glLinkProgram操作产生最后的可执行程序,它包含最后可以在硬件上执行的硬件指令
    glLinkProgram(this->m_nProgram);        // 链接
    glGetProgramiv(this->m_nProgram, GL_LINK_STATUS, &nRes);
    if (!nRes)
    {
        glGetProgramInfoLog(this->m_nProgram, 512, nullptr, chLogInfo);
        std::cout << "ERROR::SHADEF::FRAGEMENT::LINK_FAILED:" << chLogInfo << std::endl;
    }

    glDeleteShader(nVertexShader);
    glDeleteShader(nFragementShader);
}

Shader::~Shader()
{
}

#include 
#include 
#include 

void Shader::useShaderPrograme()
{
    glUseProgram(this->m_nProgram); // 使用porgram
}

#endif


  1. Shader部分 GLSL

text.vs

#version 330 core
layout (location = 0) in vec4 vertex; // 
out vec2 TexCoords;

uniform mat4 projection;

// 顶点着色器将会将位置坐标与投影矩阵相乘,
//      并将纹理坐标转发给片段着色器:
void main()
{
    gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
    TexCoords = vertex.zw;
}  

text.frag

#version 330 core
in vec2 TexCoords;
out vec4 color;

// 单颜色通道的字形位图纹理
uniform sampler2D text;

// 文字的颜色
uniform vec3 textColor;

// 纹理数据中仅存储着红色分量,就通过r分量来作为取样颜色的aplpha值。
//      结果值是一个字形背景为纯透明,而字符部分为不透明的白色 的颜色。
//      将此颜色与字体颜色uniform值相乘就得到了要输出的字符颜色了
void main()
{    
    vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
    color = vec4(textColor, 1.0) * sampled;
}  

运行效果:

OpenGL 绘制简单的英文字符_第3张图片

源码下载: VS2015
http://download.csdn.net/detail/yulinxx/9757228

你可能感兴趣的:(OpenGL)