【Direct2D開發】 通過操作像素實現紋理混合


轉載請注明出處:http://www.cnblogs.com/Ray1024

 

一、概述

我們都知道Direct2D可以加載並顯示圖片,但是不知道你有沒有想過,這個2D的圖形引擎可以進行紋理混合嗎?如果可以進行紋理混合,那我們2D的圖形引擎就可以做更多的事情,我們可以對圖片進行更加豐富的操作。

接觸過3D渲染知識的人都知道着色器這個東西,在3D渲染中,着色器分為頂點着色器和像素着色器,這里我們主要實現的是類似於3D渲染中的像素着色器的功能,即紋理(圖片)混合。

 

二、思路解析

在Direct2D中想要實現紋理(圖片)混合的功能,我們就可以考慮,如果我們可以讀寫紋理(圖片)的每個像素的color數據,那就可以實現紋理(圖片)混合的功能。

但是我們如何來操作(讀寫)圖片的像素數據呢?

因為Direct2D加載圖片是用windows圖像處理組件(WIC),我在WIC的MSDN文檔中找到了方法。

1.IWICBitmap::Lock函數介紹

HRESULT Lock(
  [in]  const WICRect        *prcLock,
  [in]        DWORD          flags,
  [out]       IWICBitmapLock **ppILock
);

功能 :提供對位圖的矩形區域的訪問
參數 :
	prcLock [in]	要訪問的矩形區域
	flags 	[in]	訪問模式(讀/寫)
	ppILock [out]	接收鎖定的內存位置的指針,IWICBitmapLock類型
返回 :如果成功,返回S_OK

 

2.關於IWICBitmapLock類型,我們介紹它的一個成員函數:

HRESULT GetDataPointer(
  [out] UINT *pcbBufferSize,
  [out] BYTE **ppbData
);

功能:獲取鎖定矩形中左上角像素的指針
參數:
	pcbBufferSize 	[out]	獲取內存大小
	ppbData 		[out]	獲取內存數據

 

接下來,我們將詳細介紹實現紋理混合的過程。

 

三、紋理混合實現

1.加載IWICBitmap對象

這一步相信大家都很熟悉了,因為每次創建D2D位圖都必須經過這一步操作。直接上代碼:

ID2D1Bitmap*			pBitmap		= NULL;
IWICBitmapDecoder*		pDecoder	= NULL;
IWICBitmapFrameDecode*	pSource		= NULL;
IWICBitmap*				pWIC		= NULL;
IWICFormatConverter*	pConverter	= NULL;
IWICBitmapScaler*		pScaler		= NULL;
UINT					originalWidth	= 0;
UINT					originalHeight	= 0;

// 1.加載IWICBitmap對象

HRESULT hr = pIWICFactory->CreateDecoderFromFilename(
	uri,
	NULL,
	GENERIC_READ,
	WICDecodeMetadataCacheOnLoad,
	&pDecoder
	);

if (SUCCEEDED(hr))
{
	hr = pDecoder->GetFrame(0, &pSource);
}

if (SUCCEEDED(hr))
{
	hr = pSource->GetSize(&originalWidth,&originalHeight);
}

if (SUCCEEDED(hr))
{
	hr = pIWICFactory->CreateBitmapFromSourceRect(
		pSource, 0,0,(UINT)originalWidth,(UINT)originalHeight, &pWIC);
}

 

2.從IWICBitmap對象讀取像素數據

