1. 程式人生 > >Direct-X學習筆記--Alpha顏色混合

Direct-X學習筆記--Alpha顏色混合

Alpha混合技術是灰常有用的東東。待我好好學習一下。

一.簡介

首先看一下Alpha通道,Alpha通道是計算機中儲存圖片透明度資訊的通道,它是一個8位灰度的通道,用256級灰度記錄影象中的透明資訊,定義透明,不透明,半透明等,其中黑色表示完全透明,白色表示不透明,灰色為半透明。 如果不用Alpha混合,我們繪製圖形的顏色總是替換當前顏色緩衝區中存在的顏色,這樣後面的物體總是覆蓋在原有的物體上。但是當想要繪製類似於玻璃、水等具有透明效果的物體時,這種方法顯然滿足不了要求。通過定義一個表示物體半透明度的Alpha值和一個半透明計算公式,可以將要繪製的物體顏色與顏色緩衝區中存在的顏色相混合,從而繪製出具有半透明效果的物體,即傳說中的Alpha Blend。

二.Direct-X中的Alpha融合公式

DirectX中的Alpha融合公式如下: OutPutColor = (RGBsrc * Ksrc) OP (RGBdst * Kdst)

OutPutColor表示alpha混合後的顏色值. RGBsrc表示源顏色值,即將要繪製的圖元的顏色值 Ksrc表示源混合係數,通常賦值為表示半透明程度的alpha值,也可以是屬於列舉型別D3DBLEND的任意值,用來和RGBsrc相乘。 RGBdst表示目標顏色值,即當前顏色緩衝區中的顏色值 Kdst表示目標混合係數,可以是屬於列舉D3DBLEND的任意值,用來和RGBdst相乘。 OP表示源計算結果與顏色緩衝區計算結果的混合方法,預設狀態下OP為D3DBLEND_ADD,即源計算結果與顏色緩衝區計算結果相加。


圖形顯示中,對alpha混合最普遍的用法是:把OP賦值為D3DBLEND_ADD,使源計算結果和顏色緩衝區計算結果相加,這樣一來,alpha混合顏色的公式變為:

color = (RGBsrc * Ksrc) + (RGBdst * Kdst) 注意,這個公式中的*,其實指的是向量的點積運算,因為所謂的RGBsrc,和RGBdest都是一個顏色和Alpha組合而成的四元組(R,G,B,A)即紅,綠,藍,Alpha值,各對應融合的引數。所以更加詳細的來看,上面的公式應該寫成: color = (Rsrc * Ksrc1 + Rdst * Kdes1, Gsrc * Ksrc2 + Gdst * Kdst2, Bsrc * Ksrc3 + Bdst * Kdst3, Asrc * Ksrc4  + Adst * Kdst4); 而其實這個公式可以更加簡化,即我們要透明的物體使用一個係數K,而要透明到的地方只需要設定成(1- K)即可,在DX中已經為我們設定好了這個巨集:即把Ksrc賦值為D3DBLEND_SRCALPHA,即當前繪製畫素的alpha值;把Kdst賦值為D3DBLEND_INVSRCALPHA,即1減去當前繪製畫素的alpha值。 其實上面的設定已經可以較好地模擬大多數半透明物體的效果。當然我們也可以自己再設定其他的融合方式。所以我們還是有必要記一下其他的融合方式以及融合係數。

三. 融合的方式和融合係數整理

