李一天
DirectX 10圖形應(yīng)用程序截獲技術(shù)的研究
李一天
DirectX是主流圖形API,在多媒體、娛樂等領(lǐng)域得到廣泛應(yīng)用。為實(shí)現(xiàn)圖形表面的跨進(jìn)程共享,從DirectX 10開始使用DirectX 圖形基礎(chǔ)架構(gòu)(DXGI)對圖形硬件進(jìn)行底層管理。DXGI帶來性能提升的同時(shí)也給圖形應(yīng)用程序的截獲工作帶來困難。為完成DirectX 10程序的攔截,需要同時(shí)截獲多個(gè)圖形庫的函數(shù)。針對此問題,完善DLL替換+Detours的圖形應(yīng)用程序截獲框架,克服現(xiàn)有截獲技術(shù)的缺陷,完成DirectX 10程序的截獲工作,并解決因DXGI、D3D10圖形庫間依賴關(guān)系產(chǎn)生的進(jìn)程死鎖問題。最后,基于該截獲框架,開發(fā)出一套單機(jī)驅(qū)動的多投影顯示系統(tǒng),DirectX 10程序不需做任何修改就能多投影顯示。實(shí)驗(yàn)結(jié)果表明,使用所提出的截獲技術(shù)后圖形應(yīng)用程序畫面流暢,運(yùn)行穩(wěn)定。
函數(shù)截獲;Detours;多投影顯示
圖形應(yīng)用程序主要使用兩種圖形庫:Direct3D圖形庫和OpenGL圖形庫。Direct3D由微軟開發(fā),在微軟平臺(Windows和Xbox)上提供圖形應(yīng)用程序開發(fā)接口。OpenGL定義了一套跨平臺、跨語言的圖形應(yīng)用程序編程接口。由于多數(shù)圖形應(yīng)用程序可以在Windows平臺上使用Direct3D運(yùn)行,本文的討論限定在Windows平臺上,并以Direct3D為主。
目前大多數(shù)操作系統(tǒng)以用戶態(tài)動態(tài)鏈接庫(Dynamic Linking Library,或DLL)的形式向應(yīng)用程序提供大多數(shù)系統(tǒng)服務(wù)。對于圖形服務(wù)而言,操作系統(tǒng)通過用戶態(tài)的動態(tài)鏈接圖形庫(opengl32.dll或d3d9.dll)向應(yīng)用程序提供圖形繪制及顯卡資源管理功能。
Y. Mao提出DLL替換+Detours的截獲技術(shù)[1],在不修改應(yīng)用程序源代碼和二進(jìn)制鏡像的前提下,可以截獲D3D9圖形庫所有函數(shù)。從DirectX 10開始,DirectX使用DirectX Graphics Infrastructure(DXGI)對圖形硬件進(jìn)行底層管理[2]。為完成對DirectX 10圖形應(yīng)用程序的攔截,需要同時(shí)截獲多個(gè)圖形庫的函數(shù),加大了截獲難度。例如,若想截獲圖形應(yīng)用程序的幀緩存更新(Present)以及繪制函數(shù)(Draw),我們必須同時(shí)完成對dxgi.dll和d3d10.dll動態(tài)鏈接庫的截獲工作。
綜合以上情況,本文深入研究圖形應(yīng)用程序的截獲技術(shù)以及DirectX 10多媒體繪制引擎的工作機(jī)理,設(shè)計(jì)DirectX 10圖形應(yīng)用程序的截獲框架,在不修改應(yīng)用程序源代碼和二進(jìn)制鏡像的前提下,完成DirectX 10圖形應(yīng)用程序的函數(shù)截獲。
為了測試截獲技術(shù)的性能以及適用性,基于該截獲框架,我們利用已有的多投影顯示的幾何校正、邊緣融合算法[3],開發(fā)出一套單機(jī)驅(qū)動的多投影顯示系統(tǒng)。該系統(tǒng)對DirectX 10程序是透明的,程序源碼不需做任何修改就能多投影顯示。實(shí)驗(yàn)結(jié)果表明,使用本文的截獲技術(shù)后圖形應(yīng)用程序的畫面流暢,運(yùn)行穩(wěn)定。
Windows API攔截(Hook)的機(jī)制有2種,分為內(nèi)核級的攔截和用戶級的攔截[4]。內(nèi)核級的攔截主要是通過一個(gè)內(nèi)核模式的驅(qū)動程序來實(shí)現(xiàn),它的功能強(qiáng)大,能捕捉到系統(tǒng)活動的任何細(xì)節(jié),但實(shí)現(xiàn)難度也較大。而用戶級的攔截則通常是在普通的DLL中實(shí)現(xiàn)整個(gè)API的攔截工作,本文討論的即為用戶級攔截。在Windows平臺下攔截API函數(shù)一般有以下3種方法:
(1)修改函數(shù)IAT(Import Address Table)表和ET(Export Table)表
在Window 32操作系統(tǒng)中,可執(zhí)行文件是基于Microsoft設(shè)計(jì)的PE(Protable Executable)格式[2],如圖1所示:

