XNA에서 2D 스프라이트를 마스크하는 가장 좋은 방법은 무엇입니까?


24

현재 스프라이트를 숨기려고합니다. 단어로 설명하는 대신 몇 가지 예제 사진을 만들었습니다.

마스크 할 영역 마스크 할 영역 (흰색)

마스크 할 이미지와 마스크 할 영역 자른 적색 스프라이트입니다.

최종 결과 최종 결과.

이제 XNA에서는 두 가지 작업을 수행하여이 작업을 수행 할 수 있습니다.

  1. 스텐실 버퍼를 사용하십시오.
  2. 픽셀 쉐이더를 사용하십시오.

나는 본질적으로 이것을 한 픽셀 쉐이더를 시도했다.

float4 main(float2 texCoord : TEXCOORD0) : COLOR0
{
    float4 tex = tex2D(BaseTexture, texCoord);
    float4 bitMask = tex2D(MaskTexture, texCoord);

    if (bitMask.a > 0)
    { 
        return float4(tex.r, tex.g, tex.b, tex.a);
    }
    else
    {
        return float4(0, 0, 0, 0);
    }
}

이것은 이미지를 자르는 것처럼 보이지만 (이미지가 움직이기 시작하면 정확하지는 않지만) 내 문제는 이미지가 끊임없이 움직이고 (정적이지 않음)이 자르기는 동적이어야합니다.

셰이더 코드를 변경하여 위치를 고려할 수있는 방법이 있습니까?


또는 스텐실 버퍼 사용에 대해 읽었지만 대부분의 샘플은 실제로 원하지 않는 렌더 대상을 사용하는 것으로 보입니다. (나는 이미 게임의 나머지 부분에 3 또는 4를 사용하고 있으며 그 위에 다른 것을 추가하는 것은 과도하게 보입니다)

내가 찾은 렌더 타겟을 사용 하지 않는 유일한 튜토리얼 은 여기에있는 Shawn Hargreaves의 블로그 에있는 것입니다. 그 문제는 XNA 3.1에 대한 것이며 XNA 4.0으로 잘 변환되지 않는 것입니다.

픽셀 쉐이더가 나아갈 길인 것처럼 보이지만 위치를 올바르게 얻는 방법을 잘 모르겠습니다. 셰이더 좌표에 대해 내 화면 좌표 (500, 500 등)를 0과 1 사이로 변경해야한다고 생각합니다. 내 유일한 문제는 변환 된 좌표를 올바르게 사용하는 방법을 알아 내려고 노력하는 것입니다.

도움을 주셔서 감사합니다.


스텐실은 갈 길인 것 같습니다. 제대로 사용하는 방법도 알아야합니다 .P 문제에 대한 좋은 답변을 기다릴 것입니다.
Gustavo Maciel

허나, 좋은 스텐실 튜토리얼은 대부분 XNA 3.1을위한 것이고, 대부분의 4.0 튜토리얼은 RenderTargets를 사용합니다. 4.0에서는 작동하지 않는 것처럼 보이는 이전 3.1 자습서에 대한 링크로 내 질문을 업데이트했습니다. 어쩌면 누군가가 그것을 4.0으로 올바르게 번역하는 방법을 알고 있습니까?
electroflame

당신이 그것을 달성하기 위해 픽셀 쉐이더를 사용한다면, 당신이 당신의 픽셀 화면 / 세계 좌표 unproject해야 할 것입니다 생각 (스텐실이 문제가 없을 것) (당신이 원하는 무슨에 따라 다름), 이것은 좀 낭비
구스타보 마시 엘을

이 질문은 훌륭한 질문입니다.
ClassicThunder

답변:


11

픽셀 쉐이더 방식을 사용할 수 있지만 불완전합니다.

또한 픽셀 셰이더에 스텐실을 배치 할 위치를 알려주는 일부 매개 변수를 추가해야합니다.

sampler BaseTexture : register(s0);
sampler MaskTexture : register(s1) {
    addressU = Clamp;
    addressV = Clamp;
};

//All of these variables are pixel values
//Feel free to replace with float2 variables
float MaskLocationX;
float MaskLocationY;
float MaskWidth;
float MaskHeight;
float BaseTextureLocationX;  //This is where your texture is to be drawn
float BaseTextureLocationY;  //texCoord is different, it is the current pixel
float BaseTextureWidth;
float BaseTextureHeight;

float4 main(float2 texCoord : TEXCOORD0) : COLOR0
{
    //We need to calculate where in terms of percentage to sample from the MaskTexture
    float maskPixelX = texCoord.x * BaseTextureWidth + BaseTextureLocationX;
    float maskPixelY = texCoord.y * BaseTextureHeight + BaseTextureLocationY;
    float2 maskCoord = float2((maskPixelX - MaskLocationX) / MaskWidth, (maskPixelY - MaskLocationY) / MaskHeight);
    float4 bitMask = tex2D(MaskTexture, maskCoord);

    float4 tex = tex2D(BaseTexture, texCoord);

    //It is a good idea to avoid conditional statements in a pixel shader if you can use math instead.
    return tex * (bitMask.a);
    //Alternate calculation to invert the mask, you could make this a parameter too if you wanted
    //return tex * (1.0 - bitMask.a);
}

C # 코드에서는 다음 SpriteBatch.Draw과 같이 호출 전에 변수를 픽셀 셰이더에 전달합니다 .

maskEffect.Parameters["MaskWidth"].SetValue(800);
maskEffect.Parameters["MaskHeight"].SetValue(600);

응답 해주셔서 감사합니다! 이것은 작동하는 것처럼 보이지만 현재 문제가 있습니다. 현재 이미지의 왼쪽에서 잘립니다 (가장자리가 똑바로 나타남). 위치에 화면 좌표를 사용해야합니까? 아니면 이미 0-1로 변환해야합니까?
electroflame 1

나는 문제를 본다. 픽셀 쉐이더를 공유하기 전에 컴파일해야했습니다. : P maskCoord계산에서 X 중 하나는 Y 여야합니다. 수정으로 초기 답변을 편집하고에 필요한 UV 클램프 설정을 포함 시켰습니다 MaskTexture sampler.
Jim

1
이제 완벽하게 작동합니다. 감사합니다! 내가 언급해야 할 유일한 것은 스프라이트를 중심에 둔 경우 (원점에 너비 / 2 및 높이 / 2 사용) X 및 Y 위치에 대해 너비 또는 높이의 절반을 빼야한다는 것입니다. 쉐이더). 그 후에는 완벽하게 작동하며 매우 빠릅니다! 정말 감사합니다!
electroflame

18

이미지 예

첫 번째 단계는 그래픽 카드에 스텐실 버퍼가 필요하다는 것을 알리는 것입니다. GraphicsDeviceManager를 만들 때이 작업을 수행하기 위해 PreferredDepthStencilFormat을 DepthFormat.Depth24Stencil8로 설정하여 실제로 쓸 스텐실이 있습니다.

graphics = new GraphicsDeviceManager(this) {
    PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8
};

AlphaTestEffect는 좌표계를 설정하고 알파 테스트를 통과 한 알파로 픽셀을 필터링하는 데 사용됩니다. 필터를 설정하지 않고 좌표계를 뷰 포트로 설정합니다.

var m = Matrix.CreateOrthographicOffCenter(0,
    graphics.GraphicsDevice.PresentationParameters.BackBufferWidth,
    graphics.GraphicsDevice.PresentationParameters.BackBufferHeight,
    0, 0, 1
);
var a = new AlphaTestEffect(graphics.GraphicsDevice) {
    Projection = m
};

다음으로 2 개의 DepthStencilState를 설정해야합니다. 이 상태는 SpriteBatch가 스텐실로 렌더링 될 때와 SpriteBatch가 BackBuffer로 렌더링 될 때를 나타냅니다. 우리는 주로 두 가지 변수 StencilFunction과 StencilPass에 관심이 있습니다.

  • StencilFunction은 SpriteBatch가 개별 픽셀을 그릴 때와 무시 될시기를 나타냅니다.
  • 스텐실 패스는 그린 픽셀 픽셀이 스텐실에 영향을 미치는시기를 나타냅니다.

첫 번째 DepthStencilState의 경우 StencilFunction을 CompareFunction으로 설정했습니다. 이로 인해 StencilTest는 성공하고 StencilTest는 SpriteBatch가 해당 픽셀을 렌더링합니다. StencilPass가 StencilOperation으로 설정되어 있습니다. StencilTest가 성공하면 해당 픽셀이 ReferenceStencil의 값으로 StencilBuffer에 기록됩니다.

var s1 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.Always,
    StencilPass = StencilOperation.Replace,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

요약하면 StencilTest는 항상 통과하고 이미지는 정상적으로 화면에 그려지며 화면에 그려진 픽셀의 경우 1 값이 StencilBuffer에 저장됩니다.

두 번째 DepthStencilState는 약간 더 복잡합니다. 이번에는 StencilBuffer의 값이있을 때만 화면에 그리려고합니다. 이를 달성하기 위해 StencilFunction을 CompareFunction.LessEqual로 설정하고 ReferenceStencil을 1로 설정합니다. 이는 스텐실 버퍼의 값이 1 일 때 StencilTest가 성공 함을 의미합니다. StencilPass를 StencilOperation으로 설정 유지하면 StencilBuffer가 업데이트되지 않습니다. 이를 통해 동일한 마스크를 사용하여 여러 번 그릴 수 있습니다.

var s2 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.LessEqual,
    StencilPass = StencilOperation.Keep,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

요약하면 StencilTest가 StencilBuffer가 1보다 작거나 (마스크의 알파 픽셀) StencilBuffer에 영향을 미치지 않는 경우에만 통과합니다.

이제 DepthStencilStates가 설정되었습니다. 실제로 마스크를 사용하여 그릴 수 있습니다. 첫 번째 DepthStencilState를 사용하여 마스크를 그리면됩니다. 이것은 BackBuffer와 StencilBuffer 모두에 영향을 미칩니다. 스텐실 버퍼의 값은 마스크의 투명도가 0이고 색상이 포함 된 1의 값을 가지므로 StencilBuffer를 사용하여 이후 이미지를 마스킹 할 수 있습니다.

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, a);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask                                   
spriteBatch.End();

두 번째 SpriteBatch는 두 번째 DepthStencilStates를 사용합니다. 무엇을 그리 든 StencilBuffer가 1로 설정된 픽셀 만 스텐실 테스트를 통과하고 화면에 그려집니다.

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, a);            
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();

아래는 Draw 메소드의 코드 전체입니다. 게임 생성자에서 PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8을 설정하는 것을 잊지 마십시오.

GraphicsDevice.Clear(ClearOptions.Target 
    | ClearOptions.Stencil, Color.Transparent, 0, 0);

var m = Matrix.CreateOrthographicOffCenter(0,
    graphics.GraphicsDevice.PresentationParameters.BackBufferWidth,
    graphics.GraphicsDevice.PresentationParameters.BackBufferHeight,
    0, 0, 1
);

var a = new AlphaTestEffect(graphics.GraphicsDevice) {
    Projection = m
};

var s1 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.Always,
    StencilPass = StencilOperation.Replace,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

var s2 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.LessEqual,
    StencilPass = StencilOperation.Keep,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, a);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask                                   
spriteBatch.End();

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, a);            
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();

1
+1 이것은 XNA 4.0에서 본 가장 완벽한 StencilBuffer 예제 중 하나입니다. 감사합니다! 내가 픽셀 쉐이더 답변을 선택한 유일한 이유는 설정하기가 더 쉽고 (실제로 부팅하기가 더 빠르기) 때문입니다. 그러나 이것은 완벽하게 유효한 답변이며 이미 많은 쉐이더가있는 경우 더 쉬울 수 있습니다. 감사! 이제 두 경로 모두에 대한 완전한 답을 얻었습니다!
electroflame 16

이것은 나를위한 최선의 진정한 해결책이며, 처음보다 더
메디 BUGNARD

3D 모델이 있다면 어떻게 사용할 수 있습니까? 이것이 내 질문 gamedev.stackexchange.com/questions/93733/…을 해결할 수 있다고 생각 하지만 3D 모델에 적용하는 방법을 모르겠습니다. 내가 할 수 있는지 아십니까?
dimitris93 2019

0

위의 답변 에서 , 내가 설명한대로 정확하게했을 때 이상한 일이 일어났습니다.

내가 필요한 유일한 것은 둘 다 DepthStencilStates였습니다.

var s1 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.Always,
    StencilPass = StencilOperation.Replace,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

var s2 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.LessEqual,
    StencilPass = StencilOperation.Keep,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

그리고 무첨가 AlphaTestEffect.

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, null);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask                                   
spriteBatch.End();

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, null);            
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();

그리고 예상대로 모든 것이 잘되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.