Home > 圖像編程 > Freetype2 文字渲染

Freetype2 文字渲染

Freetype2 是一個開源的文字渲染程序庫,支持多種向量和點陣字型格式。以前的項目用過這程序庫,不過不是我親手整合。以前的項目有很多不同的重載函數去提供各樣文字渲染功能。今個星期花了三天整合,花了不少時間想,怎麼可以最有彈性、又最簡單呢?

過去,現在

要把文字輸出到 Direct3D 或 OpenGL 等圖形設備,並不是很簡單的事情 (除非用 Direct3D 的 ID3DXFont,不過其他平台就很難模擬同樣的效果)。

我第一次接觸這個問題的時候,是採用 DOS 年代的方法,就是把字型以點陣圖格式存放 (1、2 或 4 bit per pixel)。在 渲染每個字的時候,把字型逐個載入 Texture。壞處是,每個字型級數需要新成不同的檔案,而且字型級越大,檔案就平方級數上升。

之後,上一次決定使用 freetype2 ,可把向量字型光柵化 (Rasterize) 至 Texture,之後的渲染過程都是差不多。但是由於為渲染字型加入很多功能 (如自動跳行、陰影效果、漸變效果等等),和渲染文字的 API 有十數個重載函數 (不同功能的組合)。使用時也比較難。

今次,雖然也是以 freetype2 光柵化為基礎,但有了一點新的想法。和大家分享一下。

設計過程

渲染文字有大概三個方法,包括之前談到的點陣字型、向量字型光柵化和把向量字型 Tessellate 成為三角形。暫時 Mil 會利用 freetype2 實現首二種,第三種到有需要才做吧。

文字光柵化的渲染過程:

  1. 開啟向量字型,設定級數;
  2. 把字串中逐個字符光柵化至 texture 的一個地方 (如該字符已在 texture 上,就直接使用該 UV);
  3. 把字符的長方形 (位置、UV) 加入 Vertex Buffer;
  4. 當沒有更多字符,或 Texture 已滿,就用 Draw call 把 Vertex Buffer 及 Texture 渲染。

我考慮了一段時間,發覺第三步是可以用 GeometryBuilder 來實現,好處是簡單而且跨平台。而且,GeometryBuilder 會生成 Geometry 物件

,可以直接用現存的機制渲染,那麼就可以結合 Material 的功能,在 Direct3D 版本使用 HLSL 來設定輸出效果。嗯……

那麼,根本不用考慮二維的文字渲染,直接把文字輸出至三維物件坐標 (Object coordinate) 就行了!

這剎那間,一切便得簡單。不用寫不同的重載來做文字效果等功能,而且還可以用不同的 view/projection matrices 實現二維、三維渲染。

最後,文字渲染只加了兩個 API,字型物件建構函數,和一個渲染字串到 Object Space 的函數。

本文開頭的 Lua 示範,就是這麼簡單:

// Initialize
local device = Graphics.Device_Instance()
self.font1 = Graphics.Font("Font/arial.ttf", 32)
self.font2 = Graphics.Font("Font/msgothic.ttc", 32)
self.font3 = Graphics.Font("Font/consola.ttf", 16)
self.material = Graphics.Material("Effect/font")

// Render frame
device:SetMaterial(self.material)	

device:SetProjection(Math.Matrix44_Perspective(-0.05, 0.05, -0.05, 0.05, 0.1, 1000))
device:SetView(Math.Matrix44_LookAt(Math.Vector3(2.0, 2.0, 2.0), Math.Vector3(1.25, 0.5, 0), Math.Vector3(0, 1, 0)))
device:SetWorld(Math.Matrix44_Scale(Math.Vector3(0.01, 0.01, 0.01)))

self.font1:DrawText(Math.Vector2(0, 80), "Hello, World!")
self.font2:DrawText(Math.Vector2(0, 40), "哈囉,世界!")
self.font2:DrawText(Math.Vector2(0,  0), "ハローワールド!")

local resolution = device:GetResolution()
device:SetProjection(Math.Matrix44_Orthographic(-0.5, resolution.x - 0.5, -0.5, resolution.y - 0.5, 0, 1000))
device:SetView(Math.Matrix44_Identity)
device:SetWorld(Math.Matrix44_Identity)
self.font3:DrawText(Math.Vector2(10, 10), string.format("FPS %3.2f (%2.2fms)", 1 / timeStep, timeStep * 1000))

註: -0.5 的 projection 設定是因為 Direct3D 的 Texture Sampling 計算方法。這樣可以做到點對點的二維渲染。

Unicode

Unicode 是最簡單的編碼方式去支持不同的文字。為了支持 Unicode,DrawText 的參數使用 wchar_t*,而編碼採用 UTF-16。

Lua 並不直接支持 Unicode。我採用了 UTF-8 的文字格式。我修改了 SWIG 的 wchar.i:

%{
wchar_t* str2wstr(const char* str)
{
    int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0) * sizeof(wchar_t);
    if (size == 0)
        return 0;
        
    wchar_t* p = (wchar_t*)malloc(size);
    if (p == 0)
        return 0;

    if (MultiByteToWideChar(CP_UTF8, 0, str, -1, p, size) == 0) {
        free(p);	
        return 0;
    }
    
    return p;
}
%}

%typemap(in, checkfn="lua_isstring") wchar_t*
%{
$1 = str2wstr(lua_tostring(L, $input));
if ($1==0) {lua_pushfstring(L,"Error in converting to wchar (arg %d)",$input);goto fail;}
%}

%typemap(freearg) wchar_t*
%{
free($1);
%}

%typemap(typecheck) wchar_t * = char *;

只要把這個 header include 在 Swig 文件裡就能自動新成 Lua 的 Binding 代碼。

後記

我告訴太太,這幾晚花了很多時間,畫了幾個字出來。她說:

很難的嗎?

我也不知道。為甚麼我會為做這個而花了三整個晚上?為甚麼我會為這個花腦筋?更重要的是,只是畫幾個字而已,為甚麼我會感到當中的樂趣?最後,還在二時半開始寫這篇文章……

Comments:0

Comment Form
Remember personal info

Trackbacks:0

Trackback URL for this entry
http://miloyip.seezone.net/wp-trackback.php?p=80
Listed below are links to weblogs that reference
Freetype2 文字渲染 from Milo的遊戲開發

Home > 圖像編程 > Freetype2 文字渲染

Search
Feeds
Meta

Return to page top