Direct-X学习笔记--公告板

Direct9学习之-------------------------公告板技术 一. 公告板的应用:             公告板可被用于场景中对细节要求不高的树木花草,也可用于玩家头顶的某些特效表现等。其好处自然是高效简洁。 二. 公告板基本原理: 公告板最基本的原理就是,绘制一个带贴图的矩形区域,由阿尔法混合使贴图看起来更逼真,并且保证每帧矩形都是正对摄像机,从而在效果上表现出贴图中的对象。 由于只绘制了一个矩形因此其绘制的性能很高。 三. 公告板 阅读详情

传说中的公告板,其实我之前一直以为公告板技术是一项很神秘的技术,但是学习了之后发现公告板技术其实很简单,但是却是一个非常“聪明的”技术,可以用非常小的开销,模拟出不错的效果。


一.简介

公告板技术(Billboard),是一种使用二维纹理模拟表现三维的技术。它可以用一种简单的方式来完成很多特别的效果。例如纹理的Alpha混合与动画 在一起使用,可以实现很多没有固定表面的效果,如烟雾、火焰、爆炸、能量盾、云彩等。在一定程度上来说,这些效果也可以由其他更真实的技术来实现,但公告板技术最吸引人的地方在于实现这些效果时所使用的系统资源极低。

公告板技术的原理其实比较简单,所谓的公告板,正如其名,就是一个二维的纹理平面,说白了就是一个矩形,然后使用Alpha混合,将纹理贴图贴在这个矩形上,使之看起来更加逼真,然后,再实时计算摄像机(或者观察点)的位置,使这个矩形绕着Y轴旋转,正对着摄像机的方向。这样,不管我们怎样在水平方向上旋转摄像机,这个矩形都会以“最佳形态”(面朝观察者)来展示自己,这样,这个物体看起来就像3维的啦。

注意:公告板本身没有使纹理看起来更像三维模型的能力,公告板只不过是通过Alpha透明,去除杂边,并且让它一直朝向我们罢了,所以使用公告板技术时,这个纹理本身必须看起来比较立体。


二.公告板的使用

其实公告板很简单,说白了就是两个三角形,组成一个矩形,然后在矩形上贴上一层纹理。我们先做到这一步,绘制一个地面,然后绘制一个二维的人物。

第一步就是定义灵活顶点格式,然后定义顶点缓冲区:
//------------绘制图形步骤1.定义灵活顶点格式
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX1)//坐标为经过变换的屏幕坐标,顶点的颜色


//------------绘制图形步骤2.根据上面定义的顶点格式,创建一个顶点的结构体
struct stVertex
{
	float _x, _y, _z;//位置坐标
	float _u, _v; //纹理坐标


	stVertex(float x, float y, float z, float u, float v) : _x(x), _y(y), _z(z), _u(u), _v(v){}
	stVertex(){}
};
接着,我们初始化顶点缓冲区,并且加载一份人物的纹理:
stVertex vertex[4];


	//顶点数据
	vertex[0] = stVertex(-100.0f,   0.0f, 0.0f, 0.0f, 1.0f);
	vertex[1] = stVertex(-100.0f,   700.0f, 0.0f, 0.0f, 0.0f);
	vertex[2] = stVertex( 100.0f,   0.0f, 0.0f, 1.0f, 1.0f);
	vertex[3] = stVertex( 100.0f,   700.0f, 0.0f, 1.0f, 0.0f);


	//----------绘制图形步骤5.为定点缓冲区分配内存,并将数组中的顶点值拷贝到顶点缓冲区中
	//通过设备指针来创建顶点缓冲区,用来存储顶点数据
	g_pDevice->CreateVertexBuffer(
		sizeof(vertex),//顶点缓冲区大小
		D3DUSAGE_WRITEONLY,//顶点缓冲区作用
		D3DFVF_CUSTOMVERTEX,//通知系统顶点格式
		D3DPOOL_MANAGED,//顶点缓冲区存储位置,此处表示由系统处理
		&g_pVB, //返回顶点缓冲区指针
		NULL //系统保留参数,NULL
		);


	void* pVertices = NULL;


	//锁定顶点缓冲区,向其中拷贝数据
	g_pVB->Lock(
		0, //锁定的偏移量
		sizeof(vertex),//锁定的大小
		&pVertices, //锁定之后存储空间
		0 //锁定的标识,0
		);


	//将数组中的内容拷贝到缓冲区中
	memcpy(pVertices, vertex, sizeof(vertex));


	//解锁
	g_pVB->Unlock();


	//创建纹理
	D3DXCreateTextureFromFile(g_pDevice, TEXT("kelisi.png"), &g_pCharacterTextrue);
