您的位置:首页 > 其它

DirectX学习笔记(八):Direct3D融合技术详解及物体透明效果的实现

2016-09-18 22:03 736 查看

前言:

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



相关知识说明:

1.融合方程:

当我们处于背景图和茶壶三角形单元进行光栅化计算时,我们需要将计算得到的茶壶的像素颜色和背景图的像素颜色进行合成,以使得背景图透过茶壶得以显示。这种将当前计算得到的像素(源像素)颜色值与先前计算得出的像素(目标像素)颜色值合成的做法就是融合技术。

首先,上方程:OutputPixel = SourcePixelⓧSourceBlendFactor + DestPixel ⓧ DestBlendFactor

简单来说:融合后的颜色值  =  当前计算得出的像素颜色值 ⓧ  源融合因子 + 处于后台缓存的像素颜色值 ⓧ  目标融合因子.。

OutputPixel:融合后的颜色值。


SourcePixel:当前计算得到的、用于与后台缓存中对应像素进行融合的像素颜色值。


SourceBlendFactor :源融合因子,指定了源像素的颜色值在融合中所占的比例。在[0,1]区间内。


DestPixel :当前处于后台缓存中的像素颜色值。

DestBlendFactor:目标融合因子,指定了目标像素的颜色值在融合中所占的比例。在[0,1]区间内。

通过此方程可以看出我们可以通过源融合因子和目标融合因子修改源像素和目标像素的颜色值,以达到不同的效果。

以下强调一点:

融合计算的开销还是比较大的,所以当我们绘制玩需要进行融合的几何体后,一定要禁用融合。在对三角形单元元组进行处理时,之后应该l立刻绘制出来,这样就避免了在每帧图像中都启用和禁用融合运算。

我们通过如下方法进行融合运算的启动和禁用:

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



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

通过设定融合因子和目标融合因子,我们可以创建一系列不同的效果。我们通过如下方式设定D3DRS_SRCBLEND 和D3DRS_DESTBLEND的值来对源融合因子和目标融合因子分别设定:

//通过设定D3DRS_SRCBLEND 和D3DRS_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值的来源。我们在设置一个对象的颜色属性时,有三种方式:

1.顶点颜色:

这个是最古老的方法,也是最麻烦的。最早使用顶点缓冲区或者索引缓冲区绘图的时候设置过定点属性,其中有颜色属性,可以设置Alpha值。

2.光照和材质:

材质中各种光的反射系数是一个四元组,其中就包含了Alpha值。

3.纹理:

最容易的就是设置纹理来确定一个模型的颜色,所以这个也是最常用的。

既然有三种设置对象的颜色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);




实现的方法就如上述流程。


代码实现:

#include <windows.h>
#include<d3d9.h>
#include<d3dx9.h>
#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; } }  //自定义一个SAFE_RELEASE()宏,便于资源的释放

//创建一个网格结构,用来存储茶壶网格数据
ID3DXMesh*   Teapot = 0;
//茶壶的材质
D3DMATERIAL9 TeapotMtrl;

IDirect3DDevice9*       _device; //Direct3D设备
IDirect3DVertexBuffer9* _vb;         //顶点缓存

IDirect3DTexture9* tex = 0;  // 纹理
//bg 的材质
D3DMATERIAL9            BkGndMtrl;

//函数声明
//初始化Direct3D
//消息过程,处理消息
//初始化材质
bool InitD3D(HINSTANCE hInstance, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9 ** device);
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
D3DMATERIAL9 InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p);

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

//材质
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){}

};

//以下为窗口过程
LRESULT CALLBACK 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);
}

//初始化D3D
bool InitD3D(HINSTANCE hInstance, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9 ** device)
{

//定义一个完整的窗口类
WNDCLASS wc;
wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszClassName = "LSZDX";
wc.lpszMenuName = 0;

//注册窗口类
if (!(RegisterClass(&wc)))
{
MessageBox(0, "RegisterClass is FAILED", 0, 0);
return false;
}

//创建窗口
HWND hwnd = 0;
hwnd = CreateWindow("LSZDX", "LSZDXDEMO", WS_OVERLAPPEDWINDOW, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, hInstance, 0);
if (hwnd == 0)
{
MessageBox(0, "CreateWIndow is FAILED", 0, 0);
return false;
}

//绘制和更新窗口
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);

//以下为初始化D3D
//*---------------
//获取IDirect3D9的指针
IDirect3D9 *d3d9 = 0;
d3d9 = Direct3DCreate9((D3D_SDK_VERSION));
if (!d3d9)
{
MessageBox(0, "Direct3D9Create9 is FAILED", 0, 0);
return false;
}

//检验硬件顶点运算
D3DCAPS9 caps;
d3d9->GetDeviceCaps(
D3DADAPTER_DEFAULT,
deviceType,
&caps);
int vp = 0;
if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

//填充D3DPRESENT_PARAMETERS
D3DPRESENT_PARAMETERS d3dpp;
d3dpp.BackBufferWidth = width;
d3dpp.BackBufferHeight = height;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = windowed;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = 0;
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

//创建IDirect3DDevice9接口
if (FAILED(d3d9->CreateDevice(
D3DADAPTER_DEFAULT,
deviceType,
hwnd,
vp,
&d3dpp,
device)))
{
MessageBox(0, "CreateDevice is FAILED", 0, 0);
return false;
}
d3d9->Release();
return true;
}

//初始化材质
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* v;
//数据锁定,写入
_vb->Lock(0, 0, (void**)&v, 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, 1.0f);

//解锁
_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));  //0填充
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, "crate.jpg", &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;
}

//显示操作
bool DisPlay()
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG));//用0来填充消息可类比为:memset()函数

while (msg.message != WM_QUIT)
{
//PeekMessage函数是以查看的方式从系统中获取消息
//并将该消息(如果存在)放于指定的结构
if (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
//PM_REMOVE:PeekMessage处理后,消息从队列里除掉。

//TranslateMessage函数将虚拟键消息转换为字符消息。
//字符消息被寄送到调用线程的消息队列里,
//当下一次线程调用函数GetMessage或PeekMessage时被读出。
//TranslateMessage只能用于转换调用GetMessage或PeekMessage接收的消息。
::TranslateMessage(&msg);

//DispatchMessage函数
//该函数分发一个消息给窗口程序。
//通常消息从GetMessage函数获得。
//消息被分发到回调函数(过程函数),作用是消息传递给操作系统,
//然后操作系统去调用我们的回调函数,也就是说我们在窗体的过程函数中处理消息
::DispatchMessage(&msg);
}
else
{
if (_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();

// Draw the background

//设置灵活顶点格式
_device->SetFVF(FVF_VERTEX);
//指定数据流来源
_device->SetStreamSource(0, _vb, 0, sizeof(Vertex));
//设置bj的材质
_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;
}

//
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
if (!InitD3D(hInstance, SCREEN_WIDTH, SCREEN_HEIGHT, true, D3DDEVTYPE_HAL, &_device))
{
MessageBox(0, "InitD3D is FAILED", 0, 0);
return 0;
}

if (!Setup())
{
MessageBox(0, "Setup() - FAILED", 0, 0);
return 0;
}
DisPlay();
_device->Release();
return 0;

}


运行效果:



如果要运行代码,您需要将下面这张图放入工程源目录下,并且更名为:crate 图片格式JPG



有问题的欢迎留言交流。一起努力,一起进步。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  DirectX