1.融合方式及操作符:
D3DBLENDOP_ADD源畫素計算結果與目標畫素的計算結果相加,即【最終結果】=【源】+【目標】
D3DBLENDOP_SUBTRACT源畫素計算結果與目標畫素的計算結果相減,即【最終結果】=【源】-【目標】
D3DBLENDOP_REVSUBTRACT目標畫素的計算結果減去源畫素計算結果,即【最終結果】=【目標】-【源】
D3DBLENDOP_MIN在源畫素計算結果和目標畫素計算結果之間取小者。即【最終結果】= MIN(【目標】,【源】)
D3DBLENDOP_MAX在源畫素計算結果和目標畫素計算結果之間取大者。即【最終結果】= MAX(【目標】,【源】) 2.融合因子: D3DBLEND_ZERO融合因子=(0,0,0,0)
D3DBLEND_ONE融合因子=(1,1,1,1)
D3DBLEND_SRCCOLOR融合因子=(R_src,G_src,B_src,A_src)
D3DBLEND_INVSRCCOLOR融合因子=(1-R_src,1-G_src,1-B_src,1-A_src)
D3DBLEND_SRCALPHA融合因子=(1-A_src,A_src,A_src,A_src)
D3DBLEND_INVSRCALPHA融合因子=(1-A_src,1-A_src,1-A_src,1-A_src)
D3DBLEND_DESTALPHA融合因子=(A_dst , A_dst, A_dst  , A_dst)
D3DBLEND_INVDESTALPHA融合因子= (1-A_dst, 1-A_dst, 1-A_dst , 1-A_dst ).
D3DBLEND_DESTCOLOR融合因子=(R_dst , G_dst, B_dst  , A_dst).
D3DBLEND_INVDESTCOLOR融合因子= (1 - R_dst, 1 - G_dst, 1 - B_dst, 1 - A_dst).
D3DBLEND_SRCALPHASAT融合因子= (f, f, f, 1),其中f = min(A_src,1 - A_dst)

其中R_src  , G_src , B_src  , A_src分別表示源(即source)畫素的紅、綠、藍、透明四個分量值,而R_dst  , G_dst, B_dst  , A_dst表示目標(即destination)畫素的紅、綠、藍、透明四個分量值。

四.Alpha值的設定

使用Alpha融合時,需要明確Alpha值的來源。我們在設定一個物件的顏色屬性時,有三種方式: 1.頂點顏色:這個是最古老的方法,也是最麻煩的。最早使用頂點緩衝區或者索引緩衝區繪圖的時候設定過定點屬性,其中有顏色屬性,可以設定Alpha值。 2.光照和材質:材質中各種光的反射係數是一個四元組,其中就包含了Alpha值。 3.紋理:最容易的就是設定紋理來確定一個模型的顏色,所以這個也是最常用的。 既然有三種設定物件的顏色Alpha值的方式,而且常用程度是紋理>光照材質>頂點顏色,所以Alpha值的來源順序也就很明瞭了,如果有紋理,那就從紋理獲取,如果沒有紋理,那就從光照材質中獲取,如果光照材質也沒有,那就從頂點屬性中獲取。

五.使用Alpha Blend

個人感覺Blend用於R,G,B,A都可以,可能是因為透明的情況用得比較多,所以才常用Alpha blend吧(純屬個人猜測)。 這裡我們使用了第二種情況,設定光照材質,改變模型材質的Alpha值,來達到觀察不同Alpha值進行融合的效果。 感覺Alpha Blend就是我們在繪製某個透明物體時,開啟的一種渲染狀態。 使用Alpha Blend有下面幾部: 1.設定繪製物件的Alpha值,預設的值是1.0f,即不透明,所以我們如果需要設定某個物件為透明的,就改變他的Alpha值,就可以讓其透明。 這裡面,我們直接使用的是物件的材質資訊,即預設的1.0f,並且增加了一個可以改動材質Alpha值的函式,讓Alpha值改變。 2.開啟Alpha測試 DX預設是關閉Alpha測試的,所以我們需要通過DX的萬能函式...SetRenderState來開啟Alpha測試。
<span style="white-space:pre">	</span>//開啟Alpha融合
	m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
當然,關閉的話,第二個引數為false即可。我們繪製完透明物件之後,不需要Alpha blend時,要將其關閉。 3.設定融合因子
這裡面就是用上面我們說過的,DX為我們準備的最常用的那兩個融合因子就可以啦!
<span style="white-space:pre">	</span>//設定融合因子
	m_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);		//設定源融合因子
	m_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);	//設定目標融合因子
4.設定融合運算方式 這一步其實可以省略的,DX預設的Alpha融合方式就是ADD方式,但是我們還是寫一下,萬一哪天不用這個了呢,還得會設定別的呀!
<span style="white-space:pre">	</span>//設定融合運算方式(可以省略,DX預設即為D3DBLENDOP_ADD的融合運算方式)
	m_pDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);

六.完整的Demo