先從WIC位圖創建IWICBitmapLock對象,然后從IWICBitmapLock獲取圖片像素數據的指針,代碼如下:

	// 2.從IWICBitmap對象讀取像素數據
	IWICBitmapLock *pILock = NULL;
	WICRect rcLock = { 0, 0, originalWidth, originalHeight };
	hr = pWIC->Lock(&rcLock, WICBitmapLockWrite, &pILock);

	if (SUCCEEDED(hr))
	{
		UINT cbBufferSize = 0;
		BYTE *pv = NULL;

		if (SUCCEEDED(hr))
		{
			// 獲取鎖定矩形中左上角像素的指針
			hr = pILock->GetDataPointer(&cbBufferSize, &pv);
		}

 

3.進行紋理混合的像素計算

對獲取到的圖片像素數據進行像素計算,代碼如下:

		// 3.進行紋理混合的像素計算
		for (unsigned int i=0; i<cbBufferSize; i+=4)
		{
			if (pv[i+3] != 0)
			{
				pv[i]	*=color.b;
				pv[i+1]	*=color.g;
				pv[i+2]	*=color.r;
				pv[i+3] *=color.a;
			}
		}

在上面代碼中,需要注意的是像素計算的方法為顏色color的分量相乘。

還有,細心的朋友可以看出,像素數據的步長為4,每個步長內的4個數組成一個像素完整的顏色值,並且顏色格式為BGRA格式,每一個顏色的取值范圍為0.f~1.f。

 

4.顏色混合操作結束,釋放IWICBitmapLock對象

紋理混合計算結束后,調用Rlease函數釋放IWICBitmapLock對象,即可將計算后的圖片像素數據寫入IWICBitmap對象即WIC位圖,如下:

		// 4.顏色混合操作結束,釋放IWICBitmapLock對象
		pILock->Release();

 

5.使用WIC位圖創建D2D位圖

到現在為止,真正意義上的紋理混合的像素數據的讀取、計算和寫入就完成了。我們直接使用WIC位圖創建D2D位圖即可,如下:

// 5.使用IWICBitmap對象創建D2D位圖

if (SUCCEEDED(hr))
{
	hr = pIWICFactory->CreateFormatConverter(&pConverter);
}
if (SUCCEEDED(hr))
{
	hr = pIWICFactory->CreateBitmapScaler(&pScaler);
}
if (SUCCEEDED(hr))
{
	hr = pScaler->Initialize(pWIC, (UINT)originalWidth, (UINT)originalHeight, WICBitmapInterpolationModeCubic);
}
if (SUCCEEDED(hr))
{
	hr = pConverter->Initialize(
		pScaler,
		GUID_WICPixelFormat32bppPBGRA,
		WICBitmapDitherTypeNone,
		NULL,
		0.f,
		WICBitmapPaletteTypeMedianCut
		);
}

if (SUCCEEDED(hr))
{
	hr = pRenderTarget->CreateBitmapFromWicBitmap(
		pConverter,
		NULL,
		&pBitmap
		);
}

 

6.顯示

上面的一系列紋理混合操作結束后,我們就可以將混合之后的紋理繪制到窗口顯示了。

在我們這個例子中,創建了4個ID2D1Bitmap對象即D2D位圖,m_pBitmap為原圖的位圖,m_pBitmapBlended、m_pBitmapBlended1、m_pBitmapBlended2分別為原圖和紅色、綠色、藍色進行紋理混合之后的位圖,創建代碼如下(GetBlendedBitmapFromFile函數為我們上面介紹的所有步驟):

		// 創建位圖
		if (SUCCEEDED(hr))
		{
			LoadBitmapFromFile(m_pRT,m_pWICFactory, L"bitmap.png",0,0, &m_pBitmap);
		}

		// 創建位圖,並進行顏色混合
		if (SUCCEEDED(hr))
		{
			// 從文件創建WIC位圖,將WIC位圖進行顏色混合,之后創建D2D位圖
			m_pBitmapBlended = GetBlendedBitmapFromFile(m_pWICFactory, m_pRT, L"bitmap.png", D2D1::ColorF(1, 0, 0, 1));//紅色
			m_pBitmapBlended1 = GetBlendedBitmapFromFile(m_pWICFactory, m_pRT, L"bitmap.png", D2D1::ColorF(0, 1, 0, 1));//綠色
			m_pBitmapBlended2 = GetBlendedBitmapFromFile(m_pWICFactory, m_pRT, L"bitmap.png", D2D1::ColorF(0, 0, 1, 1));//藍色
		}

 

結果演示圖如下:

 

前面只介紹了紋理混合的重要代碼,其余代碼就不列出了,有興趣的朋友可以點擊此處下載,源碼為D2DBitmapBlend。

 

四、擴展延伸

上面介紹完Direct2D中的紋理混合操作,但是還是比較簡單的操作,因為它只對紋理進行顏色混合。

其實,我們還可以進行紋理之間的混合操作。原理很簡單,如下:

  1.創建疊加紋理,讀取像素數據;

  2.創建主紋理,讀取疊加像素數據;

  3.使用主像素數據和疊加像素數據行混合操作;

  4.使用計算后的主紋理WIC位圖創建D2D位圖;

  5.顯示。

注意,兩個紋理進行混合的計算方法很重要,這需要借鑒3D渲染中的線性插值法進行紋理混合。

 

接觸過3D渲染的朋友都會知道,3D渲染中,紋理混合的計算方式原理為線性插值,比如GLSL中mix函數,如下:

genType mix (genType x, genType y, float a)

最終的片段顏色值由mix函數將兩者進行混合后得到。mix這個函數是GLSL中一個特殊的線性插值函數,前兩個參數分別為主紋理和疊加紋理的像素數據,第三個參數為紋理混合中的疊加紋理所占的比例,計算原理如下:

  x和y混合之后 = x⋅(1−a)+y⋅a

這就是我們用到的紋理混合的計算原理。

 

我們現在進行2個紋理混合的操作,這里我只貼上紋理混合的線性混合計算的部分:

		for (unsigned int i=0; i<cbBufferSize; i+=4)
		{
			if (pv[i+3] != 0)
			{
				pv[i]	= pv[i]*(1-proportion)	 + pv1[i]*proportion;
				pv[i+1]	= pv[i+1]*(1-proportion) + pv1[i+1]*proportion;
				pv[i+2]	= pv[i+2]*(1-proportion) + pv1[i+2]*proportion;
				pv[i+3] = pv[i+3]*(1-proportion) + pv1[i+3]*proportion;				
			}
		}

上面計算部分的proportion為疊加紋理占的比例,這個參數是紋理混合中必不可少的部分。其余代碼省略,有興趣的朋友可以點擊此處下載,源碼為D2DBitmapBlendWithBitmap。

 

這是兩個紋理混合后的效果如下:

 

五、結語

Direct2D中的紋理混合過程到這里就全部介紹完了。這樣我們使用Direct2D也可以達到3D渲染中紋理混合的效果了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM