天天看點

桌面透明視窗程式渲染

        市面上基本所有的3D遊戲都依賴一個普通的windows視窗,包含标題欄、邊框、最小化、最大化、關閉按鈕。視窗的大小決定了玩家可視的遊戲空間,整個視窗的像素都被遊戲内容填充滿,視窗背景不是透明的。渲染時,隻要建立一個主渲染緩沖區,将各元素渲染在上面,再顯示就可以了。

        本文介紹一種方法,視窗的背景是透明的,視窗中隻渲染主要的遊戲元素,比如主角,而windows桌面就是舞台,你可以看到你的角色在桌面上奔跑,還可以用滑鼠與它互動,如圖:

桌面透明視窗程式渲染
桌面透明視窗程式渲染
桌面透明視窗程式渲染

采用類似技術的遊戲有“哈姆寶寶”、“寵物王國”等。該技術比較适合寵物養成類遊戲,比傳統2D寵物的渲染方式要複雜的多。

//華麗的分隔線

        先介紹下渲染的基本步驟:

        1. 将角色渲染到貼圖(RenderToTexture),此時貼圖在顯存中

        2. 貼圖内容拷貝到System Memory Texture,此時貼圖在記憶體中。(對顯示卡帶寬要求較高,在老舊的顯示卡上,大部分的性能都消耗在這個步驟,CPU占用率90%以上)

        3. 從System Memory Texture“拷貝”到windows視窗的GDI位圖上。這裡不是簡單的拷貝,需要對貼圖中每個像素進行特殊處理。

        4. 更新視窗内容,就完成顯示了

        需要注意的是,步驟1和2中的貼圖,大小、格式一緻,并且與所在視窗的客戶區大小一緻(客戶區大小不一定等同于視窗大小)。是以,這裡都是逐像素拷貝,不存在任何拉伸問題。

        按照以上渲染步驟,是以,渲染前要做的準備工作如下:

        1. 建立視窗

        2. 建立與視窗關聯的GDI位圖

        3. 建立System Memory Texture

        4. 建立渲染用的Texture

//華麗的分隔線

【準備步驟1】

拿上圖中的例子來說,渲染該寵物的貼圖大小為256x256,那麼首先要建立一個同等大小的Windows視窗,不帶邊框:

        m_hWnd = ::CreateWindowEx(WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_LAYERED, ClassName, Title, WS_POPUP,0,0,256,256,0,NULL,hInst, NULL);

注意到style參數為WS_POPUP,就是建立不帶邊框、标題欄等内容的視窗,這時,客戶區大小等同于視窗大小。

WS_EX_LAYERED也是至關重要的一個屬性,用于實作視窗透明化功能,後面會提到。

其它參數請查閱MSDN。

【準備步驟2】

然後是建立GDI位圖,代碼如下:

BITMAPV4HEADERbm4;

bm4.bV4Width = 256;         //視窗寬度

bm4.bV4Height = -256;      //視窗高度。為什麼是負數,請查閱BITMAP相關技術文檔

bm4.bV4BitCount = 32;     //像素比特數,這裡必須為32,像素格式A8R8G8B8

bm4.bV4Size = sizeof(BITMAPV4HEADER);

bm4.bV4Planes = 1;

bm4.bV4V4Compression = BI_RGB;

m_hDC = ::CreateCompatibleDC( 0 );

m_hBp = ::CreateDIBSection(m_hDC, (BITMAPINFO *)&bm4, DIB_RGB_COLORS, &m_pBitmapBits, 0, 0 );

m_hOldObj = ::SelectObject(m_hDC, m_hBp);

退出時記得銷毀:

if ( m_hDC )

{

::SelectObject( m_hDC, m_hOldObj );

::DeleteDC(m_hDC);

m_hDC = NULL;

}

if ( m_hBp )

{

::DeleteObject( m_hBp );

m_hBp = NULL;

}

變量聲明如下:

HDC m_hDC;

HBITMAP m_hBp;

HGDIOBJ m_hOldObj;

void *            m_pBitmapBits;

【準備步驟3】

建立System Memory Texture:

hr = dev9->CreateOffscreenPlainSurface( 256, 256, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &m_pSysMemSurface, NULL );  //D3DFMT_X8R8G8B8也可以

銷毀代碼:

if ( m_pSysMemSurface )

{

m_pSysMemSurface->Release();

m_pSysMemSurface = NULL;

}

