1. 程式人生 > >桌面透明視窗程式渲染

桌面透明視窗程式渲染

        市面上基本所有的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:
	}
}


相關推薦

桌面透明視窗程式渲染

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

vc++開發簡單的半透明視窗程式示例

環境:xp  vc6.0 1: 開啟vc,依次點選 檔案 -> 新建 -> 工程 -> MFC AppWizard {exe} ,填寫工程名test  -> 確定 -> 基本對話方塊 -> 完成 2: 開啟 Test2Dlg.cpp 找

C# 實現視窗程式winform像QQ一樣靠近桌面邊緣自動隱藏視窗

實現原理:   實現這個功能的原理步驟如下:     1、判斷窗體程式是否靠近桌面邊緣;     2、獲取桌面螢幕大小與窗體程式大小;     3、把窗體程式顯示在桌面以外隱藏起來,預留部分窗體方便使用者拉出程式;     4、判斷滑鼠是否在窗體程式上,在就靠邊顯示整個窗體程式,不在就隱藏顯示,並調

第2篇 Qt5基礎(二)編寫Qt多視窗程式

  1、通過程式碼來設定按鈕的中文文字會覆蓋以前在設計模式設定的文字,(另外,如果大家以前學過Qt 4,那麼現在可能會激動地發現不用在使用setCodecForTr()等函式就可以直接顯示中文了)不過,在程式碼中直接使用中文字不是一個好的習慣,建議在編寫程式時使用英文,當程式完成後使用

java 使用html寫UI 做winform win桌面客戶端程式(一)

大家好,今天給大家帶來使用java+html寫winform  win客戶端桌面程式的教程。 在讀本文之前你首先要能接受: 1   客戶端基於java+html所以 軟體包中需要包含 jre和 cef(chrome核心)的dll,軟體包比較大。 2 &

淺談Windows SDK視窗程式的訊息機制

Windows系統的訊息機制 一個庫函式(比如fopen),最終會呼叫作業系統的API來實現其功能,在Windows中,不僅庫函式最終會呼叫系統函式,系統函式反過來也會呼叫使用者函式,這種機制就是通過訊息來實現的。 我們假設程式發生了一項滑鼠點選“關閉”按鈕的操作,系統會發現這次操作,並將這次操作包裝成訊息

微信小程式 渲染層網路層錯誤

在我們載入圖片的時候往往會出現一些小問題,例如:明明圖片載入成功了,但是控制檯還是會報 渲染層網路層錯誤,找了很多地方,也發現不出來問題。 出現這種情況的原因在於,頁面顯示載入的時候image裡面的值是空的,從後臺拿值是需要一定的額時間的,在這個時間內image的src是空的,所以報錯。解決方法

QT從console程式到到視窗程式之實踐

我們都知道,在QT中基於Qobject的視窗有兩類,QWidget和QWindow,當然,這兩類下又有QDialog,QWidget,QMainWindow,QSplashScreen,QMidSubWindow,QDesktopWidget五個類。 這裡,我們來看看QMainWindo

C#實戰025:控制檯呼叫Forms視窗程式

   今天寫socket檔案傳輸時要用到選擇檔案功能,那就就要在控制檯狀態下彈相應的視窗,這樣才能方便選擇自己所需要的檔案,但是用控制檯呼叫Froms視窗很少有人用,找半天都找不到方法(比如說我們要開啟選擇檔案的視窗)。 在Windows窗體程式中藥呼叫窗體程式只要直接將工具

VS2010 MFC視窗程式 pugixml讀寫XML

       為了用VC++讀寫XML檔案前後弄了差不多5天了,試過微軟自家的MSXML和libxml2庫,介紹MSXML的相關書籍和CSDN部落格裡的文章基本全是XP時代的MSXML4.dll,WINDOWS 7 system32目錄只有MSXML3.d

WinCE桌面新增應用程式的快捷方式及自啟動

  將可執行程式MyApp.exe放入FAT分割槽Flash中,在桌面建立其快捷方式: (1) 建立KinglyApp.lnk 快捷方式檔案   使用桌面Windows 自帶的記事本建立,儲存時將字尾名改為lnk即可。.lnk 檔案其實是一個文字檔案,它包含用於連結目標的命令列以及命令列的長

MFC視窗程式退出訊息的響應

1.MFC三個結束訊息的區別 WM_CLOSE: 在系統選單裡選擇了“關閉”或者點選了視窗右上角的“X”按鈕,你的視窗過程就會收到WM_CLOSE。DefWindowProc對 WM_CLOSE的處理是呼叫DestroyWindow。當然,你可以不讓DefWin

Qt5.11 實現透明視窗與滑鼠穿透的方法

實現透明視窗很簡單,只要重寫paintEvent函式,設定QPen與QBrush變數的RGBA中A的值為小於255的值即可,A的全稱為Alpha,在顏色中表示透明度,數值範圍與RGB相同,255為完全不透明,0表示完全透明 滑鼠穿透我查了好久,網上一直在說Qt沒有自帶的方法

Windows API程式設計——最簡單的視窗程式框架示例

 用Windows API實現一個自定義視窗也需要這麼一大堆最基本的程式框架: #include <windows.h> static LPCTSTR lpszAppName = "windows API 視窗示例";//視窗名稱 HBRUSH hBlueB

C# 視窗程式除錯輸出(非中斷)

非中斷模式下的除錯 首先設定一下IDE 在程式中引用 using System.Diagnostics; 呼叫方式: Debug.WriteLine(); Debug.WriteLineIf();//兩個引數,當第一個引數為true時,第二個引數的除錯資訊會顯示出來。 Tra

10 訊息佇列與一個簡單的視窗程式

訊息:當我們點選滑鼠的時候,或者當我們按下鍵盤的時候,作業系統都要把這些動作記錄下來,儲存到一個結構體中,這個結構體就是訊息; 訊息佇列:每個執行緒只有一個訊息佇列;訊息對列與執行緒相關的;訊息佇列就是一堆連結串列; 視窗與執行緒的關係:一個執行緒可以有多個視窗,一個視窗指對應一個執行緒;

C++呼叫API初步建立Windows視窗程式

首先在進行介紹前,先介紹一下api,個人理解,api是在windows.h中提供的一些封裝好的函式。 建立一個視窗程式的一般步驟是先註冊一個視窗類名,然後再建立一個視窗,傳遞資訊進行處理(視窗的操作都是通過資訊傳遞來實現的) 下面介紹幾個要用到api函式 RegistetClass()&

web應用打包成桌面可執行程式

本教程旨在幫助開發人員把web應用程式打包成一個可執行的桌面應用程式,並生成一個安裝包setup.exe,直接安裝到本地電腦,方便使用者使用。 1、背景 開發web專案時,大多數選擇主流的chrome等瀏覽器作為開發除錯工具,但終端使用者可能要求相容IE等不常用的瀏覽器,此

使用vs2017編寫不帶console視窗程式時,使用printf列印輸出資訊

在使用vs2017編寫不帶console視窗程式時,有時需要列印一些資訊,來方便知道一些比如變數的值等,以往都是笨笨的使用messagebox彈出,但是當需要看多個值得時候,這種方法顯然就很難受了,也想過在vs中列印,也感覺不舒服,最後在網上看到可以用一個使用console視窗列印,在程式中照常使用

如何編寫簡單的應用window視窗程式

#include<windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInst,LPSTR lpszCmdL