Direct3D融合技术详解及物体透明效果的实现

Direct3D融合技术详解及物体透明效果的实现

前言

融合技术,将当前要进行光栅化的像素的颜色与先前已经光栅化并处于同一位置的像素的颜色进行合成,即:将正在处理的图元颜色值与存储在后台缓存中的像素颜色值进行合成,利用该技术,可以获得多种效果,比如透明效果。

相关知识说明

1.融合方程

当我们处于背景图和茶壶三角形单元进行光栅化计算时,我们需要将计算得到的茶壶的像素颜色和背景图的像素颜色进行合成,以使得背景图透过茶壶得以显示。这种将当前计算得到的像素(源像素)颜色值与先前计算得出的像素(目标像素)颜色值合成的做法就是融合技术。
融合方程: O u t p u t P i x e l = S o u r c e P i x e l S o u r c e B l e n d F a c t o r + D e s t P i x e l D e s t B l e n d F a c t o r OutputPixel = SourcePixel*SourceBlendFactor + DestPixel*DestBlendFactor
简单来说:融合后的颜色值 = 当前需要光栅化像素的颜色值 * 源融合因子 + 处于后台缓存的像素的颜色值*目标融合因子

OutputPixel:融合后的颜色值
SourcePixel:当前计算得到的、用于与后台缓存中对应像素进行融合的像素颜色值
SourceBlendFactor :源融合因子,指定了源像素的颜色值在融合中所占的比例,在[0,1]区间内
DestPixel :当前处于后台缓存中的像素颜色值
DestBlendFactor:目标融合因子,指定了目标像素的颜色值在融合中所占的比例,在[0,1]区间内

因此,可通过修改源融合因子和目标融合因子,修改源像素和目标像素的颜色值,以实现不同的效果
注意:融合计算的开销比较大,当绘制完需要进行融合的几何体后,一定要禁用融合

Device->SetRenderState(D3DRS_ALPHABLENDENABLE,true); // 启用融合运算
Device->SetRenderState(D3DRS_ALPHABLENDENABLE,false); // 禁用融合运算

2.融合方式和融合系数整理

通过设定融合因子和目标融合因子,我们可以创建一系列不同的效果。可通过设定D3DRS_SRCBLENDD3DRS_DESTBLEND的值来设置源融合因子以及目标融合因子

Device->SetRenderState(D3DRS_SRCBLEND,Source); // 设置源融合因子
Device->SetRenderState(D3DRS_DESTBLEND,Destination); // 设置目标融合因子

融合方式及操作符:
D3DBLENDOP_ADD 源像素计算结果与目标像素的计算结果相加,即【最终结果】=【源】+【目标】
D3DBLENDOP_SUBTRACT 源像素计算结果与目标像素的计算结果相减,即【最终结果】=【源】-【目标】
D3DBLENDOP_REVSUBTRACT 目标像素的计算结果减去源像素计算结果,即【最终结果】=【目标】-【源】
D3DBLENDOP_MIN 在源像素计算结果和目标像素计算结果之间取小者。即【最终结果】= MIN(【目标】,【源】)
D3DBLENDOP_MAX 在源像素计算结果和目标像素计算结果之间取大者。即【最终结果】= MAX(【目标】,【源】)

Source和Destination可以取下面的值:
融合因子:
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)红 绿 蓝 透明 四个分量值

3.Alpha值的设定

Alpha分量的主要作用就是指定像素的透明度
使用Alpha融合时,需要明确Alpha值的来源。设置一个对象的颜色属性时,有三种方式:

  • 顶点颜色
    • 使用顶点缓冲区或者索引缓冲区绘图设置顶点属性,其中有颜色属性,可以设置Alpha值
  • 光照和材质
    • 材质中各种光的反射系数是一个四元组,其中就包含了Alpha值
  • 纹理
    • 通过设置纹理来进行设置一个模型的颜色

三种设置对象颜色的Alpha值的方式,常用程度是纹理>光照材质>顶点颜色,即Alpha值的来源顺序,如果有纹理,就从纹理获取,没有纹理就从光照材质中获取,如果光照材质中也没有,就从顶点属性中获取。

通常从纹理的Alpha通道获取Alpha信息。Alpha通道是保留给存储了Alpha分量的纹理元的一个额外的位集合。当纹理映射到某个图元中时,Alpha通道中的Alpha分量也进行了映射,并成为该图元的Alpha分量。

默认情况下如果当前设置的纹理有一个Alpha通道,Alpha值就取自该Alpha通道。如果没有Alpha通道,就取自顶点颜色。可通过以下方式指定Alpha值的来源:
> // 设定Alpha值来源于漫反射颜色光
Device->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_DIFFUSE);
Device->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG1);

Alpha融合实现方式如上述流程

示例代码main.cpp:

#include "d3dUtility.h"

//
// Globals
//
#define SCREEN_WIDTH 800 
#define SCREEN_HEIGHT 600 
#define FVF_VERTEX (D3DFVF_XYZ | D3DFVF_TEX1)
#define SAFE_RELEASE(p) { if(p) { (p)->Release();(p)=NULL; } } 

IDirect3DDevice9* Device = 0; // Direct3D设备
ID3DXMesh* Teapot = 0; // 创建网格 存储茶壶网格数据
IDirect3DVertexBuffer9* vb; // 顶点缓存
IDirect3DTexture9* tex = 0; // 纹理

D3DMATERIAL9 TeapotMtrl; // 茶壶的材质
D3DMATERIAL9 BkGndMtrl; // bg的材质

// 初始化颜色属性
const D3DXCOLOR WHITE(D3DCOLOR_XRGB(255, 255, 255));
const D3DXCOLOR BLACK(D3DCOLOR_XRGB(0, 0, 0));
const D3DXCOLOR RED(D3DCOLOR_XRGB(255, 0, 0));

D3DMATERIAL9 InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p);

// 初始化材质颜色
const D3DMATERIAL9 WHITE_MTRL = InitMtrl(WHITE, WHITE, WHITE, BLACK, 2.0f);
const D3DMATERIAL9 RED_MTRL = InitMtrl(RED, RED, RED, BLACK, 2.0f);

// 顶点结构
struct Vertex
{
	float _x, _y, _z; // 位置
	float _u, _v; // 纹理坐标
	Vertex() {}
	Vertex(float x, float y, float z, float u, float v) :_x(x), _y(y), _z(z), _u(u), _v(v) {}
};

// 初始化材质
D3DMATERIAL9 InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p)
{
	D3DMATERIAL9 mtrl;
	mtrl.Ambient = a; // 指定材质对环境光的反射率
	mtrl.Diffuse = d; // 指定材质对漫射光的反射率
	mtrl.Specular = s; // 指定材质对镜面光的反射率
	mtrl.Emissive = e; // 该分量用于增强物体的亮度,使其看起来好像自发光
	mtrl.Power = p; // 镜面高光点的锐度,越大则高光点锐度越大
	return mtrl;
}

// 顶点缓存和索引缓存
bool Init()
{
	Device->CreateVertexBuffer(6 * sizeof(Vertex), D3DUSAGE_WRITEONLY, FVF_VERTEX, D3DPOOL_MANAGED, &vb, 0);
	Vertex Vertices[] =
	{
		{-10.0f, -10.0f, 5.0f, 0.0f, 1.0f},
		{-10.0f, 10.0f, 5.0f, 0.0f, 0.0f},
		{10.0f, 10.0f, 5.0f, 1.0f, 0.0f},
		{-10.0f, -10.0f, 5.0f, 0.0f, 1.0f},
		{10.0f, 10.0f, 5.0f, 1.0f, 0.0f},
		{10.0f, -10.0f, 5.0f, 1.0f, 0.0f},
	};
	//Vertex* v;
	VOID* pVertices;
	//vb->Lock(0, 0, (void**)&v, 0);
	vb->Lock(0, 0, (void**)&pVertices, 0);
	/*
	v[0] = Vertex(-10.0f, -10.0f, 5.0f, 0.0f, 1.0f);
	v[1] = Vertex(-10.0f, 10.0f, 5.0f, 0.0f, 0.0f);
	v[2] = Vertex(10.0f, 10.0f, 5.0f, 1.0f, 0.0f);

	v[3] = Vertex(-10.0f, -10.0f, 5.0f, 0.0f, 1.0f);
	v[4] = Vertex(10.0f, 10.0f, 5.0f, 1.0f, 0.0f);
	v[5] = Vertex(10.0f, -10.0f, 5.0f, 1.0f, 0.0f);
	*/
	memcpy(pVertices, Vertices, sizeof(Vertices));
	vb->Unlock();

	return true;
}

bool Setup()
{
	TeapotMtrl = WHITE_MTRL; // 茶壶的材质为白色
	TeapotMtrl.Diffuse.a = 0.5f; // 设置茶壶的材质对漫射光的反射率

	BkGndMtrl = WHITE_MTRL; // 背景的材质为白色材质
	D3DXCreateTeapot(Device, &Teapot, 0); // 创建一个茶壶 用Teapot网格数据结构保存

	Init(); // 初始化顶点数据

	// 创建直照射光
	D3DLIGHT9 light;
	::ZeroMemory(&light, sizeof(light));
	light.Type = D3DLIGHT_DIRECTIONAL; // 光照的类型为直射光
	light.Ambient = D3DXCOLOR(0.8f, 0.8f, 0.8f, 1.0f); // 光源所发出的环境光的颜色
	light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); // 光源所发出的漫射光颜色
	light.Specular = D3DXCOLOR(0.2f, 0.2f, 0.2f, 1.0f); // 光源所发出的镜面光颜色
	light.Direction = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // 光线在世界坐标系中的传播方向
	Device->SetLight(0, &light); // 光源初始化完毕 在D3D所维护的光源内部列表中注册此光源
	Device->LightEnable(0, true); // 光源注册成功后 打开光源的控制开关

	// 重新规范化法向量 并启用镜面高光
	Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
	Device->SetRenderState(D3DRS_SPECULARENABLE, true);

	// 创建纹理
	D3DXCreateTextureFromFile(Device, "docare.png", &tex);

	// 线性纹理过滤
	Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

	// 设定Alpha值来源于漫反射颜色光
	Device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
	Device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);

	// 设定D3DRS_SRCBLEND 和 D3DRS_DESTBLEND 源融合因子 目标融合因子进行设定
	Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

	// 茶壶属于3D物体 需要取景变换
	D3DXVECTOR3 pos(0.0f, 0.0f, -2.0f);
	D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
	D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
	D3DXMATRIX V;
	D3DXMatrixLookAtLH(&V, &pos, &target, &up);

	Device->SetTransform(D3DTS_VIEW, &V);

	// 透视投影
	D3DXMATRIX proj;
	D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI*0.5f, (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT, 1.0f, 1000.0f);

	Device->SetTransform(D3DTS_PROJECTION, &proj);

	return true;
}

void Cleanup()
{
	d3d::Release(vb);
	d3d::Release(tex);
	d3d::Release(Teapot);
}

bool Display(float timeDelta)
{
	if (Device) // Only use Device methods if we have a valid device.
	{
		// 获取键盘消息
		if (::GetAsyncKeyState('A') & 0x8000f)
			TeapotMtrl.Diffuse.a += 0.001f;
		if (::GetAsyncKeyState('D') & 0x8000f)
			TeapotMtrl.Diffuse.a -= 0.001f;

		// force alpha to [0,1] interval
		if (TeapotMtrl.Diffuse.a > 1.0f)
			TeapotMtrl.Diffuse.a = 1.0f;
		if (TeapotMtrl.Diffuse.a < 0.0f)
			TeapotMtrl.Diffuse.a = 0.0f;

		// 绘制场景
		Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
		Device->BeginScene();
		Device->SetFVF(FVF_VERTEX);
		Device->SetStreamSource(0, vb, 0, sizeof(Vertex));
		// 设置Bg的材质
		Device->SetMaterial(&BkGndMtrl);
		Device->SetTexture(0, tex);
		Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
		// 启用融合运算
		Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
		// 设置茶壶的材质
		Device->SetMaterial(&TeapotMtrl);
		// 禁用茶壶的纹理
		Device->SetTexture(0, 0);
		// 绘制
		Teapot->DrawSubset(0);
		// 关闭融合计算
		Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);

		Device->EndScene();
		Device->Present(0, 0, 0, 0);
	}
	return true;
}

//
// WndProc
//
LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_DESTROY:
		::PostQuitMessage(0);
		break;

	case WM_KEYDOWN:
		if (wParam == VK_ESCAPE)
			::DestroyWindow(hwnd);
		break;
	}
	return ::DefWindowProc(hwnd, msg, wParam, lParam);
}

//
// WinMain
//
int WINAPI WinMain(HINSTANCE hinstance,
	HINSTANCE prevInstance,
	PSTR cmdLine,
	int showCmd)
{
	if (!d3d::InitD3D(hinstance,
		SCREEN_WIDTH, SCREEN_HEIGHT, true, D3DDEVTYPE_HAL, &Device))
	{
		::MessageBox(0, "InitD3D() - FAILED", 0, 0);
		return 0;
	}

	if (!Setup())
	{
		::MessageBox(0, "Setup() - FAILED", 0, 0);
		return 0;
	}

	d3d::EnterMsgLoop(Display);

	Cleanup();

	Device->Release();

	return 0;
}

将texture的材质图片放在工程目录下
运行效果:
在这里插入图片描述
可通过按键A or D调整Alpha数值
完整框架代码及入门教程:DirectX9.0入门及实例程序

猜你喜欢

转载自blog.csdn.net/gaoyz1/article/details/84669422