然后,按照同样的方法,绘制一个矩形,作为地面,下面就先分别设置一下世界矩阵,然后绘制,看一下结果:


好吧,这真是“公告板”--我们的人物被钉在板子上了。为什么会出现这种情况?
答案是没有进行Alpha混合,Alpha混合就像我们之前GDI的透明遮罩法一样,可以使人物周围的边框去掉,不过前提是纹理图片要有Alpha通道。我们设置一下Alpha混合,在运行一下:
恩,这样就对了,额。。。请忽略抠图时留下的白边。。。
其实公告板所能表现的效果也就是这种效果啦,公告板真正做的不是提升纹理的立体程度,而是让纹理时刻对着我们,否则,如果不用公告板的话,我们将摄像机平移并转动一些角度之后,就会出现下面的情况:

可见,模型被压扁了,还有更狠的:


好了,下面才是公告板技术的核心:
实时获得观察矩阵,并且根据观察矩阵获得公告板矩阵,进行世界变换:
<span style="white-space:pre">	</span>//获得取景变换矩阵
	D3DXMATRIX matView;
	g_pCamera->CalculateViewMatrix(&matView);


	//根据取景变换矩阵构造公告板矩阵0
	D3DXMATRIX matBillboard;
	D3DXMatrixIdentity(&matBillboard);
	matBillboard._11 = matView._11;
	matBillboard._13 = matView._13;
	matBillboard._31 = matView._31;
	matBillboard._33 = matView._33;
	//还需要进行一次逆运算
	D3DXMatrixInverse(&matBillboard, NULL, &matBillboard);
	D3DXMatrixIdentity(&matCharacter);
	matCharacter = matBillboard * matCharacter;

这里的CalculateViewMatrix是之前摄像机类的计算观察矩阵的函数,可以通过这个函数获得观察矩阵,通过上面的公式,转化出公告板的世界矩阵。在绘制之前设置一下即可。这样,不管我们怎么转,得到的都是这样的效果,即人物会一直面向我们:


其实公告板的实现还有一种方式,就是通过观察点和眼睛的位置,计算向量,转动时,计算偏转角度,然后将模型旋转相应的角度,但是个人感觉这个好麻烦,还是算矩阵来的快....
// 通过当前观察方向来构造公告板矩阵
   D3DXMATRIX matBillboard;
   D3DXVECTOR3 vDir = vAt - vEye;
   if( vDir.x > 0.0f )
       D3DXMatrixRotationY( &matBillboard, -atanf(vDir.z/vDir.x)+D3DX_PI/2);
   else
       D3DXMatrixRotationY( &matBillboard, -atanf(vDir.z/vDir.x)-D3DX_PI/2);


三.完整的Demo

下面附上上图效果的完整代码:
// D3DDemo.cpp : 定义应用程序的入口点。
//


#include "stdafx.h"
#include "D3DDemo.h"
#include "DirectInput.h"
#include "Camera.h"




#define MAX_LOADSTRING 100




// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名


// 此代码模块中包含的函数的前向声明:
HWND                g_hWnd;
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


//---------改造3D窗口需要的内容------------
LPDIRECT3D9 g_pD3D = NULL; //D3D接口指针
LPDIRECT3DDEVICE9 g_pDevice = NULL; //D3D设备指针
CDirectInput* g_pDirectInput = NULL; //控制指针
CCamera*      g_pCamera      = NULL;    //摄像机指针


LPDIRECT3DTEXTURE9  g_pGroundTexture = NULL; //地面纹理
LPDIRECT3DTEXTURE9  g_pCharacterTextrue = NULL; //人物纹理


//------------绘制图形步骤1.定义灵活顶点格式
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX1)//坐标为经过变换的屏幕坐标,顶点的颜色


//------------绘制图形步骤2.根据上面定义的顶点格式,创建一个顶点的结构体
struct stVertex
{
	float _x, _y, _z;//位置坐标
	float _u, _v; //纹理坐标


	stVertex(float x, float y, float z, float u, float v) : _x(x), _y(y), _z(z), _u(u), _v(v){}
	stVertex(){}
};


//----------绘制图形步骤3.声明一个顶点缓冲区指针
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVBFloor = NULL;


