2011-07-23

freeimage + DDS ( nvidia texture tools )

前段時間遇到一個需求是把 bmp, png 轉成 dds 格式,且在過程中自動把 bmp, png 的圖形放大到二的冪次方大小。 

後來使用兩套工具解決這個需求:
  • Freeimage 來做圖檔的讀取工具,
  • nvidia 提供的 texture tools 來做圖檔的格式轉換及壓縮工具,
freeimage library 提供方便快速的圖形讀取和寫入功能,但對(DirectX Surface)DDS 格式的支援只有到讀取的程度,致使無法儲存 DDS 格式檔案。texture tools 則是提供簡單的圖檔讀取及完整的 DDS 檔案格式支援,其 DDS 支援的部分剛好可以補足 freeimage 在支援 DDS 格式不足之處。

底下為把圖片轉成 DDS 格式的簡單實作:

1) 載入圖檔  (src.jpg):
std::string filename("src.jpg");
FREE_IMAGE_FORMAT fif =FreeImage_GetFileType( filename.c_str() );
FIBITMAP* dib =NULL;
dib = FreeImage_Load( fif, filename.c_str() );

2) 將圖檔轉成 RGBA 32bit 格式:
FIBITMAP* dib32 = FreeImage_ConvertTo32Bits(dib);

3) 取得圖形資料:
int nWidth =FreeImage_GetWidth(dib);
int nHeight =FreeImage_GetHeight(dib);
BYTE* pSrcDIB =FreeImage_GetBits(dib32);

4) 設定欲壓縮的圖形資料格式
nvtt::InputOptions inputOptions;
inputOptions.setTextureLayout(nvtt::TextureType_2D, nWidth, nHeight);
inputOptions.setMipmapData(pSrcDIB, nWidth, nHeight);
inputOptions.setFormat(nvtt::InputFormat_BGRA_8UB);

inputOptions.setWrapMode(nvtt::WrapMode_Clamp);
inputOptions.setAlphaMode(nvtt::AlphaMode_Transparency);

inputOptions.setNormalMap(false);
inputOptions.setConvertToNormalMap(false);
inputOptions.setGamma(2.2f, 2.2f);
inputOptions.setNormalizeMipmaps(false);

inputOptions.setMipmapGeneration(false);

5) 設定 DDS 檔格式,此處使用的是 DXT1a 格式
nvtt::CompressionOptions compressionOptions;
compressionOptions.setFormat(nvtt::Format_DXT1a);
//compressionOptions.setQuality(nvtt::Quality_Fastest);
//compressionOptions.setQuality(nvtt::Quality_Normal);
//compressionOptions.setQuality(nvtt::Quality_Production);
compressionOptions.setQuality(nvtt::Quality_Highest);

6) 壓縮並儲存成 DDS 檔 (dst.dds):
nvtt::OutputOptions outputOptions;
outputOptions.setFileName("dst.dds");

nvtt::Compressor compressor;
compressor.enableCudaAcceleration(false);
compressor.process(inputOptions, compressionOptions, outputOptions);

7) 最後不忘刪除記憶體:
FreeImage_Unload(dib32);
FreeImage_Unload(dib);

2011-05-15

TextEdit 上字型會自動更換的問題

中文版 OSX SnowLeopard 上 TextEdit 的預設字型是 Heiti TC,它在英文字的呈現上不易閱讀,可以在 TextEdit --> Preferences --> Font 的地方更改成比較容易閱讀的字型,但惡夢來了,文字輸入的過程中若輸入中文之後再輸入英文,字型會被自動改回 Heiti TC,但 Preferences --> Font 裡仍然是自己設定的字型,網路上也 有人 提出一樣的問題這邊 也提到...
Snow Leopard 增加了一項自作聰明的新設計-會偵測目前使用的輸入法切換字體,也就是,就算你切換到英文或其他語系,但是當你在「文字編輯」(TextEdit)等軟體中,只要是使用繁體中文語系輸入法打字,輸出的文字就會自動切換以 Heiti TC 顯示…。

個人認為...

其實是因為設定的字型無法顯示目前輸入法所輸入的字,所以為了顯示該字,才又跳回預設的字型。

發生在我這裡的情況是將字型設定成 Consolas 之後,輸入中文再輸入英文,英文字的字型就變成 Heiti TC,原因是 Consolas 不是 Unicode 字型 的 unicode set 裡不包含中文字,無法顯示中文,後來使用雅黑-Consolas混合字體就不再出現字型被切換回 Heiti TC 的問題了。

2011-05-01

Unity 3D 將程式部署在 iOS 4.3.2 (iphone4) 上執行

最近建立了在 ios 上開發 unity3d 程式的環境,建立過程大概敘述如下:

1) 作業系統 OSX 10.6.7 + Xcode 4.0,手機部分為 iOS4.3.2。
2) 參考這個連結,設定 Xcode (http://www.alexwhittemore.com/?p=398)。
    連結內前兩項的意思是:
        a) 對 iOS4.3.2 進行 JB 動作,完成之後安裝 AppSync for 4.0+ 軟體。
        b) 在 mac 上建立一個 Certificate:
            i) 在功能表執行 Keychain Access --> Certificate Assistant --> Create a Certificate 。
            ii) Name 的部分設定為 "iPhone Developer"。
                Identity Type 為 Self Signed Root。
                Certificate Type 為 Code Signing。
                將 Let me override defaults 打勾。
            iii) Serial Number : 1
                Validity Period (days): 3650
            iv) 一直按下一步到底。

3) 完成 2) 所提供連結之後續步驟(3~7)。
    步驟到這邊就已足夠用 Xcode 開發一般的 iOS Project 。

4) 在 unity3D 下建立 project 並 switch platform 至 ios。
5) Player Settings --> Other Settings --> Bundle Identifier 設定為 my.company.[project name](注意前段 my.company 部分與 3) 中的 script 一致。 

6) Target Platform 設定為 Universal armv6 + armv7
7) SDK Version 設定為 iOS latest
8) Target iOS Version 設定為 4.2
9) 按下 Build And Run 之後將自動生成 Xcode project 且被 Xcode 開啟。
10) 在 Xcode 下重覆 3) 步驟。
11) 在 Build Settings 頁面下的 Code Signing 項目裡的 Code Signing Identity 全設定成 Any iOS SDK : Don't Code Sign。

12) Summary 的 Deployment Target 改成 4.3
13) 按下 Run (在 Stop 右方的下拉式功能表選擇 Unity-iPhone | [device name] (4.3.2)

2011-04-26

NodeJS 在 mac 上的安裝, 刪除

node.js 可以在 http://nodejs.org/ 取得。
解壓縮之後,在 nodejs 目錄下輸入下列指令:
1) ./configure
2) make
3) sudo make install

刪除 nodejs 的指令:
1) make uninstall

update 20110429:
或是透過 mac port 也可以安裝 nodejs。
1) sudo port install nodejs 即可

透過 mac port 刪除 nodejs 的指令
1) sudo port uninstall nodejs

但在安裝前須先安裝 mac port (http://www.macports.org/install.php)。

mac port 一些指令:

1) port selfupdate 檢查更新
2) port installed 檢查已安裝項目
3) port search [item] 搜尋可安裝項目 (例 port search nodejs)
4) port install [item] 安裝項目
5) port uninstall [item] 反安裝項目

2011-03-08

Unity 3D 非對稱投影

透過修改投影矩陣可以達到非對稱的透視投影效果,下圖是正常的透視投影結果:

正常的透視矩陣所投影的結果

若是我們直接移動攝影機位置讓這三個圖柱向左偏移(攝影機向右偏),將得到結果如下圖:

圖柱向左移動但扭曲變形

雖然圓柱向左偏移,但因為透視投影矩陣的關係產生扭曲變形的結果。若要使圓柱偏移且不扭曲變形,以此例在 Unity3D 而言,修改投影矩陣的 m02 元素即可:

修改過的投影矩陣


2011-02-24

Unity 3D 和 CodePage 950

在 Unity 裡使用 System.Text.Encoding 功能時,如果 CodePage 設定成 950 (BIG-5) 的話,各位可能會發現,在編輯器下執行沒有問題(甚至預設的 CodePage 就是 950),但將程式編譯為執行檔之後執行會得到類似的訊息:

System.NotSupportedException: CodePage 950 not supported
  at System.Text.Encoding.GetEncoding (Int32 codepage) [0x00000] in <filename unknown>:0
  ...

而且預設的 CodePage 變成 UTF8 @@

