D3D 9 通过 Alpha 测试与 Alpha 混合随意控制纹理透明度

网上不怎么见得到关于 D3D 9 的技术文章了,笔者最近在写一个 2D 的功能,希望通过某些设置来随意控制一张 2D 纹理贴图的透明度,网上找来找去,所有的文章,要不就是照抄市面教材,要么是照抄官方文档,稍微好一点的,就是在官方教材的基础上增加中文翻译,哎哟我去,找了两天啊,功能是实现了,可是心中的疑惑一直挥之不去,为啥 ?因为我的代码是照抄的啊,我只知道这样子设置可以得到这样子的效果,但是我特么不知道为啥啊,我特么要是下次再遇到同样的问题,我是不是又要再去抄一次代码 ?

好了,牢骚就发到这里吧,先来说一说本文的核心问题 —— 在 Direct3D 9 里面,如何通过控制顶点的 Diffuse 中的 Alpha 分量实现纹理的任意透明度;

用 D3D 做 2D,普遍的习惯就是用 4 个顶点来绘制一个矩形,随后设置上贴图,这样子一个最简单的 Sprite 就完成了,顶点格式我们可以这样子定义:

1 struct D3D9VERTEX2D { 2     float x, y, z, rhw; 3  DWORD diffuse; 4     float u, v; 5 };

因为图片文件自身哪怕带有 Alpha 数据,其 Alpha 值也是固定的,我们需要一种手段,在游戏运行过程中随意控制纹理的透明度,笔者自己习惯的做法就是通过控制四个顶点的 diffuse 中的 alpha 值来实现透明度控制,笔者把顶点的 diffuse 值默认被初始化为 0xFFFFFFFF,也就是 D3DCOLOR_ARGB( 255, 255, 255, 255 ),但是单纯控制透明度还不行,我们还要考虑镂空效果,以表现出要镂空的地方镂空,不需要镂空的地方,则是按照我们的意图随意控制透明度,效果如下图所示:

D3D 9 通过 Alpha 测试与 Alpha 混合随意控制纹理透明度_第1张图片

上图中,妹子是背景图(笑),前景图是中间那个粉红色的球,球原本是一张 png 图片,自身带有 alpha 通道,可以实现镂空,但是球的透明度,就需要通过 alpha blend 来额外实现;

要实现这个功能,我们需要设置三种类型的参数,分别是:Alpha Blend、Alpha Test 以及 Texture Stage State,中文不好翻译,直接上英文(笑);

首先是 Alpha Blend,这点很多人都知道,就是一条公式:

output_color = ( src_color * src_alpha ) + ( dest_color * ( 1 - src_alpha ) )

设置的方法也很简单,就是 SetRenderState 打开 D3DRS_ALPHABLENDENABLE,设置 D3DRS_SRCBLEND 为 D3DBLEND_SRCALPHA,设置 D3DRS_DESTBLEND 为 D3DBLEND_INVSRCALPHA,至于中间那个加号,则是设置 D3DRS_BLENDOP 为 D3DBLENDOP_ADD,代码如下:

1 d3d9_device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); 2 d3d9_device->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); 3 d3d9_device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); 4 d3d9_device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

问题来了,公式中的两个 color 以及一个 alpha 参数哪里来 ??

兴许有人说 dest_color 就是帧缓存中的颜色数据,那 src_color 和 src_alpha 又是哪里来呢 ??答不出来了吧。

答案就是 —— 从纹理层(Texture Stage)运算结果得来的;

接下来我们看看 Texture Stage 是怎么设置的:

1 d3d9_device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); 2 d3d9_device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); 3 d3d9_device->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_CURRENT ); 4 
5 d3d9_device->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); 6 d3d9_device->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE ); 7 d3d9_device->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE );

这六句代码是什么意思 ??

聪明的人已经看出来了,前三句代码负责生成 Alpha Blend 要用到的 src_color,而后三句代码,则是负责生成 src_alpha;

我们都知道,Texture Stage 主要有两部分组成 —— 颜色成分运算与 alpha 成分运算,每一层都遵循下面两条公式,来产生输出给下一层的颜色数据以及 alpha 数据:

color = color_arg1 op color_arg2

alpha = alpha_arg1 op alpha_arg2

我们先来看看上面第一条公式如何生成颜色数据,color_arg1 被设置为来自纹理,color_arg2 被设置为来自上一层的颜色值,如果该层处于第 0 层,则 color_arg2 相当于顶点的 diffuse 通过 Gouraud shading 运算之后的结果,我们这里可以近似的看成是顶点的 diffuse 值就可以了,而 op 被设置为乘法运算符,对于一张 png 图片,因为我们的 diffuse 已经预设为 0xFFFFFF,这个值会在 D3D 内部运算时被插值为 (1.0, 1.0, 1.0 ),所以实际上进行的是浮点数运算,因为 1.0 乘以颜色值,其结果都是颜色值自身,所以输出的 color 就是纹理自身的颜色值;