//初始化顶点缓冲区
void initVB()
{
	//----------绘制图形步骤4.定义一个结构体数组用来给每个顶点赋值
	//数组中存储当前程序中顶点的数据



	stVertex vertex[4];


	//顶点数据
	vertex[0] = stVertex(-100.0f,   0.0f, 0.0f, 0.0f, 1.0f);
	vertex[1] = stVertex(-100.0f,   700.0f, 0.0f, 0.0f, 0.0f);
	vertex[2] = stVertex( 100.0f,   0.0f, 0.0f, 1.0f, 1.0f);
	vertex[3] = stVertex( 100.0f,   700.0f, 0.0f, 1.0f, 0.0f);


	//----------绘制图形步骤5.为定点缓冲区分配内存,并将数组中的顶点值拷贝到顶点缓冲区中
	//通过设备指针来创建顶点缓冲区,用来存储顶点数据
	g_pDevice->CreateVertexBuffer(
		sizeof(vertex),//顶点缓冲区大小
		D3DUSAGE_WRITEONLY,//顶点缓冲区作用
		D3DFVF_CUSTOMVERTEX,//通知系统顶点格式
		D3DPOOL_MANAGED,//顶点缓冲区存储位置,此处表示由系统处理
		&g_pVB, //返回顶点缓冲区指针
		NULL //系统保留参数,NULL
		);


	void* pVertices = NULL;


	//锁定顶点缓冲区,向其中拷贝数据
	g_pVB->Lock(
		0, //锁定的偏移量
		sizeof(vertex),//锁定的大小
		&pVertices, //锁定之后存储空间
		0 //锁定的标识,0
		);


	//将数组中的内容拷贝到缓冲区中
	memcpy(pVertices, vertex, sizeof(vertex));


	//解锁
	g_pVB->Unlock();


	//创建纹理
	D3DXCreateTextureFromFile(g_pDevice, TEXT("kelisi.png"), &g_pCharacterTextrue);


	//创建地板的缓冲区及纹理
	stVertex vertexFloor[4];


	vertexFloor[0] = stVertex(-4000.0f, 0.0f, -4000.0f, 0.0f, 1.0f);
	vertexFloor[1] = stVertex(-4000.0f, 0.0f,  4000.0f, 0.0f, 0.0f);
	vertexFloor[2] = stVertex( 4000.0f, 0.0f,  -4000.0f, 1.0f, 1.0f);
	vertexFloor[3] = stVertex( 4000.0f, 0.0f, 4000.0f, 1.0f, 0.0f);


	g_pDevice->CreateVertexBuffer(sizeof(vertexFloor), D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &g_pVBFloor, NULL);


	void* pVerticesFloor;
	g_pVBFloor->Lock(0, sizeof(vertexFloor), &pVerticesFloor, 0);
	memcpy(pVerticesFloor, vertexFloor, sizeof(vertexFloor));
	g_pVBFloor->Unlock();


	D3DXCreateTextureFromFile(g_pDevice, TEXT("floor.jpg"), &g_pGroundTexture);


}






void onCreatD3D()
{
	g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if (!g_pD3D)
		return;


	//检测硬件设备能力的方法
	/*D3DCAPS9 caps;
	ZeroMemory(&caps, sizeof(caps));
	g_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);*/


	//获得相关信息,屏幕大小,像素点属性
	D3DDISPLAYMODE d3ddm;
	ZeroMemory(&d3ddm, sizeof(d3ddm));


	g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);




	//设置全屏模式
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	/*d3dpp.Windowed = false;
	d3dpp.BackBufferWidth = d3ddm.Width;
	d3dpp.BackBufferHeight = d3ddm.Height;*/


	d3dpp.Windowed = true;
	d3dpp.BackBufferFormat = d3ddm.Format;
	d3dpp.BackBufferCount = 1;


	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;//交换后原缓冲区数据丢弃


	//是否开启自动深度模板缓冲
	d3dpp.EnableAutoDepthStencil = true;
	//当前自动深度模板缓冲的格式
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;//每个像素点有16位的存储空间,存储离摄像机的距离



	g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pDevice);


	if (!g_pDevice)
		return;


	//设置渲染状态,设置启用深度值
	g_pDevice->SetRenderState(D3DRS_ZENABLE, true);


	//设置渲染状态,关闭灯光
	g_pDevice->SetRenderState(D3DRS_LIGHTING, false);


	//设置渲染状态,裁剪模式
	g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);


	//g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE) ;


}

void CreateCamera()
{
	g_pCamera = new CCamera(g_pDevice);
	g_pCamera->SetCameraPosition(&D3DXVECTOR3(0.0f, 200.0f, -1500.0f));
	g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 200.0f, 0.0f));
	g_pCamera->SetViewMatrix();
	g_pCamera->SetProjectionMartix();
}

void onInit()
{
	//初始化D3D
	onCreatD3D();


	//初始化顶点缓冲区
	initVB();


	//创建摄像机
	CreateCamera();

}


void onDestroy()
{
	if (!g_pDevice)
		g_pDevice->Release();
	g_pDevice = NULL;
}