變量聲明如下:

LPDIRECT3DSURFACE9 m_pSysMemSurface; 

【準備步驟4】

建立渲染貼圖的方法就不講了,各引擎不一樣。

//華麗的分隔線

準備工作完畢後,可以開始渲染。

【渲染步驟1】先将物體渲染到RenderTexture

【渲染步驟2】從RT拷貝到System Memory Texture

LPDIRECT3DSURFACE9 rt_surface= NULL;  

HRESULT hr;

hr = rt_texture->GetSurfaceLevel(0, &rt_surface);

//【渲染步驟2核心語句】

hr = dev9->GetRenderTargetData( rt_surface, m_pSysMemSurface );

rt_surface->Release();

//【渲染步驟3】從System Memory Texture到GDI位圖

D3D9SurfaceBlt2DIBPerPixelAlpha( m_hWnd, m_pSysMemSurface, m_hDC, m_pBitmapBits, 0xff);

//華麗的分隔線

D3D9SurfaceBlt2DIBPerPixelAlpha實作如下:

bool	D3D9SurfaceBlt2DIBPerPixelAlpha( HWND window, LPDIRECT3DSURFACE9 pSurface, HDC srcDC, void *pSrcDCBmp, BYTE wndAlpha )	//隻支援32bit的surface
{
	RECT	wr = {0};
	D3DSURFACE_DESC		d3dsurface_desc;
	D3DLOCKED_RECT		d3drt = {0};
	if ( !::GetClientRect(window, &wr ) )
	{
		return false;
	}
	POINT	dstPT = {wr.left, wr.top};
	if (!::ClientToScreen(window, &dstPT))
	{
		return false;
	}

	HRESULT hr = pSurface->LockRect( &d3drt, NULL,  0);//D3DLOCK_NOSYSLOCK|D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_READONLY );
	assert( SUCCEEDED(hr) && d3drt.pBits );
	if ( NULL == d3drt.pBits )	return	false;

	pSurface->GetDesc(&d3dsurface_desc);

	//【渲染步驟3核心語句】
	BltSurface32ToDIB32_SelfMulAlpha(pSrcDCBmp, d3drt.pBits, d3dsurface_desc.Width, d3dsurface_desc.Height, d3drt.Pitch);

	pSurface->UnlockRect();


	SIZE	dstSZ = {wr.right - wr.left, wr.bottom - wr.top};
	BLENDFUNCTION	bfc = { AC_SRC_OVER, 0, wndAlpha, AC_SRC_ALPHA };
	POINT	srcPT = {0, 0};

	//【渲染步驟4】這句就是通知視窗更新顯示内容,視窗必須擁有屬性WS_EX_LAYERED
	::UpdateLayeredWindow(window, 0, &dstPT, &dstSZ, 
		srcDC, &srcPT, 0, &bfc, ULW_ALPHA );

	return	true;
}
           

//華麗的分隔線

BltSurface32ToDIB32_SelfMulAlpha函數是用彙編實作的,有三個版本:MMX、SSE、SSE2,可以固定選擇一個,也可以根據目前cpu支援的指令集動态選擇一個。

這裡就貼一個SSE版本,該函數的具體實作細節,因為年代久遠,我也記不太清了,有問題歡迎咨詢讨論 [email protected]。

【渲染步驟3】的關鍵問題是GDI位圖需要“自乘alpha”,懶得碼字了,請參閱MSDN,

BLENDFUNCTION和UpdateLayeredWindow。

