Precise Texture Overlay

穿精又带淫゛_ 提交于 2019-12-13 06:03:44

问题


I'm trying to set up a two-stage render of objects in a 3D engine I'm working on written in C++ with DirectX9 to facilitate transparency (and other things). I thought it was all working nicely until I noticed some dodgyness on the edge of objects rendered before objects using this two stage method.

The two stage method is simple:

  • Draw model to off-screen ("side") texture of same size using same zbuffer (no MSAA is used anywhere)

  • Draw off-screen ("side") texture over the top of the main render target with a suitable blend and no alpha test or write

In the image below the left view is with the two stage render of the gray object (a lamppost) with the body in-front of it rendered directly to the target texture. The right view is with the two-stage render disabled, so both are rendered directly onto the target surface.

On close inspection it is as if the side texture is offset by exactly 1 pixel "down" and 1 pixel "right" when rendered over the target surface (but is rendered correctly in-place). This can be seen in an overlay of the off screen texture (which I get my program to write out to a bitmap file via D3DXSaveTextureToFile) over a screen shot below.

One last image so you can see where the edge in the side texture is coming from (it's because rendering to the side texture does use z test). Left is screen short, right is side texture (as overlaid above).

All this leads me to believe that my "overlaying" isn't very effective. The code that renders the side texture over the main render target is shown below (note that the same viewport is used for all scene rendering (on and off screen)). The "effect" object is an instance of a thin wrapper over LPD3DXEFFECT, with the "effect" field (sorry about shoddy naming) being a LPD3DXEFFECT itself.

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);
}

The C++ side definition for the VertexOver struct and constructor (HLSL side shown below somewhere):

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;
    }
};

Inefficiency in re-creating and passing the vertices down to the GPU each draw aside, what I really want to know is why this method doesn't quite work, and if there are any better methods for overlaying textures like this with an alpha blend that won't exhibit this issue

I figured that the texture sampling may matter somewhat in this matter, but messing about with options didn't seem to help much (for example, using a LINEAR filter just makes it fuzzy as you might expect implying that the offset isn't as clear-cut as a 1 pixel discrepancy). Shader code:

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;
}

I've looked about for a "Blended Blit" or something but I can't find anything, and other related searches have only brought up forums implying that rendering a quad with an orthographic projection is the way to go about doing this.

Sorry if I've given far too much detail for this issue but it's both interesting and infuriating and any feedback would be greatly appreciated.


回答1:


It looks for me that you problem is the mapping of texels to pixels. You must offset a screen-aligned quad with a half pixel to match the texels direct to the screenpixels. This issue is explaines here: Directly Mapping Texels to Pixels (MSDN)




回答2:


For anyone else hitting a similar wall, my specific problem solved by adjusting the U and V values of the verticies sent to the GPU for the overlaid texture triangles thus:

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;
}

See Directly Mapping Texels to Pixels as provided by Gnietschow's answer for an explanation as to why this makes sense.



来源:https://stackoverflow.com/questions/16626461/precise-texture-overlay

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!