Game Programming with DirectX -- 12[紅+綠+藍+Alpha]
第十二集 紅+綠+藍+Alpha
紅綠藍組成了絢爛的顏色世界, 當紅綠藍遇到Alpha後, 這個顏色世界又多了一份神祕.
神祕的Alpha, 我們可以簡單的認爲它和紅, 綠, 藍一樣, 是組成色彩的一種屬性, 用來表示透明度.
Directx Graphics 中的色彩一般使用 "RGB" 格式表示, RGB格式有三種原色 --- red(紅), green(綠), blue(藍), 其他顏色都是通過三種原色的不同比例混合來表示.
RGB格式現在最常見存儲方式是每種原色一個字節 --- 決定了現在的原色表示範圍只有[0, 255], RGB共三個字節(24位); 如再加上一個字節的Alpha值, 有四個字節(32位);
12.1 色彩
12.1.1 D3DCOLOR
32 位的RGB格式剛好是一個DWORD --- D3DCOLOR, 通常的順序如圖12.1,
|<------------------ 32 bits ------------------>|
+-----------+-----------+-----------+-----------+
| alpha | red | green | blue |
+-----------+-----------+-----------+-----------+
high bit low bit
圖12.1
DirectX Graphics 中爲我們提供生成這個DWORD的Macro,
#define D3DCOLOR_ARGB(a,r,g,b) /
((D3DCOLOR)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff)))
#define D3DCOLOR_RGBA(r,g,b,a) D3DCOLOR_ARGB(a,r,g,b)
#define D3DCOLOR_XRGB(r,g,b) D3DCOLOR_ARGB(0xff,r,g,b)
#define D3DCOLOR_XYUV(y,u,v) D3DCOLOR_ARGB(0xff,y,u,v)
#define D3DCOLOR_AYUV(a,y,u,v) D3DCOLOR_ARGB(a,y,u,v)
還有一個特殊的單位化表示的D3DCOLOR_COLORVALUE, DirectX Graphics把顏色基本運算合併在一個結構D3DXCOLOR中, 簡單實用.
12.1.2 Gamma校正
Gamma 校正是全屏的 , 現在的顯卡都支持, 一般 Gamma 校正的用途是調整屏幕顯示的明暗度, 同樣, Gamma 校正可以實現一些特殊效果, 如第一視覺的RPG中, 男主人公被敵人偷襲, 眼前景物漸漸變成血紅色 … , 男主人公從昏睡中醒過來, 眼前從黑黑一片到看到美麗的女主人公, 精彩的故事由此開始 … .
DirectX Graphics中, 色彩到屏幕輸出通過Gamma梯度映射校正, 如圖12.2
圖12.2
圖中梯度映射校正最大爲65535, 表示梯度使用WORD表示. 每種原色都有各自的梯度映射校正, 默認的三原色梯度映射校正都是相同的梯度斜率1, 例如對於RGB(0, 128, 255)進行校正, 如圖12.3
圖12.3
0 [梯度映射校正]--> 0 [梯度到原色] --> 0 [最後輸出顏色的RED分量]
128 [梯度映射校正]--> 32896 [梯度到原色] --> 128 [最後輸出顏色的GREEN分量]
255 [梯度映射校正]--> 65535 [梯度到原色] --> 255 [最後輸出顏色的BLUE分量]
現在我們改變GREEN分量的 梯度, 使其斜率爲2, 如圖12.4, 則有
圖12.4
0 [梯度映射校正]--> 0 [梯度到原色] --> 0 [最後輸出顏色的RED分量]
128 [梯度映射校正]--> 65535 [梯度到原色] --> 255 [最後輸出顏色的GREEN分量]
255 [梯度映射校正]--> 65535 [梯度到原色] --> 255 [最後輸出顏色的BLUE分量]
使用這種Gamma校正, 屏幕會明顯偏向於綠色.
DirectX Graphics 中 , RED, GREEN, BLUE 的 梯度通過D3DGAMMARAMP結構定義,
/* Gamma Ramp: Same as DX7 */
typedef struct _D3DGAMMARAMP
{
WORD red [256];
WORD green[256];
WORD blue [256];
} D3DGAMMARAMP;
然後通過IDirect3DDevice9的函數SetGammaRamp設置,
void SetGammaRamp(UINT iSwapChain,
DWORD Flags,
CONST D3DGAMMARAMP * pRamp);
void GetGammaRamp(UINT iSwapChain,
D3DGAMMARAMP * pRamp);
這裏注意有些顯卡使用WORD中的高位字節, 有些使用低位字節, 所以最好兩個字節設置相同的數值, 如0xCOCO.
12.2 Alpha Test
Alpha 字節的用處之一, 就是根據Alpha的數值和一特定數值的邏輯比較運算的結果決定當前位的顏色是否顯示.
12.2.1 邏輯比較運算類型
邏輯運算類型在枚舉結構中定義,
typedef enum _D3DCMPFUNC {
D3DCMP_NEVER = 1,
D3DCMP_LESS = 2,
D3DCMP_EQUAL = 3,
D3DCMP_LESSEQUAL = 4,
D3DCMP_GREATER = 5,
D3DCMP_NOTEQUAL = 6,
D3DCMP_GREATEREQUAL = 7,
D3DCMP_ALWAYS = 8,
D3DCMP_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */
} D3DCMPFUNC;
a. D3DCMP_NEVER : 所有比較都返回失敗
b. D3DCMP_LESS : 當前Alpha值小於特定值時返回成功
c. D3DCMP_EQUAL : 當前Alpha值等於特定值時返回成功
d. D3DCMP_LESSEQUAL : 當前Alpha值小於等於特定值時返回成功
e. D3DCMP_GREATER : 當前Alpha值大於特定值時返回成功
f. D3DCMP_NOTEQUAL : 當前Alpha值不等於特定值時返回成功
g. D3DCMP_GREATEREQUAL : 當前Alpha值大於等於特定值時返回成功
h. D3DCMP_ALWAYS : 所有比較都返回成功
12.2.2 Alpha-Test 的過程
1. 打開Alpha-Test標誌(一般只是在渲染的時候打開, 渲染結束後關閉)
IDirect3DDevice9::SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
2. 設置邏輯比較的類型(默認類型爲D3DCMP_ALWAYS)
IDirect3DDevice9::SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS);
3. 設置比較用的特定數值(默認爲0)
IDirect3DDevice9::SetRenderStateD3DRS_ALPHAREF, 0);
12.3 Alpha Test 例子
12.3.1 代碼更新
我們來看看game12的主要更新的代碼(下載game12 project),
---------------------------------------------------------------
// d9graphics.cpp 中, 根據鼠標選取的比較運算和特殊比較值更新
VOID CD9Graphics::UpdateAlpha()
{
m_pDev->SetRenderState(D3DRS_ALPHAREF, (DWORD)g_nValue);
m_pDev->SetRenderState(D3DRS_ALPHAFUNC, g_aTest[g_nTest]);
}
// d9graphics.cpp 中, 根據鼠標選取的不同Gamma校正更新屏幕明暗度
VOID CD9Graphics::UpdateGamma(INT nIndex)
{
if (g_gamma == nIndex)
{
return;
}
g_gamma = nIndex;
INT nGamma = g_aGamma[nIndex];
D3DGAMMARAMP map = { 0 };
for (INT i = 0; i < 256; i++)
{
map.red[i] = g_ramp.red[i] * nGamma;
map.green[i] = g_ramp.green[i] * nGamma;
map.blue[i] = g_ramp.blue[i] * nGamma;
}
m_pDev->SetGammaRamp(0, D3DSGR_CALIBRATE, &map);
}
VOID CD9Graphics::Render()
{
// reset device if last render detect the device lost
HRESULT h = 0L;
if (g_bLastDev)
{
h = m_pDev->TestCooperativeLevel();
if (h == D3DERR_DEVICENOTRESET)
{
TermObject();
m_pDev->Reset(&g_d3dpm);
InitObject();
g_bLastDev = FALSE;
}
else
{
return;
}
}
m_pDev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
if (FAILED(m_pDev->BeginScene()))
{
return;
}
m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
m_pDev->SetStreamSource(0, m_pVB, 0, sizeof(MYVERTEXTEX));
m_pDev->SetFVF(D3DFVF_MYVERTEXTEX);
m_pDev->SetTexture(0, m_pTexture);
m_pDev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
m_pDev->EndScene();
h = m_pDev->Present(NULL, NULL, NULL, NULL);
if (FAILED(h))
{
if (h == D3DERR_DEVICELOST)
{
g_bLastDev = TRUE;
}
else if (h == D3DERR_DRIVERINTERNALERROR)
{
DestroyWindow(g_hWnd);
}
}
}
---------------------------------------------------------------
12.3.2 例子說明
例子中使用的天藍色256 * 256的圖片每位都有特定的Alpha值, 當使用不同的比較運算同不同的比較數值進行比較後, 得到的結果影響顯示的效果. Alpha-Test的運算效率優於Alpha-Blend, 所以如果能用Alpha-Test代替Alpha-Blend得到相同的效果, 建議使用Alpha-Test.
12.4 Alpha Blend
Alpha Blend 中也使用到了Alpha字節, 但Alpha字節並不是起決定性作用的. Alpha Blend的作用是將要寫入幀緩衝器的顏色(源顏色)和幀緩衝器中原有顏色(目標顏色)按比例係數進行混合, 混合時分成三原色分量進行, 顏色混合公式,
FinalColor = SrcColor * SrcBlendFactor + DestColor * DestBlendFactor
12.4.1 混合比例係數
SrcBlendFactor 和DestBlendFactor取值定義在D3DBLEND中, DirectX Graphics SDK中有詳細的說明
12.4.2 Alpha Blend 的過程
1. 打開Alpha Blend標誌(一般只是在渲染的時候打開, 渲染結束後關閉)
IDirect3DDevice9::SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
2. 設置源和目標比例係數
( 默認源比例係數爲D3DBLEND_ONE, 目標比例係數爲D3DBLEND_ZERO)
IDirect3DDevice9::SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE)
IDirect3DDevice9::SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO)
12.5 Alpha Blend 例子
12.5.1 代碼更新
我們來看看 game13 的主要更新的代碼 ( 下載 game13 project),
---------------------------------------------------------------
// d9graphics.cpp 中, 根據鼠標選取的源和目標比例係數更新
VOID CD9Graphics::UpdateAlpha()
{
m_pDev->SetRenderState(D3DRS_SRCBLEND, g_aBlend[g_nSrc]);
m_pDev->SetRenderState(D3DRS_DESTBLEND, g_aBlend[g_nDst]);
}
VOID CD9Graphics::Render()
{
//...
m_pDev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
if (FAILED(m_pDev->BeginScene()))
{
return;
}
m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
m_pDev->SetStreamSource(0, m_pVB, 0, sizeof(MYVERTEXTEX));
m_pDev->SetFVF(D3DFVF_MYVERTEXTEX);
m_pDev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
m_pDev->EndScene();
h = m_pDev->Present(NULL, NULL, NULL, NULL);
//...
}
---------------------------------------------------------------
12.5.2 例子說明
例子中使用RGB(0, 0, 128)的幀緩衝器背景色, 物體顏色爲RGB(128, 0, 0), 根據不同的源和目標比例係數, 混合的顏色不同. 例子中沒有使用光照, 加上光照Alpha Blend效果相同, 只是源顏色加上了光源提供的顏色.
a. 源比例係數 : D3DBLEND_ONE; 目標比例係數 : D3DBLEND_ZERO
RGB(128, 0, 0) = RGB(128, 0, 0, X) * (1, 1, 1, 1) +
RGB(0, 0, 128, X) * (0, 0, 0, 0)
b. 源比例係數 : D3DBLEND_SRCCOLOR; 目標比例係數 : D3DBLEND_ZERO
這時的源比例爲單位化的物體顏色 == (0.5, 0, 0, X)
RGB(64, 0, 0) = RGB(128, 0, 0, X) * (0.5, 0, 0, X) +
RGB(0, 0, 128) * (0, 0, 0, 0)
c. 源比例係數 : D3DBLEND_SRCCOLOR; 目標比例係數 : D3DBLEND_DESTCOLOR
源比例爲單位化的物體顏色 == (0.5, 0, 0, X)
目標比例爲單位化的幀緩衝器背景色 == (0, 0, 0.5, X)
RGB(64, 0, 64) = RGB(128, 0, 0, X) * (0.5, 0, 0, X) +
RGB(0, 0, 128, X) * (0, 0, 0.5, X)
其他類型的Alpha Blend同樣可以計算出混合的顏色.
Alpha Blend 在多重紋理中是非常重要的一項功能.