Cách tốt nhất để che dấu các họa tiết 2D trong XNA?


24

Tôi hiện đang cố gắng để che dấu một số sprite. Thay vì giải thích nó bằng lời, tôi đã tạo ra một số hình ảnh ví dụ:

Khu vực để mặt nạ Khu vực để mặt nạ (màu trắng)

Khu vực để mặt nạ, với hình ảnh để mặt nạ Bây giờ, sprite đỏ cần được cắt.

Kết quả cuối cùng Kết quả cuối cùng.

Bây giờ, tôi biết rằng trong XNA, bạn có thể thực hiện hai điều để thực hiện việc này:

  1. Sử dụng bộ đệm stear.
  2. Sử dụng Pixel Shader.

Tôi đã cố gắng thực hiện một pixel shader, về cơ bản đã làm điều này:

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

Điều này dường như cắt xén hình ảnh (mặc dù, không chính xác một khi hình ảnh bắt đầu di chuyển), nhưng vấn đề của tôi là hình ảnh liên tục chuyển động (chúng không tĩnh), vì vậy việc cắt xén này cần phải linh hoạt.

Có cách nào để tôi có thể thay đổi mã shader để tính đến vị trí của nó không?


Ngoài ra, tôi đã đọc về việc sử dụng Bộ đệm stent, nhưng hầu hết các mẫu dường như xoay quanh việc sử dụng một mục tiêu kết xuất, điều mà tôi thực sự không muốn làm. (Tôi đã sử dụng 3 hoặc 4 cho phần còn lại của trò chơi và thêm một số khác vào đầu trò chơi có vẻ quá mức cần thiết)

Hướng dẫn duy nhất tôi thấy rằng không sử dụng Rendertarget là một từ blog của Shawn Hargreaves ở đây. Tuy nhiên, vấn đề với vấn đề đó là XNA 3.1 và dường như không dịch tốt cho XNA 4.0.

Dường như với tôi rằng pixel shader là con đường để đi, nhưng tôi không chắc làm thế nào để định vị chính xác. Tôi tin rằng tôi sẽ phải thay đổi tọa độ trên màn hình của mình (đại loại như 500, 500) thành từ 0 đến 1 cho tọa độ đổ bóng. Vấn đề duy nhất của tôi là cố gắng tìm ra cách sử dụng chính xác các tọa độ được chuyển đổi.

Cảm ơn trước sự giúp đỡ nào!


Mặc dù vậy, stent dường như là con đường để đi, tôi cũng cần biết cách sử dụng chúng đúng cách: P Tôi sẽ chờ đợi một câu trả lời tốt về vấn đề này.
Gustavo Maciel

Heh, hầu hết các hướng dẫn tốt về Macintosh đều dành cho XNA 3.1 và hầu hết các hướng dẫn 4.0 đều sử dụng RenderTarget (có vẻ lãng phí đối với tôi). Tôi đã cập nhật câu hỏi của mình với một liên kết đến một hướng dẫn 3.1 cũ có vẻ như nó có thể hoạt động, nhưng không phải trong 4.0. Có lẽ ai đó biết làm thế nào để dịch nó sang 4.0 chính xác?
Electroflame

Nếu bạn sử dụng pixel shader để thực hiện nó, tôi nghĩ bạn sẽ phải hủy tọa độ pixel của mình thành màn hình / thế giới (tùy thuộc vào những gì bạn muốn làm) và điều này thật lãng phí (stprint sẽ không gặp vấn đề này)
Gustavo Maciel

Câu hỏi này là một câu hỏi xuất sắc.
ClassicThunder

Câu trả lời:


11

Bạn có thể sử dụng phương pháp tiếp cận pixel shader của mình nhưng nó không đầy đủ.

Bạn cũng sẽ cần phải thêm một số tham số vào nó để thông báo cho trình đổ bóng pixel nơi bạn muốn đặt stprint của mình.

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

Trong mã C # của bạn, bạn chuyển các biến cho trình đổ bóng pixel trước SpriteBatch.Drawcuộc gọi như sau:

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

Cảm ơn bạn đã trả lời! Điều này có vẻ như nó sẽ hoạt động, nhưng hiện tại tôi đang gặp vấn đề với nó. Hiện tại, nó đang được cắt ở bên trái của hình ảnh (cũng có cạnh thẳng). Tôi có nên sử dụng tọa độ màn hình cho các vị trí không? Hay tôi đã phải chuyển đổi chúng thành 0 - 1?
Electroflame

Tôi thấy vấn đề. Tôi nên đã biên dịch pixel shader trước khi chia sẻ nó. : P Trong maskCoordphép tính, một trong số X phải là Y. Tôi đã chỉnh sửa câu trả lời ban đầu của mình bằng cách khắc phục và bao gồm các cài đặt kẹp UV mà bạn sẽ cần cho MaskTexture sampler.
Jim

1
Bây giờ nó hoạt động hoàn hảo, cảm ơn bạn! Điều duy nhất tôi nên đề cập là nếu bạn đặt các họa tiết của mình ở giữa (sử dụng Width / 2 và height / 2 cho gốc), bạn sẽ cần phải trừ một nửa chiều rộng hoặc Chiều cao cho các vị trí X và Y của bạn (mà bạn chuyển vào người đổ bóng). Sau đó, nó hoạt động hoàn hảo, và cực kỳ nhanh chóng! Cảm ơn rất nhiều!
Electroflame