參考這篇討論,原因是少了幾個動態連結檔,將 C:\Program Files\Unity\Editor\Data\Mono\lib\mono\unity 目錄下的 I18N.dll 和 I18N.CJK.dll 複製到編譯結果的目錄裡即可排除上述情況(與 System.Data.dll 同目錄)。

2011-01-22

Mac OS 睡眠之後無法 umount(eject) smb 目錄或連上 smb 的問題

根據這篇連結,MAC OS 進入睡眠再醒來之後會遇到類似的問題:

1. 原本 mount 的 smb 分享目錄無法存取也無法 eject。
2. 無法再連到 smb server,也找不到 smb server 分享的目錄。

這裡把這篇連結提出的解決方法做個筆記如下:

sudo sysctl -w net.inet.tcp.delayed_ack=0
defaults write com.apple.desktopservices DSDontWriteNetworkStores true

2011-01-05

Windows Message in the Unity3D : WndProc

先前提到可以用 Hooks 的方法在 Unity 裡監控 Windows Message,但是使用 Hooks 這個方法我們沒辦法更改 Message 的內容。因此這邊提出第二個方式。Windows 傳送 Message 給 Unity 時,會呼叫 Unity 預設的 Message 處理函數,但透過函數:

pOldWndProc =(WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (LONG)SubWndProc);

我們可以將原本 Windows 呼叫 Unity 預設的 Message 處理函數改為呼叫我們指定的函數,指定的函數把我們想要處理的 Message 處理完,再把剩下的 Message 丟回給 Unity 來處理。

原本實作是使用 dllimport 讓 SetWindowLong function 可以在 C# 裡呼叫, 並把整個 callback function 及流程實作出來,且測試時運作都相當正常,但程式在關閉時會出現 Access Violation 的錯誤,後來將整個實作改成 C DLL 之後錯誤才沒有出現,不知道是什麼原因?底下是 DLL 部分的原始碼:
#include "stdafx.h"
LRESULT CALLBACK SubWndProc(
 HWND hWnd, 
 UINT nMessage, 
 WPARAM wParam, LPARAM lParam); 
 
 WNDPROC  gOldWndProc = NULL; 
 HWND  gUnityWnd = NULL; 
 
 #ifdef    __cplusplus
 extern "C" {
 #endif    /*    __cplusplus    */
 
 __declspec(dllexport) bool __stdcall init(HWND hWnd)
 {
  gOldWndProc =(WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (LONG)SubWndProc);
  gUnityWnd =hWnd;
 
  if (gOldWndProc !=NULL)
   return true;
 
  return false;
 }
 
 __declspec(dllexport) void __stdcall release()
 {
  SetWindowLong(gUnityWnd, GWL_WNDPROC, (LONG)gOldWndProc);
  gOldWndProc =0;
  gUnityWnd =0;
 }
 
#ifdef    __cplusplus
}
#endif    /*    __cplusplus    */
 
 
LRESULT CALLBACK SubWndProc(
 HWND hWnd, 
 UINT nMessage, 
 WPARAM wParam, LPARAM lParam)
 {
  switch(nMessage)
  {
   case WM_IME_SETCONTEXT:
   case WM_IME_STARTCOMPOSITION:
   case WM_IME_ENDCOMPOSITION:
   case WM_IME_COMPOSITION:
   case WM_IME_REQUEST:
   {
    //...
   }
   break;
 }
 return CallWindowProc(gOldWndProc, hWnd, nMessage, wParam, lParam);
}

Unity 可以透過呼叫 DLL 提供的 init() 函數,讓 Windows 改為呼叫我們指定的函數 (SubWndProc) 來處理 Message,透過 release() 函數讓 Message 處理流程復原。底下是 Unity 部分的原始碼(DLL 檔名為 UnityIMEDLL.dll 且檔案放在 Assets/Plugins 目錄下)
using UnityEngine;
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
 
public class IMEInputBox : MonoBehaviour
{
    //-----------------------------------------------------------
    [DllImport("UnityIMEDLL")]
    protected static extern bool init(IntPtr hWnd);
 
    [DllImport("UnityIMEDLL")]
    protected static extern void release();
 
    [DllImport("user32")]
    protected static extern IntPtr GetActiveWindow();
 
    //-----------------------------------------------------------
    // Use this for initialization
    void Start ()
    {
        Debug.Log("init UnityIMEDLL.");
        try
        {
            init(GetActiveWindow());
        }
        catch (Exception e)
        {
            Debug.Log(e.ToString());
        }
    }
 
