艾莉兒:在OpenGL環境下,我可是顛倒著工作的哦。2018/12/9 EDIT:增加3D坐標轉換的部分
這次介紹Direct3D和OpenGL的一個重要差異,
OpenGL對貼圖坐標的定義是上下顛倒的。一般繪圖軟體都以圖的左上角為原點,用外部函式庫讀取圖檔,傳回的記憶體區塊也是從左上角開始,然後往下第二列、第三列……,一直到右下角,Direct3D也採用這個設計。
但OpenGL定義(0,0)是圖的左下角,把貼圖從主記憶體傳給顯示記憶體的函式glTexImage2D,也假設給它的指標是從左下角開始,所以顯示記憶體裡儲存的貼圖其實是倒立的。
而兩者的螢幕坐標定義相同,都是左下角(-1,-1),右上角(1,1)。
(圖中紅字是貼圖坐標,藍字是螢幕坐標)
不過可以看到兩種情況的貼圖,帽子的羽飾都是靠近(0,0),(1,1)都是左耳。
傳入這4個頂點的話
貼圖坐標 |
螢幕坐標 |
(0,0) |
(-1,1) |
(1,0) |
(1,1) |
(1,1) |
(1,-1) |
(0,1) |
(-1,-1) |
請看上圖把坐標一個一個對應,D3D和OpenGL都可以畫出正立的鈷寶頭像,此時直接用相同的坐標、相同的矩陣不會有問題。
可是,如果有用到framebuffer object做兩階段畫圖就會出問題了。
以這個紙娃娃系統為例,先在一個buffer把頭和五官重疊再畫到畫面上,第一階段會變成正立,可是再把這個framebuffer畫在主畫面時(假設要用在對話框),使用相同的規則會畫成倒立的。
同理多一個階段又會倒轉一次,如果有些貼圖來自檔案,有些是用framebuffer產生,就會正立、倒立夾雜了。
要是畫之前先檢查貼圖來源,是framebuffer就做特別處理把坐標反向呢?是可以讓坐標正確,但是要多記錄貼圖種類,畫的時候還多出計算坐標的步驟。
對這個問題,我的解法是:
在OpenGL內部完全以倒立的狀態處理。矩陣stack裡的第一個矩陣,2D繪圖時是把-1~1換成單位是像素,就做y坐標逆轉的處理。
(矩陣的寫法是把向量放在右邊乘)
之後如果要做其他轉換乘上其他矩陣,y坐標逆轉的效果會保留。
3D的坐標轉換是將頂點坐標依序乘以model、view、projection矩陣,我在projection動手腳,將OpenGL的Y坐標逆轉,model和view則是兩個平台相同。
(fovX=橫向視角,fovX=縱向視角,cot是三角函數的cotangent)
至於culling設定,顯示晶片是把矩陣都乘完,算出螢幕坐標後才處理culling,因此兩者的culling設定要相反。
因為PMD模型是順時針為正面,我採用順時針為正。
//Direct3D ID3D11RasterizerState* cullNoMSRS; //名稱由來:有cull,無multisample的rasterizer state D3D11_RASTERIZER_DESC rsState; ZeroMemory(&rsState, sizeof(D3D11_RASTERIZER_DESC)); rsState.FillMode = D3D11_FILL_SOLID; rsState.CullMode = D3D11_CULL_BACK; rsState.FrontCounterClockwise = 0; //設定順時針為正面 rsState.DepthClipEnable = TRUE; device->CreateRasterizerState(&rsState, &cullNoMSRS);
//OpenGL設定逆時針為正面 glFrontFace(GL_CCW); |
至於如何讓玩家看到的是正立?我的引擎有做一個功能,因為視窗可以縮放,大小不一定和遊戲內定解析度相同,所以先把畫面畫在一個內部buffer,整個畫面畫完才把buffer貼到主畫面,正好可以在這一步做。
過程如下
OpenGL的這個現象,我買的一本介紹D3D和OpenGL的書裡也沒寫,是用了一個工具:GLIntercept觀察它的內部資料才發現的,製作Cyber Sprite紙娃娃系統時也確實造成問題,然後想出了將y坐標顛倒的方法。
這樣以後引擎要做Direct3D版的話,D3D和OpenGL可以用相同的矩陣和頂點坐標,不用準備兩份資料。
關於開頭的圖:還是無線繪比較容易畫,這個畫法描線花了很久,上色倒是很快。
配色沒餘力從頭想了,把以前畫某張圖的記錄拿來用。
還有想小幅修改一下艾莉兒和鈷寶的服裝,但還沒有定案。