void onLogic(float fElapsedTime)
{
	//使用DirectInput类读取数据
	g_pDirectInput->GetInput();


	// 沿摄像机各分量移动视角
	if (g_pDirectInput->IsKeyDown(DIK_A))  g_pCamera->MoveAlongRightVec(-10.0f);
	if (g_pDirectInput->IsKeyDown(DIK_D))  g_pCamera->MoveAlongRightVec( 10.0f);
	if (g_pDirectInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 10.0f);
	if (g_pDirectInput->IsKeyDown(DIK_S))  g_pCamera->MoveAlongLookVec(-10.0f);
	if (g_pDirectInput->IsKeyDown(DIK_I))  g_pCamera->MoveAlongUpVec( 10.0f);
	if (g_pDirectInput->IsKeyDown(DIK_K))  g_pCamera->MoveAlongUpVec(-10.0f);


	//沿摄像机各分量旋转视角
	if (g_pDirectInput->IsKeyDown(DIK_LEFT))  g_pCamera->RotationUpVec(-0.003f);
	if (g_pDirectInput->IsKeyDown(DIK_RIGHT))  g_pCamera->RotationUpVec( 0.003f);
	if (g_pDirectInput->IsKeyDown(DIK_UP))  g_pCamera->RotationRightVec(-0.003f);
	if (g_pDirectInput->IsKeyDown(DIK_DOWN))  g_pCamera->RotationRightVec( 0.003f);
	if (g_pDirectInput->IsKeyDown(DIK_J)) g_pCamera->RotationLookVec(-0.001f);
	if (g_pDirectInput->IsKeyDown(DIK_L)) g_pCamera->RotationLookVec( 0.001f);


	////鼠标控制右向量和上向量的旋转
	//g_pCamera->RotationUpVec(g_pDirectInput->MouseDX()* 0.001f);
	//g_pCamera->RotationRightVec(g_pDirectInput->MouseDY() * 0.001f);


	//鼠标滚轮控制观察点收缩操作
	static FLOAT fPosZ=0.0f;
	fPosZ += g_pDirectInput->MouseDZ()*0.03f;


	//计算并设置取景变换矩阵
	D3DXMATRIX matView;
	g_pCamera->CalculateViewMatrix(&matView);
	g_pDevice->SetTransform(D3DTS_VIEW, &matView);

}


void onRender(float fElasedTime)
{
	//前两个参数是0和NULL时,清空整个游戏窗口的内容(清的是后台)
	//第三个是清除的对象:前面表示清除颜色缓冲区,后面表示清除深度缓冲区,D3DCLEAR_STENCIL清空模板缓冲区
	g_pDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,100,100), 1.0f, 0);


	g_pDevice->BeginScene();


	//设置世界矩阵
	D3DXMATRIX matCharacter, matWorld;  
	D3DXMatrixTranslation(&matWorld, 0.0f, 0.0f, 0.0f); 
	D3DXMatrixTranslation(&matCharacter, 0.0f, 0.0f, 0.0f);

	//获得取景变换矩阵
	D3DXMATRIX matView;
	g_pCamera->CalculateViewMatrix(&matView);


	//根据取景变换矩阵构造公告板矩阵0
	D3DXMATRIX matBillboard;
	D3DXMatrixIdentity(&matBillboard);
	matBillboard._11 = matView._11;
	matBillboard._13 = matView._13;
	matBillboard._31 = matView._31;
	matBillboard._33 = matView._33;
	//还需要进行一次逆运算
	D3DXMatrixInverse(&matBillboard, NULL, &matBillboard);
	D3DXMatrixIdentity(&matCharacter);
	matCharacter = matBillboard * matCharacter;



	////----------绘制图形步骤6.设置数据源,设置灵活顶点格式,绘制图元




	//绘制地面
	//设置数据流来源
	g_pDevice->SetStreamSource(
		0, //数据流管道号(0-15)
		g_pVBFloor, //数据来源
		0, //数据流偏移量
		sizeof(stVertex)//每个数据的字节数大小
		);


	//通知系统数据格式,以便解析数据
	g_pDevice->SetFVF(D3DFVF_CUSTOMVERTEX);


	g_pDevice->SetTransform(D3DTS_WORLD, &matWorld);


	//设置纹理
	g_pDevice->SetTexture(0, g_pGroundTexture);


	g_pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);


	//绘制人物


	//设置数据流来源
	g_pDevice->SetStreamSource(
		0, //数据流管道号(0-15)
		g_pVB, //数据来源
		0, //数据流偏移量
		sizeof(stVertex)//每个数据的字节数大小
		);


	//通知系统数据格式,以便解析数据
	g_pDevice->SetFVF(D3DFVF_CUSTOMVERTEX);

	g_pDevice->SetTransform(D3DTS_WORLD, &matCharacter);  

	//设置纹理
	g_pDevice->SetTexture(0, g_pCharacterTextrue);


	//开启Alpha Blend
	g_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
	g_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	g_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);




	//绘制图元 
	g_pDevice->DrawPrimitive(
		D3DPT_TRIANGLESTRIP,    //三角形列
		0, //起始点编号
		2 //图元数量
		);


	g_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);


	g_pDevice->EndScene();




	g_pDevice->Present(NULL, NULL, NULL, NULL);
}




int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
					   _In_opt_ HINSTANCE hPrevInstance,
					   _In_ LPTSTR    lpCmdLine,
					   _In_ int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);


	// TODO: 在此放置代码。
	MSG msg;
	HACCEL hAccelTable;


	// 初始化全局字符串
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_D3DDEMO, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);


	// 执行应用程序初始化:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}


	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_D3DDEMO));





	ZeroMemory(&msg, sizeof(msg));
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			static DWORD dwTime = timeGetTime();
			DWORD dwCurrentTime = timeGetTime();
			DWORD dwElapsedTime = dwCurrentTime - dwTime;
			float fElapsedTime = dwElapsedTime * 0.001f;


			//------------渲染和逻辑部分代码----------
			onLogic(fElapsedTime);
			onRender(fElapsedTime);
			//-----------------------------------------
			if (dwElapsedTime < 1000 / 60)
			{
				Sleep(1000/ 60 - dwElapsedTime);
			}
			dwTime = dwCurrentTime;
		}
	}


	onDestroy();
	return (int) msg.wParam;
}






//
//  函数: MyRegisterClass()
//
//  目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;


	wcex.cbSize = sizeof(WNDCLASSEX);


	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc= WndProc;
	wcex.cbClsExtra= 0;
	wcex.cbWndExtra= 0;
	wcex.hInstance= hInstance;
	wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_D3DDEMO));
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName= MAKEINTRESOURCE(IDC_D3DDEMO);
	wcex.lpszClassName= szWindowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));


	return RegisterClassEx(&wcex);
}


//
//   函数: InitInstance(HINSTANCE, int)
//
//   目的: 保存实例句柄并创建主窗口
//
//   注释:
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{


	hInst = hInstance; // 将实例句柄存储在全局变量中


	g_hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);


	if (!g_hWnd)
	{
		return FALSE;
	}


	//初始化DirectInput类
	g_pDirectInput = new CDirectInput();
	g_pDirectInput->Init(g_hWnd, hInst, DISCL_FOREGROUND|DISCL_NONEXCLUSIVE, DISCL_FOREGROUND|DISCL_NONEXCLUSIVE);


	SetMenu(g_hWnd, NULL);
	ShowWindow(g_hWnd, nCmdShow);
	UpdateWindow(g_hWnd);


	onInit();


	return TRUE;
}


//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的: 处理主窗口的消息。
//
//  WM_COMMAND - 处理应用程序菜单
//  WM_PAINT - 绘制主窗口
//  WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND g_hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_KEYDOWN:
		if (wParam == VK_ESCAPE)
			PostQuitMessage(0);
		break;
	case WM_CLOSE:
		DestroyWindow(g_hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(g_hWnd, message, wParam, lParam);
	}
	return 0;
}


四.扩充

1.公告板其实更适合那种方向不敏感的东东,比如一棵树,从哪个方向看都差不多的,正好适合公告板。
2.据说有更加高级的公告板技术,十字交叉公告板:所谓十字交叉公告板既是在公告板的基础上再将矩形旋转90度,使得两个公告板交叉。十字交叉公告板比单纯公告板表现效果更好。










昇腾310 DMA内存分配实战:从dma_alloc_attrs到设备一致性优化 本文深入探讨了昇腾310芯片上DMA内存分配的核心技术与优化实践。通过剖析dma_alloc_attrs函数的三种分配路径,详细解读了设备一致性内存、CMA分配及IOMMU映射等关键机制。文章结合实战代码,提供了从设备树配置到缓存属性调优的完整方案,旨在帮助开发者解决数据传输中的一致性问题并提升DMA性能。 阅读详情

相关推荐

Python异步任务队列实战:Celery与Redis的黄金组合配置指南

在分布式系统架构中,异步任务队列是解耦耗时操作、提升系统响应能力的关键技术。其核心原理是通过消息代理将任务生产者与消费者分离,实现任务的异步执行与调度。这一架构为系统带来了可扩展性、可靠性和资源利用率提升等显著技术价值,广泛应用于邮件发送、文件处理、数据清洗等后台作业场景。在Python生态中,Celery作为分布式任务队列的事实标准,配合高性能键值数据库Redis作为消息代理和结果后端,构成了处理异步任务和定时任务的黄金组合。本文聚焦于Celery与Redis的实战配置,涵盖从环境搭建、任务定义到生产环境

weixin_33910460的博客 482

公告板技术

       在看完了毛星云的《实时渲染提炼总结》9.6-9.7与冯乐乐的《Unity Shader入门精要》后,我对公告板技术有了大概的认识,在这里总结一下。       公告板,英文名Billboarding,是一项让着色四边形根据视角方向来旋转的技术,这样看起来似乎物体总是面向观察者。与alpha纹理和动画技术结合,可用公告板创建许多不具有平滑实体表面的现象,比如常见的烟雾,云朵,爆炸效果,...

haodada1226的博客 1442

VC++ MFC实现文字滚动控件:GDI绘图与定时器驱动详解

在Windows桌面应用开发中,动态UI效果是提升用户体验的关键。其核心原理通常基于消息循环与图形设备接口(GDI)绘图,通过定时器周期性触发界面重绘来实现动画。这种技术方案在信息展示、状态监控等场景具有重要价值,能够有效吸引用户注意力并在有限空间内传递更多信息。本文聚焦于一个经典应用——文字滚动效果,深入剖析如何结合GDI文本绘制与Windows定时器消息,在VC++/MFC环境下构建一个高性能、可复用的自定义滚动文本控件。文中不仅详解了自绘与定时器驱动的闭环逻辑,还针对常见的闪烁、性能问题提供了双缓冲等

333

【游戏开发】关于Direct X(一)

1.DirectX的定义 首先,我们必须了解DirectX的定义。 DirectX,(Direct eXtension,简称DX)是由微软公司创建的多媒体编程接口。由C++编程语言实现,遵循COM。被广泛使用于Microsoft Windows、Microsoft Xbox和Microsoft Xbox 360电子游戏开发,并且只能支持这些平台。最新版本为DirectX 11,创建在最新的Wi...

茗染云华 荆挑赋樱 4361

GPU Driven地形渲染:从Compute Shader剔除到间接绘制完整实现

在实时渲染中,CPU与GPU的分工协作直接影响帧率与场景复杂度。传统渲染流程中,CPU需要逐帧处理视锥剔除、LOD选择等任务,当场景规模增大时,Draw Call数量成为明显瓶颈。GPU Driven渲染通过将决策逻辑下沉至GPU端,利用Compute Shader并行计算,结合间接绘制(Indirect Drawing)技术,让GPU自主生成绘制命令,大幅减少CPU开销。这种架构广泛应用于地形渲染、大规模植被、粒子系统等场景。本文以一个可运行的OpenGL 4.5示例为核心,剖析GPU Driven地形渲

weixin_33597080的博客 259

【C++】3D游戏公告板技术程序源码by浅墨

浅墨出品,零资源分下载,分享精神至上~ 源码的配套博文是 《 【Visual C++】游戏开发五十四 浅墨DirectX教程二十一 视觉上的诡计:公告板(Billboard)技术》 ,文章地址http://blog.youkuaiyun.com/poem_qianmo/article/details/13278851, 点击Release文件夹下的exe文件可以直接看到运行效果,运行需要DirectX运行库的支持。报缺少D3D的DLL系列错误的童鞋们请google/百度一下 “DirectX 9.0c runtime”,下载并装个最新版的。 报缺少MVCR100D.Dll错误的朋友们去下一个安装就可以了,或者直接点击sln打开工程再次编译一次。 背景音乐来自火影忍者疾风传 用键盘上W,A,S,D,I,J,K,L,↑,↓,←,→12个键加上鼠标在美丽的三维空间中翱翔。包括了Direct3D初始化,DirectInput输入处理,顶点缓存,文字输出,颜色,纹理贴图,四大变换,三维天空模拟,粒子系统等等知识。 另外,关于雪花粒子效果提醒大家一下,可在SnowParticleClass.h中的PARTICLE_NUMBER宏中改变雪花粒子数量,默认粒子数量为3000,1G显存的显卡取10万粒子数量帧数就只有8帧了。所以要自己改雪花粒子数量的话请根据自己的显卡性能酌情选择,如果你取个非常大的50万粒子数量,显卡吃不消烧了可别怪我- - 如果是想调试并运行源代码,但是报错了,请去下载最新版DirectX SDK并进行DirectX开发环境的配置。 编写环境:VS2010 我的博客地址是http://blog.youkuaiyun.com/zhmxy555,源码结合配套文章一起看效果更佳。 希望能和大家一起交流,共同学习,共同进步。

创业第一步:行动脚本与关键自检

摘要:创业第一步的行动脚本与关键自检 本文系统阐述了如何将创业构想转化为可执行落地的"第一步"。核心观点指出,真正的创业起点不是注册公司或开发产品,而是通过"行动脚本"将想法拆解为可操作步骤,并建立"关键自检"机制持续验证方向。 主要内容包括: 行动脚本的五大特质:可操作、有时效、可迭代、有自检点、能带动协作 七大操作流程:从市场洞察到产品原型、用户获取、团队组建、商业模式验证等 关键自检原则:数据驱动、快速反馈、闭环迭代 实战案例解析:校园电动滑

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 478

Python爬虫(一)京东商品价格及详情页抓取

这是一个以http://item.jd.com/2957726.html为例的单网页抓取案例 import urllib.request; #载入urllib.request,用于获取页面html源代码 from pandas import Series; #载入series包 from pandas import DataFrame; #载入dataframe包 from bs4 impo

Data Proc 1万+

【Visual C++】游戏开发五十四 浅墨DirectX教程二十一 视觉的诡计:公告板(Billboard)技术...

本系列文章由zhmxy555(毛星云)编写,转载请注明出处。 作者:毛星云(浅墨) 微博:@浅墨_毛星云邮箱: happylifemxy@163.comI'm back~,这段时间大家久等了~现在大家看到的,就是【Visual C++】游戏开发系列文章第三季中作为正式回归的第一篇文章了。在这篇文章中,我们一起详细探讨了游戏编程中公告板技术的方方面面,有“深入”的过程分析,...

weixin_33805557的博客 489

全志T113s3主线linux开发(0)--基础准备

嵌入式Linux设备的启动大概是三步:1)启动芯片+内存:常见的是Uboot,不过全志的Uboot主线对SPI NAND 不太友好,用TF卡就方便。对于T113S芯片,推荐使用Awboot,简陋但快速。开发阶段用这个就方便好多,对SPINAND支持也方便。2)启动linux内核:主线内核以下通篇都用6.13以上的内核。对比tina5.0还在用5.4 ,主要是性能、安全上有变化。6.0以上内核在devicetree的编写具体格式上有了变化,网上大量tina5.0和5.4内核的编写方式不适用。

2301_79234138的博客 2247

LT8711/LT8712 Type-C转HDMI设计方案|替代LT8711/LT8712芯片|GSV2201可完全替代兼容LT8711/LT8712

LT8711UX是一款高性能C/DP1.2型至HDMI2.0转换器,用于将USB Type-C源或DP1.2源连接到HDMI2.0接收器。该LT8711UX集成了一个DP1.2兼容的接收器,和一个HDMI2.0兼容的发射器。此外,还包括两个CC控制器,用于CC通信,以实现DP Alt模式和功率传输功能,一个用于上游Type-C端口和另一个用于下游端口。

weixin_63518914的博客 1639

校园网上公告板(源码+开题)

首先,通过需求分析明确系统的功能和特点,包括用户角色设定(如普通用户、版主等)、公告类型的划分(如通知公告、学术活动、文化交流等)、公告信息的发布与管理等。其次,在系统设计阶段,将采用合适的架构和技术,设计数据库结构、系统界面和用户交互流程,确保系统的稳定性和易用性。因此,开发一个功能全面、操作简便、信息更新及时的校园网上公告板系统,对于提升校园信息化建设水平、加强师生间的沟通与交流、促进校园文化的传播等方面都具有重要的意义。同时,公告板系统的互动性特点有助于加强师生间的互动,促进校园内部的和谐氛围。

qh602的博客 1231

SAP HANA 的一个王炸技能,Smart Data Access

SAP HANA Cloud近期推出Knowledge Graph Engine(KGE),支持RDF三元组存储和SPARQL查询,与向量引擎等组件协同实现混合检索。同时文章介绍了HANA的Smart Data Access(SDA)功能,它通过虚拟表技术实现跨系统联邦查询,支持30+种数据源(不包括PostgreSQL),适用于混合云场景。SDA通过谓词下推优化性能,典型应用包括冷热数据分层和跨系统报表分析。对于PostgreSQL连接需求,建议使用HANA的Smart Data Integration方

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 496

公告板

Direct9学习之 - 公告板技术 发布者: 新撰组 | 发布时间: 2013-11-26 21:53| 评论数: 0 游戏程序 平台类型: PC/兼容机  程序设计: 客户端 3D图形  编程语言: C/C++  引擎/SDK: DirectX 

linuxheik的专栏 534

SAE ARP 4761A内容详解

SAE ARP 4761A主要为民用航空系统及设备的安全评估提供指南,帮助制造商和设计者满足FAA的FAR 25.1309等适航标准的要求。该文档旨在为航空器和航空系统设计中涉及的安全评估流程提供一个结构化的框架,并结合一系列工具和方法,如故障树分析(FTA)、依赖图分析(DD)、马尔可夫分析(MA)等,来系统地评估潜在的安全隐患。这一标准不仅适用于大型航空器的设计和评估,还可以应用于各种类型的飞行系统,如无人驾驶飞行器(UAV)和新型的电动垂直起降飞机(eVTOL)。