    void OnDisable()
    {
        Debug.Log("release UnityIMEDLL.");
        try
        {
            release();
        }
        catch (Exception e)
        {
            Debug.Log(e.ToString());
        }
    }
}

2010-12-31

Windows Message in the Unity3D : Hooks

參考這篇連結,讓 Unity 也可以做 Hook Windows Message 的動作,底下是修改並精簡至 Unity 的範例,範例中的 m_hookType 設定成 WH_KEYBOARD ,所以是 hook 鍵盤的訊息,若要 hook 其它的訊息可以透過修改 m_hookType 來達成,我們可以在 CoreHookProc 處理傳來的鍵盤訊息,使 Unity 做出對應的動作(但要注意的是,呼叫CoreHookProc的 Thread 不是 Unity 的主要 Thread,不能執行非 thread-safe Unity API!)。

大概敘述一下底下範例的使用方法:

1) 建立一個新的 C Sharp Script,取名為 KBHooks,並將範例的程式碼覆蓋進去。
2) 將此 KBHooks.cs 從 Project 拖曳至 Hierarchy 內的 Main Camera 上。
3) 執行。

執行時在鍵盤上敲幾個鍵,離開執行之後會在 Console 視窗看到類似的訊息:


KBHooks.cs:
using UnityEngine;
 
using System;
using System.Collections;
using System.Runtime.InteropServices;
 
public class KBHooks : MonoBehaviour
{
    [DllImport("user32")]
    protected static extern IntPtr SetWindowsHookEx(
        HookType code, HookProc func, IntPtr hInstance, int threadID);
 
    [DllImport("user32")]
    protected static extern int UnhookWindowsHookEx(
        IntPtr hhook);
 
    [DllImport("user32")]
    protected static extern int CallNextHookEx(
        IntPtr hhook, int code, IntPtr wParam, IntPtr lParam);
 
    [DllImport("Kernel32")]
    protected static extern uint GetLastError();
 
    // Hook Types
    protected enum HookType : int
    {
        WH_JOURNALRECORD = 0,
        WH_JOURNALPLAYBACK = 1,
        WH_KEYBOARD = 2,
        WH_GETMESSAGE = 3,
        WH_CALLWNDPROC = 4,
        WH_CBT = 5,
        WH_SYSMSGFILTER = 6,
        WH_MOUSE = 7,
        WH_HARDWARE = 8,
        WH_DEBUG = 9,
        WH_SHELL = 10,
        WH_FOREGROUNDIDLE = 11,
        WH_CALLWNDPROCRET = 12,
        WH_KEYBOARD_LL = 13,
        WH_MOUSE_LL = 14
    }
 
    protected IntPtr m_hhook = IntPtr.Zero;
    protected HookType m_hookType = HookType.WH_KEYBOARD;
 
    protected delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);
 
    protected bool Install(HookProc cbFunc)
    {
        if (m_hhook == IntPtr.Zero)
            m_hhook = SetWindowsHookEx(
                m_hookType, 
                cbFunc, 
                IntPtr.Zero, 
                (int)AppDomain.GetCurrentThreadId());
 
        if (m_hhook == IntPtr.Zero)
            return false;
 
        return true;
    }
 
    protected void Uninstall()
    {
        if (m_hhook != IntPtr.Zero)
        {
            UnhookWindowsHookEx(m_hhook);
            m_hhook = IntPtr.Zero;
        }
    }
 
    protected int CoreHookProc(int code, IntPtr wParam, IntPtr lParam)
    {
        if (code < 0)
            return CallNextHookEx(m_hhook, code, wParam, lParam);
 
        Debug.Log(
            "hook code =" + code.ToString() + 
            " lparam=" + lParam.ToString() + 
            " wparam=" + wParam.ToString());
 
        // Yield to the next hook in the chain
        return CallNextHookEx(m_hhook, code, wParam, lParam);
    }
 
    // Use this for initialization
    void Start()
    {
        Debug.Log("install hook");
        Install(CoreHookProc);
    }
 
    void OnDisable()
    {
        Debug.Log("Uninstall hook");
        Uninstall();
    }
 
}

2010-12-24

從檔案載入 Lua function 至名稱空間

