1

我正在尝试在我正在使用 DirectX9 用 C++ 编写的 3D 引擎中设置对象的两阶段渲染,以促进透明度(和其他事情)。我认为这一切都很好,直到我注意到在使用这种两阶段方法渲染的对象之前渲染的对象边缘有些狡猾。

两阶段方法很简单:

  • 使用相同的 zbuffer 将模型绘制到相同大小的屏幕外(“侧面”)纹理(任何地方都没有使用 MSAA)

  • 使用适当的混合在主渲染目标的顶部绘制屏幕外(“侧面”)纹理,无需 alpha 测试或写入

在下图中,左视图是灰色对象(灯柱)的两阶段渲染,其前面的主体直接渲染到目标纹理。右视图禁用了两阶段渲染,因此两者都直接渲染到目标表面上。

狡猾/不狡猾的比较

在仔细检查时,好像侧面纹理在目标表面上渲染时恰好偏移了 1 个像素“向下”和 1 个像素“右”(但在原地正确渲染)。这可以在D3DXSaveTextureToFile下面的屏幕截图上的屏幕外纹理(我让我的程序通过 写入位图文件)的覆盖中看到。

在此处输入图像描述

最后一张图像,您可以看到侧面纹理中的边缘来自哪里(这是因为渲染到侧面纹理确实使用 z 测试)。左边是屏幕短,右边是侧面纹理(如上图所示)。

在此处输入图像描述

所有这一切让我相信我的“叠加”不是很有效。在主渲染目标上渲染侧面纹理的代码如下所示(请注意,相同的视口用于所有场景渲染(屏幕上和屏幕外))。“效果”对象是一个薄包装器的实例LPD3DXEFFECT,其中“效果”字段(对不起,以次充好命名)LPD3DXEFFECT本身就是一个。

void drawSideOver(LPDIRECT3DDEVICE9 dxDevice, drawData* ddat)
{ // "ddat" drawdata contains lots of render state information, but all we need here is the handles for the targetSurface and sideSurface
    D3DXMATRIX idMat;
    D3DXMatrixIdentity(&idMat); // create identity matrix
    dxDevice->SetRenderTarget(0, ddat->targetSurface); // switch to targetSurface

    dxDevice->SetRenderState(D3DRS_ZENABLE, false); // disable z test and z write
    dxDevice->SetRenderState(D3DRS_ZWRITEENABLE, false);

    vertexOver overVerts[4]; // create square
    overVerts[0] = vertexOver(-1, -1, 0, 0, 1);
    overVerts[1] = vertexOver(-1, 1, 0, 0, 0);
    overVerts[2] = vertexOver(1, -1, 0, 1, 1);
    overVerts[3] = vertexOver(1, 1, 0, 1, 0);

    effect.setTexture(ddat->sideTex); // use side texture as shader texture ("tex")
    effect.effect->SetTechnique("over"); // change to "over" technique
    effect.setViewProj(&idMat); // set viewProj to identity matrix so 1/-1 map directly
    effect.effect->CommitChanges();

    setAlpha(dxDevice); // this sets up the alpha blending which works fine

    UINT numPasses, pass;
    effect.effect->Begin(&numPasses, 0);
    effect.effect->BeginPass(0);

    dxDevice->SetVertexDeclaration(vertexDecOver);
    dxDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, overVerts, sizeof(vertexOver));

    effect.effect->EndPass();
    effect.effect->End();

    dxDevice->SetRenderState(D3DRS_ZENABLE, true); // revert these so we don't mess everything up drawn after this
    dxDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);
}

VertexOver 结构和构造函数的 C++ 端定义(HLSL 端显示在下面某处):

struct vertexOver
{
public:
    float x;
    float y;
    float z;
    float w;
    float tu;
    float tv;

    vertexOver() { }
    vertexOver(float xN, float yN, float zN, float tuN, float tvN)
    {
        x = xN;
        y = yN;
        z = zN;
        w = 1.0;
        tu = tuN;
        tv = tvN;
    }
};

重新创建顶点并将其传递给 GPU 的效率低下,每次绘制都放在一边,我真正想知道的是为什么这种方法不能很好地工作,以及是否有更好的方法可以用 alpha 混合覆盖这样的纹理不会出现这个问题

我认为纹理采样在这件事上可能有点重要,但弄乱选项似乎没有多大帮助(例如,使用 LINEAR 过滤器只会使它变得模糊,正如您可能期望的那样暗示偏移量不那么清楚-cut 为 1 个像素的差异)。着色器代码:

struct VS_Input_Over
{
    float4 pos : POSITION0;
    float2 txc : TEXCOORD0;
};

struct VS_Output_Over
{
    float4 pos : POSITION0;
    float2 txc : TEXCOORD0;
    float4 altPos : TEXCOORD1;
};

struct PS_Output
{
    float4 col : COLOR0;
};

Texture tex;
sampler texSampler = sampler_state { texture = <tex>;magfilter = NONE; minfilter = NONE; mipfilter = NONE; AddressU = mirror; AddressV = mirror;};

// side/over shaders (these make up the "over" technique (pixel shader version 2.0)
VS_Output_Over VShade_Over(VS_Input_Over inp)
{
    VS_Output_Over outp = (VS_Output_Over)0;
    outp.pos = mul(inp.pos, viewProj);
    outp.altPos = outp.pos;
    outp.txc = inp.txc;
    return outp;
}

PS_Output PShade_Over(VS_Output_Over inp)
{
    PS_Output outp = (PS_Output)0;

    outp.col = tex2D(texSampler, inp.txc);

    return outp;
}

我一直在寻找“Blended Blit”或其他东西,但我找不到任何东西,其他相关搜索只提出了论坛,暗示用正交投影渲染四边形是这样做的方法。

抱歉,如果我为这个问题提供了太多细节,但它既有趣又令人愤怒,任何反馈都将不胜感激。

4

2 回答 2

2

在我看来,您的问题是纹素到像素的映射。您必须将屏幕对齐的四边形偏移半个像素,以将纹素直接匹配到屏幕像素。这个问题在这里解释:Directly Mapping Texels to Pixels (MSDN)

于 2013-05-18T17:31:58.360 回答
0

对于遇到类似墙的其他人,我的具体问题通过调整发送到 GPU 的重叠纹理三角形的顶点的 U 和 V 值来解决,因此:

for (int i = 0; i < 4; i++)
{
    overVerts[i].tu += 0.5 / (float)ddat->targetVp->Width; // ddat->targetVp is the viewport in use, and the viewport is the same size as the texture
    overVerts[i].tv += 0.5 / (float)ddat->targetVp->Height;
}

请参阅Gnietschow 的回答提供的Directly Mapping Texels to Pixels,以了解为什么这是有意义的。

于 2013-05-18T17:53:09.213 回答