qq_41854911的博客 3147

【系统架构设计师】2025年下半年真题论文回忆版及写作要点

本文摘要围绕云原生数据库、性能测试、无服务器架构和秒杀场景等关键技术进行论述。云原生数据库通过容器化、微服务和自动化运维实现弹性扩展;性能测试需要规划测试场景并关注响应时间等关键指标;无服务器架构以事件驱动为核心,简化开发流程;秒杀场景需组合运用扩容、缓存、限流等技术应对高并发。这些技术方案为构建现代高可用系统提供了重要参考。

数据知道的博客 6156

DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果

上一章我们知道了如何使用几何着色器将顶点通过流输出阶段输出到绑定的顶点缓冲区。接下来我们继续利用它来实现一些新的效果,在这一章,你将了解: 1. 实现公告板效果 2. Alpha-To-Coverage 3. 对GPU资源进行读/写操作 4. 纹理数组 5. 实现雾效 DirectX11 With Windows SDK完整目录 Github项目源码 目录 实现雾效 HLSL...

X_Jun的博客 1066

【WorkBuddy专栏27】WorkBuddy 和 CodeBuddy 到底什么关系——一篇文章终结所有混淆

WorkBuddy 和 CodeBuddy 到底什么关系——一篇文章终结所有混淆

AI人人懂 2605

【游戏程序设计】Direct 3D 公告板技术

运行结果: 源代码: #include &lt;d3d9.h&gt; #include &lt;d3dx9.h&gt; #include &lt;tchar.h&gt; #include "CameraClass.h" #include "DirectInputClass.h" #include "SkyBoxClass.h" #include "SnowParticleClass

辣条不爱辣的博客 485

C++软件异常分析概述

本文将详细讲述Windows系统中软件异常的分类以及常用的排查方法,给大家提供一个借鉴与参考。

dvlinker的技术专栏 4万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Direct3D的血条制作问题

刚好在做人物的血条公告板,发现一个诡异的地方,那就是,一开始血条一直在人物的上方,之后死亡了,换位置了就不在人物上方了。 之后发现他是按照我的人物的移动半径来旋转血条的。但是这不是我想要的,因为血条永远都要在人物上方嘛。因为以前学过opengl的关系,所以后来想到了是要将矩阵的旋转和平移分开写,要先旋转在平移,因为在原点旋转了,那就不会再影响到后面的平移了。 这里使用DX封装好的函数:

梧桐下的四叶草 447

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

深度剖析flint-oQ3e量化原理:数据驱动混合精度与3/6/8-bit按层敏感度分配的底层逻辑

flint-oQ3e 是面向 Apple Silicon 的 **Flint 40B MoE 大模型混合精度量化版本**:它用 oMLX 的 oQ3e 方案,按"层敏感度"把不同权重分别压到 3/6/8 bit,把 79.6 GB 的 bf16 模型压缩到 18.5 GB(约 3.71 bpw),让 24–32 GB 内存的 Mac 也能跑这款每 token 激活约 2.5B 参数的推理模型。本文

gitblog_01002的博客 661

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

D3D加载纹理的步骤(1)

D3D加载纹理的一般步骤: 1.

喜欢吃一口烤肉的啵啵 1830

基于Visual C#的DirectX开发实例 ——二维显示

七、二维显示在DirectX中大部分二维显示都由Sprite来完成,如动画、公告板、二维文字等。在DirectX中有专门的Sprite类用于简化sprite的绘制和处理,下面来看一下Sprite类的一些方法:方法定义说明Beginpublic void Begin(    SpriteFlags flags);

肖泽云 6986

深入解析EDMA3中断机制与事件队列:嵌入式DMA性能优化实战

在嵌入式系统与高性能计算中,直接内存访问(DMA)技术是提升数据传输效率、释放CPU算力的核心机制。其原理在于由专用控制器接管CPU的数据搬运任务,实现内存与外设间的高速、后台数据传输。这项技术的核心价值在于通过硬件并行化,显著降低系统延迟、提升整体吞吐量,广泛应用于音视频流处理、网络数据包转发和存储系统等场景。本文将聚焦于其高级形态——增强型DMA控制器,深入剖析其中断与事件队列两大关键子系统。中断机制作为CPU与DMA控制器协同的“信号灯”,其**TCC(传输完成码)**与**影子区域**设计,实现了灵

weixin_33709590的博客 388
上一篇: 《Effictive C++》读书笔记(一)
下一篇: 《游戏引擎架构》读书笔记(三)
puppet_master
puppet_master 领域专家: C/C++技术领域 领域专家: C/C++技术领域
博客等级 码龄12年 2695粉丝 161原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值