參考連結,可將 lua 檔案裡的程式載入到 namespace 裡,好處是可避免各個不同檔案裡出現相同 function 的問題。

稍微針對 C# 做了修改如下:

protected bool load_file_into_namespace(IntPtr L, string S, string N, bool bCall)
{
    lua_pushlstring(L, N, N.Length);
    lua_gettable(L, (-10002));
 
    if (lua_isnil(L, -1))
    {
        lua_pop(L, 1);
        lua_newtable(L);
        lua_pushlstring(L, N, N.Length);
        lua_pushvalue(L, -2);
        lua_settable(L, (-10002));
    }
    else
    {
        if (!lua_istable(L, -1))
        {
            lua_pop(L, 1);
            return (false);
        }
    }
 
    lua_newtable(L);
    lua_pushlstring(L, "_G", 2);
    lua_gettable(L, (-10002));
    lua_pushnil(L);
    while (lua_next(L, -2) != 0)
    {
        lua_pushvalue(L, -2);
        lua_pushvalue(L, -2);
        lua_settable(L, -6);
        lua_pop(L, 1);
    }
 
    if ((luaL_loadfile(L, S) > 0))
    {
        Console.Write("Error in file " + S + "!\n");
        for (int i = 0; ; i++)
            if ((lua_isstring(L, i) > 0))
            {
                Console.Write(lua_tolstring(L, i, IntPtr.Zero) + "\n");
            }
            else
                break;
        lua_error(L);
        lua_pop(L, 3);
        return (false);
    }
 
    if (bCall)
        lua_call(L, 0, 0);
    else
        lua_insert(L, -4);
 
    lua_pushnil(L);
    while (lua_next(L, -2) != 0)
    {
        lua_pushvalue(L, -2);
        lua_gettable(L, -5);
        if (lua_isnil(L, -1))
        {
            lua_pop(L, 1);
            lua_pushvalue(L, -2);
            lua_pushvalue(L, -2);
            lua_pushvalue(L, -2);
            lua_pushnil(L);
            lua_settable(L, -7);
            lua_settable(L, -7);
        }
        else
        {
            lua_pop(L, 1);
            lua_pushvalue(L, -2);
            lua_gettable(L, -4);
            if (!(lua_equal(L, -1, -2) > 0))
            {
                lua_pushvalue(L, -3);
                lua_pushvalue(L, -2);
                lua_pushvalue(L, -2);
                lua_pushvalue(L, -5);
                lua_settable(L, -8);
                lua_settable(L, -8);
            }
            lua_pop(L, 1);
        }
        lua_pushvalue(L, -2);
        lua_pushnil(L);
        lua_settable(L, -6);
        lua_pop(L, 1);
    }
 
    lua_pop(L, 3);
    return (true);
}

其中 S 表示檔案路徑, N 表示名稱空間。呼叫名稱空間內的 function 方式也會有些不同:

1) 以 luaL_dostring 方式呼叫:

假設載入的file0.lua 檔如下:
function pp()
  print("this is file0");
end

載入的 file1.lua 檔案下:
function pp()
  print("this is file1");
end

載入 file0.lua 時設定的 namespace 為 A0,載入 file1.lua 時設定的 namespace 為 A1,載入的程式碼為:
load_file_into_namespace(m_luaState, "file0.lua", "A0", true);
load_file_into_namespace(m_luaState, "file1.lua", "A1", true);

則分別呼叫 file0.lua及file1.lua內的 pp() 函數方式如下:
A0.pp();
A1.pp();

程式碼寫法是:
luaL_dostring(m_luaState, "A0.pp()");
luaL_dostring(m_luaState, "A1.pp()");

2) 以 lua_pcall 方式呼叫(以呼叫 A0.pp() 為例子)
lua_getglobal(m_luaState, "A0");
if (lua_type(m_luaState, -1) == 5)
{
    lua_getfield(m_luaState, -1, "pp");
 
    if (lua_type(m_luaState, -1) == 6)
    {
        // This removes the foo table from the stack
        lua_replace(m_luaState, -2); 
        ret = lua_pcall(m_luaState, 0, 0, 0);
 
        if (ret != 0)
        {
            lua_pop(m_luaState, 1);
        }
    }
    else
    {
        lua_pop(m_luaState, 2);
    }
 
}
else
{
    lua_pop(m_luaState, 1);
}