18

hình ảnh ví dụ

Bước đầu tiên là nói với card đồ họa chúng ta cần bộ đệm stpson. Để thực hiện điều này khi bạn tạo GraphicsDeviceManager, chúng tôi đặt PreferredDepthStpsonFormat thành DepthFormat.Depth24Stpson8 để thực sự có một stprint để viết.

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

AlphaTestEffect được sử dụng để đặt hệ tọa độ và lọc các pixel bằng alpha vượt qua thử nghiệm alpha. Chúng tôi sẽ không đặt bất kỳ bộ lọc nào và đặt hệ tọa độ thành cổng xem.

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

Tiếp theo chúng ta cần thiết lập hai DepthStpsonStates. Các trạng thái này ra lệnh khi SpriteBatch kết xuất với stprint và khi SpriteBatch kết xuất lại BackBuffer. Chúng tôi chủ yếu quan tâm đến hai biến stifFeft và stifPass.

  • SttFeft ra lệnh khi SpriteBatch sẽ vẽ các pixel riêng lẻ và khi nào chúng sẽ bị bỏ qua.
  • MacintoshPass ra lệnh khi các pixel được vẽ có hiệu ứng pixel.

Đối với DepthStpsonState đầu tiên, chúng tôi đặt sttFeft thành so sánh. Điều này làm cho sttTest thành công và khi sttTest SpriteBatch hiển thị pixel đó. MacintoshPass được đặt thành sttOperation. Thay thế có nghĩa là khi stentTest thành công, pixel đó sẽ được ghi vào sttBuffer bằng giá trị của ReferenceStpson.

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

Tóm lại, sttTest luôn luôn vượt qua, hình ảnh được vẽ lên màn hình bình thường và đối với các pixel được vẽ trên màn hình, giá trị 1 được lưu trữ trong sttBuffer.

DepthStpsonState thứ hai phức tạp hơn một chút. Lần này, chúng tôi chỉ muốn vẽ lên màn hình khi giá trị trong sttBuffer là. Để đạt được điều này, chúng ta đã đặt sttFeft thành so sánhFess.LessEqual và ReferenceStpson thành 1. Điều này có nghĩa là khi giá trị trong bộ đệm stprint là 1 thì sttTest sẽ thành công. Thiết lập sttPass thành sttOperation. Giữ nguyên nhân khiến cho sttBuffer không cập nhật. Điều này cho phép chúng ta vẽ nhiều lần bằng cùng một mặt nạ.

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

Tóm lại, sttTest chỉ vượt qua khi sttBuffer nhỏ hơn 1 (các pixel alpha từ mặt nạ) và không ảnh hưởng đến sttBuffer.

Bây giờ chúng ta đã thiết lập DepthStpsonStates. Chúng ta thực sự có thể vẽ bằng mặt nạ. Đơn giản chỉ cần vẽ mặt nạ bằng DepthStpsonState đầu tiên. Điều này sẽ ảnh hưởng đến cả BackBuffer và MacintoshBuffer. Bây giờ, bộ đệm stpson có giá trị 0 trong đó mặt nạ của bạn có độ trong suốt và 1 nơi chứa màu, chúng ta có thể sử dụng sttBuffer để che các hình ảnh sau này.

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

SpriteBatch thứ hai sử dụng DepthStpsonStates thứ hai. Bất kể bạn vẽ gì, chỉ những pixel mà stifBuffer được đặt thành 1 sẽ vượt qua bài kiểm tra stprint và được vẽ lên màn hình.

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

Dưới đây là toàn bộ mã trong phương thức Draw, đừng quên đặt PreferredDepthStpsonFormat = DepthFormat.Depth24Stpson8 trong trình xây dựng trò chơi.

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 Đây là một trong những ví dụ đầy đủ nhất về MacintoshBuffer mà tôi đã thấy cho XNA 4.0. Cảm ơn vì điều đó! Lý do duy nhất tôi chọn câu trả lời pixel shader là vì nó dễ cài đặt hơn (và thực sự nhanh hơn để khởi động), nhưng đây cũng là một câu trả lời hoàn toàn hợp lệ và có thể dễ dàng hơn nếu bạn đã có nhiều shader. Cảm ơn! Bây giờ chúng tôi đã có một câu trả lời hoàn chỉnh cho cả hai tuyến đường!
Electroflame

Đây là giải pháp thực sự tốt nhất cho tôi và hơn cả giải pháp đầu tiên
Mehdi Bugnard

Làm thế nào tôi có thể sử dụng nó nếu tôi có một mô hình 3D? Tôi nghĩ rằng điều này có thể giải quyết câu hỏi của tôi gamedev.stackexchange.com/questions/93733/ , nhưng tôi không biết làm thế nào để áp dụng nó trên các mô hình 3D. Bạn có biết nếu tôi có thể làm điều đó?
dimitris93

0

Trong câu trả lời ở trên , một số điều kỳ lạ đã xảy ra khi tôi làm đúng như được giải thích.

Điều duy nhất tôi cần là cả hai 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,
};

Và rút ra mà không có 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();

Và mọi thứ đều ổn như mong đợi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.