void	BltSurface32ToDIB32_SelfMulAlphaSSE( void *pDst, void *pSrc, unsigned int width, unsigned int height, unsigned int src_pitch )
{
	int	src_pitch_sub_dst_pitch;//src pointer對齊到下一行scanline,需要跳過多少位元組

	__asm
	{
		//取參數,判斷width和height是否有任一為0
		mov		eax, height		//eax = heigh
		mov		ebx, width
		mul		ebx				//width * height
		test	eax, eax		//影響ZF
		jz		end_pixel

		//常量指派
		mov		esi, pSrc
		mov		edi, pDst

		pcmpeqd mm5, mm5	//mm5 = 0xffffffff_ffffffff
		pxor mm7, mm7		//mm7 = 0x0
		psrld mm5, 8		//mm5 = 0x00ffffff_00ffffff

		//判斷pitch
		mov		edx, src_pitch
		shl		ebx, 2		//每個像素4個位元組, dst_pitch = width * 4
		sub		edx, ebx	//src_pitch - dst_pitch
		jnz		diff_pitch

//same_pitch:
		mov		ecx, eax
		mov		edx, 1		//how many lines,eax和edx構成2層循環
		and		ecx, 1		//一行上剩下多少個不成對的象素,same_pitch時就是(width*height & 1),diff_pitch時就是(width & 1)
		shr		eax, 1		//一行上主循環多少次,same_pitch時就是(width*height >> 1)diff_pitchh時就是(width >> 1)
		jmp		test_pair_pixel

diff_pitch:
		mov		src_pitch_sub_dst_pitch, edx	//src_pitch - dst_pitch
		mov		eax, width
		mov		edx, height	//how many lines,eax和edx構成2層循環
		mov		ecx, eax
		and		ecx, 1		//一行上剩下多少個不成對的象素,same_pitch時就是(width*height & 1),diff_pitch時就是(width & 1)
		shr		eax, 1		//一行上主循環多少次,same_pitch時就是(width*height >> 1)diff_pitchh時就是(width >> 1)
		mov		ebx, eax	//main loop count on every scanline
		jmp		test_pair_pixel

loop_line:
		mov		eax, ebx

loop_pair_pixel:
		movq mm0, [esi]		//mm0 = 0xaarrggbb_AARRGGBB
		movq mm4, mm5		//mm4 = mm5 = 0x00ffffff_00ffffff
		movq mm1, mm0		//mm1 = mm0 = 0xaarrggbb_AARRGGBB
		pandn  mm4, mm0		//儲存alpha, mm4 = 0xaa000000_AA000000

		punpcklbw mm0, mm7	//mm0 = 0x00AA_00RR_00GG_00BB
		punpckhbw mm1, mm7	//mm1 = 0x00aa_00rr_00gg_00bb

		pshufw	mm2, mm0, 0xff	//mm2 = 0x00AA_00AA_00AA_00AA
		pshufw	mm3, mm1, 0xff	//mm3 = 0x00aa_00aa_00aa_00aa

		pmullw mm0, mm2		//自乘alpha,字組相乘,取低16位
		pmullw mm1, mm3

		psrlw mm0, 8		//除以256
		psrlw mm1, 8

		packuswb mm0, mm0	//合并單個象素
		packuswb mm1, mm1

		punpckldq mm0, mm1	//将2個象素合并

		pand mm0, mm5		//恢複原始alpha
		por mm0, mm4

//put_pixel:
		MOVNTQ [edi], mm0

		add esi, 8
		add edi, 8

		dec eax
test_pair_pixel:
		jnz	loop_pair_pixel

//rest_line_pixel:
		jecxz	next_line	//scanline_rest_pixel不是0就是1

		movq mm0, [esi]		//mm0 = 0xaarrggbb_AARRGGBB
		movq mm4, mm5		//mm4 = mm5 = 0x00ffffff_00ffffff
		movq mm1, mm0		//mm1 = mm0 = 0xaarrggbb_AARRGGBB
		pandn  mm4, mm0		//儲存alpha, mm4 = 0xaa000000_AA000000

		punpcklbw mm0, mm7	//mm0 = 0x00AA_00RR_00GG_00BB
		punpckhbw mm1, mm7	//mm1 = 0x00aa_00rr_00gg_00bb

		pshufw	mm2, mm0, 0xff	//mm2 = 0x00AA_00AA_00AA_00AA
		pshufw	mm3, mm1, 0xff	//mm3 = 0x00aa_00aa_00aa_00aa

		pmullw mm0, mm2		//自乘alpha,字組相乘,取低16位
		pmullw mm1, mm3

		psrlw mm0, 8		//除以256
		psrlw mm1, 8

		packuswb mm0, mm0	//合并單個象素
		packuswb mm1, mm1

		punpckldq mm0, mm1	//将2個象素合并

		pand mm0, mm5		//恢複原始alpha
		por mm0, mm4

		movd	[edi], mm0

		add esi, 4
		add edi, 4

next_line:
		add	esi, src_pitch_sub_dst_pitch	//設定指針到下一個src行
		dec	edx
		jnz	loop_line

		emms

end_pixel:
	}
}
           
桌面透明視窗程式渲染

繼續閱讀