這裡面,我們使用了一個龍的模型(好吧,還是從淺墨大大那裡偷得),讀取之後,不使用紋理資訊,使用的預設的材質資訊,並且新增一個可以改變材質Alpha值的函式,用來改變物件的透明度。 先來一張沒去掉材質的截圖:
去掉紋理資訊,開啟Alpha融合,預設Alpha值為1.0f,即完全不透明

減少材質中Diffuse中的Alpha值,半透明:
再減少,再減少,咦?龍呢? 答:被我吃了....
程式碼(將Alpha混合部分和材質資訊部分的程式碼放在了之前縮寫的CMesh類中,所以這裡僅貼出Mesh.cpp相關內容):
#include "stdafx.h"
#include "Mesh.h"


CMesh::CMesh(LPDIRECT3DDEVICE9 pDevice) : m_pDevice(pDevice)
{

}


CMesh::~CMesh(void)
{
	//釋放相關資源
	SAFE_DELETE_ARRAY(m_pMaterials);

	if (m_pTextures)
	{
		for (int i = 0; i < m_dwNumMtrls; i++)
		{
			SAFE_RELEASE(m_pTextures[i]);
		}
		SAFE_DELETE_ARRAY(m_pTextures);
	}

	SAFE_RELEASE(m_pMesh);
}

void CMesh::CreateMesh(LPSTR filename)
{
	LPD3DXBUFFER pAdjBuffer = NULL;
	LPD3DXBUFFER pMtrlBuffer = NULL;

	D3DXLoadMeshFromX(filename, D3DXMESH_MANAGED, m_pDevice, &pAdjBuffer, &pMtrlBuffer, NULL, &m_dwNumMtrls, &m_pMesh);

	//讀取材質和紋理資料
	D3DXMATERIAL *pMtrl = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer();

	m_pMaterials = new D3DMATERIAL9[m_dwNumMtrls];
	m_pTextures = new LPDIRECT3DTEXTURE9[m_dwNumMtrls];

	for (int i = 0; i < m_dwNumMtrls; i++)
	{
		//材質資訊
		m_pMaterials[i] = pMtrl[i].MatD3D;

		m_pTextures[i] = NULL;
		//不使用Mesh模型的紋理資訊
		//D3DXCreateTextureFromFileA(m_pDevice, pMtrl[i].pTextureFilename, &m_pTextures[i]);

	}

	//優化網格模型
	m_pMesh->OptimizeInplace(D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_STRIPREORDER, (DWORD*)pAdjBuffer->GetBufferPointer(), NULL, NULL, NULL);

	SAFE_RELEASE(pAdjBuffer);
	SAFE_RELEASE(pMtrlBuffer);
	
}

void CMesh::DrawMesh(const D3DXMATRIXA16& matWorld)
{
	m_pDevice->SetTransform(D3DTS_WORLD, &matWorld);
	///Alpha Blend相關

	//開啟Alpha融合
	m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
	//設定融合因子
	m_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);		//設定源融合因子
	m_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);	//設定目標融合因子
	//設定融合運算方式(可以省略,DX預設即為D3DBLENDOP_ADD的融合運算方式)
	m_pDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
	
	//繪製Mesh
	for (int i = 0; i < m_dwNumMtrls; i++)
	{
		m_pDevice->SetMaterial(&m_pMaterials[i]);
		m_pDevice->SetTexture(0, m_pTextures[i]);
		m_pMesh->DrawSubset(i);
	}
}

//增加Alpha值的函式
void CMesh::AddAlphaValue()
{
	for (int i = 0; i < m_dwNumMtrls; i++)
	{
		m_pMaterials[i].Diffuse.a += 0.1f;
		if (m_pMaterials[i].Diffuse.a > 1.0f)
			m_pMaterials[i].Diffuse.a = 1.0f;
	}
}

//減少Alpha值的函式
void CMesh::ReduceAlphaValue()
{
	for (int i = 0; i < m_dwNumMtrls; i++)
	{
		m_pMaterials[i].Diffuse.a -= 0.1f;
		if (m_pMaterials[i].Diffuse.a < 0.0f)
			m_pMaterials[i].Diffuse.a = 0.0f;
	}
}