再来看看上面第二条公式如何生成 alpha 数据,alpha_arg1 被设置为来自顶点的 diffuse,alpha_arg2 被设置为来自纹理,op 则被设置为乘法运算符,对于一张 png 图片,镂空部分的 alpha 值为 0,0 乘以任何数都是 0,所以镂空部分永远都镂空,而非镂空部分,alpha 值不为 0,乘以纹理的 alpha 值,结果取决于纹理自身的 alpha 值,但是我们通过 D3DXCreateTextureFromFileEx( ) 创建纹理的时候,纹理中所有非镂空部分的 alpha 值都被自动填充为 0xFF,也就是 1.0,所以运算结果就是 diffuse 的 alpha 值;

就是这样,上面两条公式产生的 color,其实就是纹理自身的 color,而产生的 alpha,其实就是 diffuse 的 alpha,这样子,我们就可以通过设置顶点的 diffuse 的 alpha 值,来随意控制纹理的透明度了;

为了验证一下上述说法,我们举个例子吧:

假设我们要加载一张 png 图片作为纹理,纹理的镂空部分像素值被自动填充为 0x00RRGGBB,非镂空部分被填充为 0xAARRGGBB,其中 AA RR GG BB 就是纹理自身的颜色数据以及 alpha 数据,随后我们把自己定义的顶点的 diffuse 值设置为 0xDDFFFFFF,其 alpha 值为 0xDD,而颜色值则为 0xFFFFFF;

接下来根据上面 Texture Stage 中的设置,将会分别求出颜色与 alpha 的值:

颜色值为 0xRRGGBB * 0xFFFFFF,因为被 D3D 内部插值为浮点数,所以其实是 ( RR / 255, GG / 255, BB / 255 ) * ( 1.0, 1.0, 1.0 ),其结果就是纹理自身的颜色值,也就是 0xRRGGBB;

alpha 值有两种情况,镂空和非镂空,两种情况的结果分别为 0x00 * 0xAA 与 0xDD* 0xAA,也就是 0x00 与 0xDD,其中 0xDD 就是我们 diffuse 中设置的 alpha 值;

接下来生成的颜色值与 alpha 会参与 alpha blend 运算,套用最上面的第一个公式,依旧有两种情况,分别是镂空与非镂空:

output = ( 0xRRGGBB * 0.0f ) + ( dest_color * ( 1.0f - 0.0f ) )

output = ( 0xRRGGBB * ( 0xDD/ 255 ) ) + ( dest_color * ( 1.0f - ( 0xDD / 255 ) )

第一条公式为镂空,生成的值固定是 dest_color,也就是背景图的颜色,第二条公式为非镂空,生成的值就是经过 alpha blend 的颜色值;

好了,随意控制纹理的透明度是搞定了,最后我们还要打开 Alpha Test,让 D3D 为我们进行镂空,Alpha Test 设置如下:

1 d3d9_device->SetRenderState( D3DRS_ALPHATESTENABLE, TRUE ); 2 d3d9_device->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATER ); 3 d3d9_device->SetRenderState( D3DRS_ALPHAREF, 0x00 );

上面三句代码意思很简单 —— 任何将要被渲染的像素,如果其 alpha 值大于 0,就进行渲染,否则,不予以渲染;

OK,搞定,上面的代码除了可以任意控制透明度,还可以控制纹理的色调,同样通过修改顶点的 diffuse 值,如果我们把 diffuse 设置为 D3DCOLOR_ARGB( 200, 255, 0, 0 ),就可以得到一个透明的红色色调的小球:

D3D 9 通过 Alpha 测试与 Alpha 混合随意控制纹理透明度_第2张图片

如果设置为 D3DCOLOR_ARGB( 200, 255, 255, 0 ),则小球会变成透明的黄色色调:

D3D 9 通过 Alpha 测试与 Alpha 混合随意控制纹理透明度_第3张图片

 

还可以同时控制两个纹理的色调:

D3D 9 通过 Alpha 测试与 Alpha 混合随意控制纹理透明度_第4张图片

----------------------------------------------------------

其实还有很多手段可以控制这些如此灵活的参数,pixel shader 和 vertex shader 都行,但是新手在没有学习可编程管道线之前,肯定都是用固定管道线的函数来实现各种特效,仅以这篇文章,献给所有正在学习 D3D 9 的人;

 

你可能感兴趣的:(D3D 9 通过 Alpha 测试与 Alpha 混合随意控制纹理透明度)