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 库)混合,如乘法、颜色燃烧、颜色道奇等