圖1 可執(zhí)行模塊的PE結(jié)構(gòu)
每個(gè)PE文件的文件頭都包含了該模塊的輸入函數(shù)入口地址表和輸出函數(shù)入口地址表[5],即IAT表和ET表。只要把輸入表中目標(biāo)API函數(shù)的地址改為替代函數(shù)的地址, 那么當(dāng)可執(zhí)行模塊調(diào)用該API函數(shù)時(shí)就會變成對替代函數(shù)的調(diào)用,從而實(shí)現(xiàn)API函數(shù)的攔截。這種方法比較容易實(shí)現(xiàn),應(yīng)用也很廣泛。該方法缺點(diǎn)在于無法截獲來自模塊內(nèi)部的函數(shù)調(diào)用。
(2)修改API函數(shù)的入口指針
程序的執(zhí)行都是在內(nèi)存中完成的,系統(tǒng)動態(tài)鏈接庫(DLL)在程序運(yùn)行時(shí)被動態(tài)地載入內(nèi)存中。而每個(gè)API函數(shù)在DLL中都有自己固定的地址,只需要使用Win32 API函數(shù)GetProcAddress就可以得到需要攔截的API函數(shù)的入口地址。這種方法需要注意的是,在替換目標(biāo)API函數(shù)之前,需要先保留API函數(shù)入口地址處的前幾個(gè)指令字節(jié)。然后用JMP指令或INT指令,直接或間接地跳轉(zhuǎn)到替代函數(shù)的代碼入口地址。在替代函數(shù)生命域結(jié)束前恢復(fù)目標(biāo)API的前幾個(gè)指令字節(jié),保證其原來的程序正常運(yùn)行。該方法缺點(diǎn)在于無法截獲重定向前對目標(biāo)函數(shù)的調(diào)用。
(3)動態(tài)鏈接庫替換(DLL替換)
在Windows平臺下,操作系統(tǒng)通過用戶態(tài)的動態(tài)鏈接圖形庫向應(yīng)用程序提供圖形繪制及顯卡資源管理功能,而應(yīng)用程序是通過DLL的文件位置和文件名來加載DLL。DLL替換使用一個(gè)與原始DLL相同導(dǎo)出函數(shù)的DLL(自定義DLL),截獲應(yīng)用程序?qū)υ糄LL的導(dǎo)出函數(shù)的調(diào)用。該方法不需要修改應(yīng)用程序的源代碼,且由于函數(shù)截獲在應(yīng)用程序運(yùn)行前生效,應(yīng)用程序在運(yùn)行過程中對原始DLL導(dǎo)出函數(shù)的所有調(diào)用均可被截獲。
該方法的缺點(diǎn)是對于提供面向?qū)ο蟮慕涌冢珼LL替換只能以對象為單位進(jìn)行截獲,即為了截獲某個(gè)對象的成員方法,必須截獲該對象的創(chuàng)建方法,及該對象的所有公共成員,因此開發(fā)截獲庫時(shí)工作量大。而且與系統(tǒng)兼容性差,圖形庫升級后,自定義DLL通常需要重新編寫。
從DirectX10開始用DXG1對圖形硬件進(jìn)行底層管理[6]。為完成圖形應(yīng)用程序的攔截,需要同時(shí)截獲多個(gè)圖形庫的函數(shù),加大了截獲難度。針對此問題,本文完善DLL+Detours截獲框架,完成對DXGI圖形庫、D3D10圖形庫中目標(biāo)函數(shù)的截獲工作,并解決因DXGI、D3D10圖形庫間依賴關(guān)系產(chǎn)生的進(jìn)程死鎖問題。
2.1 DLL替換+Detours的截獲框架
Y. Mao提出DLL替換+Detours 的截獲技術(shù)[7],在不修改應(yīng)用程序源代碼和二進(jìn)制鏡像的前提下,可以截獲 D3D9圖形庫所有函數(shù)。Detours是微軟開發(fā)的一個(gè)函數(shù)庫,以函數(shù)為單位進(jìn)行截獲,可以克服DLL替換的諸多缺點(diǎn)。Detours以函數(shù)為單位進(jìn)行截獲,截獲時(shí)只需要知道目標(biāo)函數(shù)在內(nèi)存中的地址。根據(jù)該地址,Detours在運(yùn)行時(shí)替換目標(biāo)函數(shù)的前5個(gè)字節(jié),將目標(biāo)函數(shù)重定向到用戶自定義的函數(shù)[8]。替換后DLL內(nèi)部和外部對目標(biāo)函數(shù)的所有調(diào)用將被截獲。
對DirectX 9應(yīng)用程序繪制函數(shù)的截獲過程,如圖2所示:

圖2 Direct3D 9 繪制函數(shù)截獲過程
自定義動態(tài)鏈接庫d3d9.dll具有與原始d3d9.dll相同的導(dǎo)出函數(shù)及文件名。我們將自定義d3d9.dll拷貝到與原始d3d9.dll同一文件夾下,同時(shí)將原始d3d9.dll重命名為orgd3d9.dll。自定義d3d9.dll載入內(nèi)存后程序自動調(diào)用其DLL初始化函數(shù)。此時(shí)我們加載原始的圖形庫(orgd3d9.dll),并使用Detours替換orgd3d9.dll的繪制函數(shù)的前5個(gè)字節(jié),將其重定向到d3d9.dll中的繪制函數(shù),完成對繪制函數(shù)的截獲工作。
2.2 截獲框架的拓展
為了實(shí)現(xiàn)圖形表面的跨進(jìn)程共享,從DirectX 10開始使用DirectX 圖形基礎(chǔ)架構(gòu),簡稱DXGI。DXGI提供了對圖形硬件進(jìn)行底層管理的功能如圖3所示:

圖3 DirectX 圖形基礎(chǔ)架構(gòu)
使得不同D3D的API之間也能共享資源。DXGI的主要功能包括,枚舉顯示設(shè)備、創(chuàng)建管理交換鏈以及全屏模式的切換等[9]。
DXGI帶來性能提升的同時(shí),也給圖形應(yīng)用程序的截獲工作帶來困難。像交換鏈的創(chuàng)建、全屏模式切換等函數(shù)都是在DXGI圖形庫中定義的。為完成DirectX 10圖形應(yīng)用程序的函數(shù)截獲,多數(shù)情況下我們需要同時(shí)截獲多個(gè)圖形庫。例如,若想截獲圖形應(yīng)用程序的幀緩存更新以及繪制函數(shù),我們必須同時(shí)完成對dxgi.dll和d3d10.dll動態(tài)鏈接庫的截獲工作。
針對此問題,本文對DLL替換+Detours的截獲框架進(jìn)行拓展,在不修改應(yīng)用程序源代碼和二進(jìn)制鏡像的前提下,截獲dxgi.dll以及d3d10.dll中所有的函數(shù)。
對DirectX 10圖形應(yīng)用程序的截獲過程,如圖4所示:

圖4 Direct3D 10函數(shù)截獲框架
這里我們自定義DXGI動態(tài)鏈接庫,在自定義dxgi.dll的初始化函數(shù)中加載D3D10圖形庫(d3d10.dll),并使用Detours完成對dxgi.dll以及d3d10.dll目標(biāo)函數(shù)的截獲工作。
這里需要特別注意DLL間的加載順序。在多線程環(huán)境下,DllMain里的代碼容易引發(fā)線程死鎖[4]。系統(tǒng)在裝載DLL的時(shí)候會自動產(chǎn)生一個(gè)Loader Lock,避免多個(gè)DLL同時(shí)被裝載。LoaderLock從LoadLibrary函數(shù)調(diào)用的開始就自動加鎖,直到DllMain 退出為止。此時(shí)若DLL存在依賴關(guān)系,極易發(fā)生進(jìn)程死鎖。
例如,我們自定義D3D10圖形庫(d3d10.dll),在自定義的d3d10.dll中加載原始d3d10.dll以及dxgi.dll,進(jìn)而截獲目標(biāo)函數(shù)。系統(tǒng)在裝載自定義d3d10.dll時(shí),自動加鎖產(chǎn)生一個(gè)Loader Lock L。之后在加載dxgi.dll時(shí),工作線程自動產(chǎn)生一個(gè)新的Loader Lock G。因?yàn)閐xgi.dll對d3d10.dll存在依賴關(guān)系,工作線程會一直嘗試獲取d3d10.dll的鎖L,進(jìn)而導(dǎo)致進(jìn)程的死鎖如圖5所示:

圖5 進(jìn)程死鎖
為避免死鎖,在同時(shí)截獲多個(gè)動態(tài)鏈接庫時(shí),要注意DLL間的依賴關(guān)系。把被依賴項(xiàng)作為自定義DLL,在其初始化函數(shù)DllMain里加載其他的DLL。DLL間的依賴關(guān)系可以通過依賴查看工具Depends進(jìn)行查看,d3d10.dll的依賴關(guān)系,如圖6所示:

圖6 d3d10.dll的依賴關(guān)系
我們編寫自定義dxgi.dll,在自定義dxgi.dll的DLL_PROCESS_ATTACH中加載原始dxgi.dll以及d3d10.dll,并使用Detours完成目標(biāo)函數(shù)的截獲工作。
3.1 自定義dxgi.dll的實(shí)現(xiàn)
使用DLL替換技術(shù)時(shí),自定義的DLL必須與原始的DLL具有相同的導(dǎo)出函數(shù)表。在Windows平臺可以通過depends工具查詢DLL的導(dǎo)出函數(shù)名稱以及依賴的函數(shù)庫。depends.exe可以列出DXGI圖形庫(dxgi.dll)的導(dǎo)出函數(shù)表如圖7所示:

圖7 DXGI圖形庫導(dǎo)出函數(shù)表
由于Direct3D的運(yùn)行時(shí)使用Component Object Model(COM)兼容的對象,它可以向用戶提供基于COM的、面向?qū)ο蟮木幊探涌?,因此DLL導(dǎo)出函數(shù)表僅包含44個(gè)函數(shù)(見圖7)。其中常用的導(dǎo)出函數(shù)有CreateDXGIFactory等。
為了導(dǎo)出相同的函數(shù)列表,自定義DLL中必須實(shí)現(xiàn)并導(dǎo)出原始DLL函數(shù)導(dǎo)出表中所有的函數(shù)。在Microsoft Visual Studio開發(fā)環(huán)境中,有兩種聲明方式。第一種是把導(dǎo)出的函數(shù)聲明為全局函數(shù),且在一個(gè).def文件中聲明函數(shù)名。另一種方式是在函數(shù)聲明中加上__declspec(dllexport)。這里我們采用第一種聲明方式。
根據(jù)不同的情況,截獲庫中的導(dǎo)出函數(shù)可以以不同的方式實(shí)現(xiàn)。常規(guī)方法是使用與原始導(dǎo)出函數(shù)具有相同簽名(包括函數(shù)名、返回值和參數(shù)列表)的函數(shù),并在該函數(shù)中直接調(diào)用原始函數(shù)。該方法要求提供函數(shù)的原型。
以dxgi.dll中的CreateDXGIFactory函數(shù)為例,在自定義DLL中該函數(shù)實(shí)現(xiàn)如圖8所示:

圖8 知道原型的函數(shù)實(shí)現(xiàn)
自定義的CreateDXGIFactory將調(diào)用參數(shù)拷貝到新的堆棧頁中,并調(diào)用原始DLL中的原始函數(shù)(下文稱為目標(biāo)函數(shù)),即OrgCreateDXGIFactory。該函數(shù)地址在應(yīng)用程序加載自定義DLL后,通過DLL初始化函數(shù)進(jìn)行初始化。
然而對于不常用的導(dǎo)出函數(shù),標(biāo)準(zhǔn)文檔中可能沒有明確地說明其原型。如dxgi.dll中的D3DKMTUnlock等導(dǎo)出函數(shù)在標(biāo)準(zhǔn)DirectX文檔中沒有說明。對于這些函數(shù),在實(shí)現(xiàn)中我們通過匯編指令直接跳轉(zhuǎn)到原始DLL中的目標(biāo)函數(shù),其實(shí)現(xiàn)如圖9所示:

圖9 沒有原型說明的函數(shù)實(shí)現(xiàn)
當(dāng)應(yīng)用程序調(diào)用該函數(shù)時(shí),我們通過一條JMP指令直接跳轉(zhuǎn)到目標(biāo)函數(shù),其實(shí)現(xiàn)不依賴于參數(shù)和返回值。為了使目標(biāo)函數(shù)能夠正確執(zhí)行,我們必須確保跳轉(zhuǎn)到目標(biāo)函數(shù)后寄存器狀態(tài)沒有發(fā)生改變。為此,我們使用C++的naked關(guān)鍵字以保證編譯器編譯該方法時(shí)不會生成除該JMP指令之外的其他指令。為了通過編譯,這些函數(shù)的返回值被定義為void。對于大部分導(dǎo)出函數(shù)我們可以使用同樣的方法實(shí)現(xiàn),而無需知道其函數(shù)原型。
3.2 目標(biāo)函數(shù)的截獲
對于需要修改實(shí)現(xiàn)的函數(shù),由于函數(shù)的實(shí)現(xiàn)通常與函數(shù)的原型有關(guān),我們必須以圖8的方式實(shí)現(xiàn)。以截獲應(yīng)用程序?qū)D形庫的幀緩沖更新函數(shù)的調(diào)用為例,說明目標(biāo)函數(shù)的截獲過程。
使用Detours以函數(shù)為單位進(jìn)行截獲時(shí),我們需要獲得目標(biāo)函數(shù)的地址。Direct3D使用COM兼容對象,它向用戶提供基于COM的、面向?qū)ο蟮木幊探涌?。Present函數(shù)的地址無法從函數(shù)導(dǎo)出表中直接獲取,我們需要使用Detours依次完成CreateDXGIFactory、CreateSwapChain函數(shù)的截獲如圖10所示:

圖10 獲取Present函數(shù)地址
CreateDXGIFactory是DXGI圖形庫的導(dǎo)出函數(shù),它的成員變量中提供了IDXGIFactory接口地址。通過IDXGIFactory接口,我們可以獲取其成員函數(shù)CreateSwapChain的地址。截獲CreateSwapChain函數(shù),獲取其成員變量IDXGISwapChain接口地址,最終獲得IDXGISwapChain接口的Present函數(shù)地址。
在獲取接口的成員函數(shù)地址時(shí),它們的地址無法直接獲取。我們使用COM的基于C語言的編程接口獲取這些函數(shù)的地址。在C語言環(huán)境中,COM對象由結(jié)構(gòu)表示,其函數(shù)指針存放在該結(jié)構(gòu)的虛函數(shù)表成員中,因此可以通過lpVtbl獲得Present函數(shù)的地址[6]。使用Detours完成Present函數(shù)的截獲部分如圖11所示:

圖11 Present函數(shù)的截獲
為測試DirectX 10圖形應(yīng)用程序截獲技術(shù)的適用性和性能,基于本文提出的的截獲框架,利用已有的多投影顯示的幾何校正、邊緣融合算法,開發(fā)出一套單機(jī)驅(qū)動的多投影顯示系統(tǒng)。該系統(tǒng)對DirectX 10應(yīng)用程序來說是透明的,程序源碼不用做任何修改就能進(jìn)行多投影顯示。
在測試中我們使用Canon EOS 20D數(shù)碼攝像機(jī)獲取幾何校正參數(shù)和顏色校正參數(shù)。該相機(jī)支持RAW輸出格式,可直接用于ITF 恢復(fù)。由于該系統(tǒng)的投影幕為非平面,為此我們使用復(fù)旦大學(xué)軟件學(xué)院交互式圖形學(xué)實(shí)驗(yàn)室開發(fā)的基于二次曲線的幾何校正工具,可以方便地校正柱面投影幕上的幾何錯位。
測試過程中投影系統(tǒng)由一臺PC驅(qū)動,該機(jī)器配有Intel Core Duo CPU P8600,ATI Mobility Radeon HD 4300圖形顯示卡,及2GB內(nèi)存。系統(tǒng)的桌面分辨率為3072×768像素。通過一個(gè)Matrox TripleHead2Go設(shè)備,顯卡的輸出被分為3個(gè)分辨率為1024×768像素的輸出通道。每個(gè)輸出通道與一個(gè)EPSON EMP-8300 LCD投影儀相連。測試基準(zhǔn)程序我們使用Ubisoft 開發(fā)的阿凡達(dá)同名游戲 Avatar。Avatar支持寬屏顯示模式,可以在全屏模式下以3072×768像素的分辨率運(yùn)行。Avatar同時(shí)支持DirectX 9.0和DirectX 10.0如圖12所示:

圖12 單機(jī)驅(qū)動的多通道投影系統(tǒng)
圖12 a)為畫面校正前PC端Avatar原始的效果截圖。圖12 b)為通過我們的截獲技術(shù)和校正技術(shù),對畫面進(jìn)行幾何校正和顏色校正后PC端的Avatar的效果截圖。PC通過三屏寶與投影儀相連后,會在投影幕上獲得連續(xù)、統(tǒng)一的畫面。
我們測試了應(yīng)用截獲技術(shù)前后Avatar的繪制幀率與穩(wěn)定性。移植后Avatar的繪制幀率如圖13所示:

圖13 輸入三通道時(shí),使用函數(shù)截獲技術(shù)移植前后Avatar的繪制幀率
顯卡輸出3個(gè)通道,每個(gè)通道的分辨率為1024×768。移植的開銷主要來自兩部分:畫面截獲和校正繪制。使用DLL替換+Detours進(jìn)行函數(shù)截獲的開銷非常小,因此,校正繪制決定了移植技術(shù)對程序性能的影響。測試表明,函數(shù)截獲和畫面校正前Avatar的繪制幀率60幀/秒,經(jīng)過移植后Avatar的繪制幀率能達(dá)到50幀/秒,并且游戲運(yùn)行穩(wěn)定。
本文完善DLL替換+Detours的截獲框架,在不修改圖形應(yīng)用程序源代碼的情況下完成對DirectX 10庫函數(shù)的截獲工作。基于該框架,利用已有的多投影顯示的幾何校正、邊緣融合算法,開發(fā)出一套單機(jī)驅(qū)動的多投影顯示系統(tǒng),DirectX 10圖形應(yīng)用程序不用做任何修改就能多投影顯示。實(shí)驗(yàn)結(jié)果表明,該截獲技術(shù)對圖形應(yīng)用程序的性能影響小,程序畫面流暢,運(yùn)行穩(wěn)定。未來我們將研究DirectX 11圖形應(yīng)用程序的截獲技術(shù)。
[1] 毛燕東.面向單機(jī)驅(qū)動的多通道投影系統(tǒng)的圖形應(yīng)用程序移植與繪制技術(shù)[D].上海:復(fù)旦大學(xué), 2009:24-28.
[2] DXGI Overview[EB/OL]. https://msdn.microsoft.com/en -us/library/bb205075-(VS.85).aspx, 2015,04.
[3] Jiang Z. Mao Y. Qin B. et al. A high resolution video display system by seamlessly tiling multiple projectors[C]. IEEE International Conference on Multimedia and Expo. 2007: 2070-2073.
[4] Father H. Hooking Windows API-Technics of Hooking API Functions On Windows[J]. Assembly Programming Journal, 2004, 2(2).
[5] Pietrek M. Peering Inside the PE: A Tour of the Win32 Protable Executable File Format[J].Microsoft Systems Journal,1994,9:27-30.
[6] Win32 Portable Executable File Format.[EB/OL]. https://msdn.microsoft.co m/en-us/library/ms809762.aspx ,2015,04.
[7] Dynamic-Link Library Best Practices[EB/OL]. https://msdn.microsoft.com/en-us/library/windows /desktop/dn633971(v=vs.85).aspx, 2015,03.
[8] Hunt G. and Brubacher D. Detours: Binary interception of win32 functions[C]. Proceedings of the 3rd USENIX Windows NT Symposium,1999:12-13.
[9] Hunt, Galen C. and Michael L. Scott. Intercepting and Instrumenting COM Applications[C]. Proceedings of the Fifth Conference on Object-Oriented Technologies and Systems (COOTS’99),1999 :45-5'6.
TP393
A
2015.04.17)
1007-757X(2015)07-0022-05
李一天(1990-),男,安徽省人,復(fù)旦大學(xué),軟件學(xué)院,碩士研究生,研究方向:計(jì)算機(jī)圖形學(xué),人機(jī)交互技術(shù),上海,201203