Direct3D 使用“乘法”混合模式和 Alpha 渲染 2D 图像

Posted

技术标签:

【中文标题】Direct3D 使用“乘法”混合模式和 Alpha 渲染 2D 图像【英文标题】:Direct3D rendering 2D images with "multiply" blending mode and alpha 【发布时间】:2010-12-09 11:22:48 【问题描述】:

我正在尝试使用 Direct3D 复制 Photoshop 滤镜乘法。我一直在阅读和谷歌搜索不同的渲染状态,我的效果几乎可以工作。问题是它忽略了纹理的 alpha 值。

这是一张说明情况的图片:

http://www.kloonigames.com/petri/***_doesnt_allow_.jpg

我找到了一种解决方案,即保存没有透明度和白色背景的图像。但我对这个解决方案并不满意。问题是我真的需要使用 alpha 值。我想逐渐淡出图像。如果混合模式忽略 alpha 值,我将无法执行此操作。

那么问题是如何用 alpha 渲染图像?

这是混合模式代码:

dev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
dev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
dev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);

Edit 添加了 SetTextureStageState

dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);

【问题讨论】:

这里是完整的渲染源代码:pastebin.com/m7d7991fb 你为纹理设置了什么->SetTextureStageState 你可以在这里看到它:pastebin.com/m7d7991fb 【参考方案1】:
dev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
dev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

会成功的。但是,您不能再使用漫反射顶点颜色中的“alpha”。在顶点颜色上设置低 alpha 值实际上会使覆盖的像素变亮。

【讨论】:

【参考方案2】:

您可以通过在像素着色器中预乘 alpha 或使用具有预乘 alpha 的纹理一步来实现此效果。

例如,如果您有 3 种可能的着色器混合操作,并且您希望每个都考虑 Alpha。

Blend = ( src.rgb * src.a ) + ( dest.rgb * (1-src.a) )
Add = ( src.rgb * src.a ) + ( dest.rgb )
Multiply = (src.rgb * dest.rgb * src.a) + (dest.rgb * (1-src.a) )

您会注意到,单次通过就不可能进行乘法运算,因为对源颜色有两个操作。但是,如果您在着色器中预乘 alpha,您可以从混合操作中提取 alpha 分量,并且可以在同一个着色器中混合所有三个操作。

在您的像素着色器中,您可以手动预乘 Alpha。或者使用 DirectXTex texconv 之类的工具来修改纹理。

return float4(color.rgb*color.a, color.a);

操作变成:

Blend = ( src.rgb ) + ( dest.rgb * (1-src.a) )
Add = ( src.rgb ) + ( dest.rgb )
Multiply = ( src.rgb * dest.rgb ) + (dest.rgb * (1-src.a) )

【讨论】:

【参考方案3】:

好吧,这并不像您想象的那么简单。我会为此使用一个效果和两个渲染目标... 我很高兴您使用一个渲染通道来尝试执行此操作,但这是行不通的。 Photoshop 有图层,每个图层都有一个 Alpha 通道。顺便说一句,很高兴知道您正在制作什么样的应用程序。

所以首先在 D3D 中,我将创建 2 个与窗口大小相同的 RGBA_32 位渲染目标并将它们清除为白色。把它变成一个这样的数组(new RenderTarget[2];)用于交换。

现在将混合状态设置为 (AlphaFunc=Add, Src=SrcAlpha, Dst=InvSrcAlpha)。对于第一个圆圈,您使用 renderTarget[1] 作为纹理/采样器输入源将其绘制到 renderTarget[0] 中。您将使用一个 Effect 渲染圆圈,该效果将采用圆圈颜色并将其与 renderTarget[1] 的采样器颜色相乘。画完一个圆圈后,您可以通过简单的索引将renderTarget[0] 与renderTarget[1] 交换,因此现在renderTarget[1] 是您绘制的对象,而renderTarget[0] 是您从中采样的对象。然后你重复第 2 圈的绘制过程,以此类推。

在你画完一个圆之后,你将最后绘制的 renderTarget 复制到 backBuffer 并呈现场景。

这是一个逻辑上你会如何做的例子。如果你需要参考编码http://www.codesampler.com/是个好地方。

void TestLayering()

bool rtIndex = false;
RenderTarget* renderTarget = new RenderTarget[2];
Effect effect = new Effect("multiplyEffect.fx");
effect.Enable();
BlendingFunc = Add;
BlendingSource = SrcAlpha;
BlendingDest = InvSrcAlpha;

for(int i = 0; i != circleCount; ++i)

  renderTarget[rtIndex].EnableAsRenderTarget();
  renderTarget[!rtIndex].EnableAsSampler();
  circle[i].Draw();
  rtIndex = !rtIndex;


//Use D3D9's StretchRect for this...
backBuffer.CopyFromSurface(renderTarget[rtIndex]);


//Here is the effects pixel shader
float4 PS_Main(InStruct In) : COLOR

float4 backGround = tex2D(someSampler, In.UV);
return circleColor * backGround;

【讨论】:

【参考方案4】:

听起来像你想要的:

dst.rgb = (src.a * src.rgb) * ((1 - src.a) * dst.rgb)

您可以使用 D3DRS_BLENDOP 来执行此操作,但遗憾的是没有 D3DBLENDOP_MULTIPLY。我认为如果没有片段着色器,这个操作是不可能的。

【讨论】:

以上是关于Direct3D 使用“乘法”混合模式和 Alpha 渲染 2D 图像的主要内容,如果未能解决你的问题,请参考以下文章

DirectX学习笔记:利用平面着色和Gouraud着色模式绘制具有颜色的三角形

如何将 2 个图像与 PHP(GD-GD2 库)混合,如乘法、颜色燃烧、颜色道奇等

chrome中的Chrome css3混合混合模式错误

Photoshop 混合模式到没有着色器的 OpenGL ES

为啥 Direct3D 应用程序在全屏模式下表现更好?

System.Drawing.SolidBrush 使用啥混合方程?