【问题标题】:XNA + Pixel Shader DifficultiesXNA + 像素着色器的困难
【发布时间】:2010-07-21 00:20:17
【问题描述】:

我已经编写了一个基本的 2d 像素着色器,但我似乎无法让它工作。如果我在激活效果的情况下进行绘制,那么屏幕上不会出现任何内容。但是如果我禁用它,那么纹理会按预期绘制到屏幕上。

我的目标是能够在屏幕上绘制任意纹理,然后让这个像素着色器“雕刻”出圆形像素块,用于范围等的覆盖系统。

这是我的像素着色器代码:

sampler TextureSampler : register(s0);
//A list of positions for circles. They are specified in texture space rather than screen space.
float2 PositionData[64];
//A matching list of radiuses. These are specified in pixels, though.
float Radii[64];
//how much of the array is filled with data.
int DataSize;
//the size of the texture being drawn.
float2 TextureSize;

float4 RenderSolidCircles(float2 texCoord : TEXCOORD0) : COLOR
{
    float opacityAcc = 1;
    float2 screenSpaceTexCoord = texCoord * TextureSize;
    for (int i = 0; i < DataSize; i++)
    {
        float2 properPosCoordinate = PositionData[i] * TextureSize;
        float dist = length(screenSpaceTexCoord - properPosCoordinate) - Radii[i];
        if (dist < 0)
        {
            opacityAcc -= min(abs(dist), 1);
        }
    }
    opacityAcc = max(0, opacityAcc);
    float4 outPix = tex2D(TextureSampler, texCoord);
    outPix.a *= opacityAcc;
    return outPix;
}


technique SolidCircles
{
    pass P0
    {
        PixelShader = compile ps_3_0 RenderSolidCircles();
    }
}

float4 PassThrough(float2 texCoord : TEXCOORD0) : COLOR
{
    return tex2D(TextureSampler, texCoord);
}
technique PassThrough
{
    pass P0
    {
        PixelShader = compile ps_3_0 PassThrough();
    }
}

这是 SolidCircles 技术的 ASM 版本:

//
// Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
//
// Parameters:
//
//   int DataSize;
//   float2 PositionData[64];
//   float Radii[64];
//   sampler2D TextureSampler;
//   float2 TextureSize;
//
//
// Registers:
//
//   Name           Reg   Size
//   -------------- ----- ----
//   PositionData   c0      64
//   Radii          c64     64
//   DataSize       c128     1
//   TextureSize    c129     1
//   TextureSampler s0       1
//
//
// Default values:
//snipped comments here

    ps_3_0
    def c130, 1, 0, -1, 2
    def c131, 3, 4, 5, 6
    def c132, 7, 8, 9, 10
    def c133, 11, 12, 13, 14
    def c134, 15, 16, 17, 18
    def c135, 19, 20, 21, 22
    def c136, 23, 24, 25, 26
    def c137, 27, 28, 29, 30
    def c138, 31, 32, 33, 34
    def c139, 35, 36, 37, 38
    def c140, 39, 40, 41, 42
    def c141, 43, 44, 45, 46
    def c142, 47, 48, 49, 50
    def c143, 51, 52, 53, 54
    def c144, 55, 56, 57, 58
    def c145, 59, 60, 61, 62
    def c146, 63, 0, 0, 0
    dcl_texcoord v0.xy  // texCoord<0,1>
    dcl_2d s0

#line 22 "C:\Users\RCIX\Documents\Visual Studio 2008\Projects\2DFXFilesTest\2DFXFilesTest\Content\OverlayFx.fx"
    mov r0.w, c130.x  // opacityAcc<0>
    mul r2.xy, v0, c129  // screenSpaceTexCoord<0,1>
    mov r5.w, -c128.x
    add r0.z, r5.w, c130.y
    cmp r12.w, r0.z, c130.y, c130.x
    mul r11.w, r12.w, c130.x
    if_ne r11.w, -r11.w
      mov r13.xy, c129  // ::TextureSize<0,1>
      mul r12.xy, r13, c0  // properPosCoordinate<0,1>
      mov r12.xy, -r12
      add r11.xy, r2, r12
      mul r16.xy, r11, r11
      add r11.z, r16.x, r16.y
      rsq r10.w, r11.z
      rcp r8.w, r10.w
      mov r9.w, -c64.x
      add r4.w, r8.w, r9.w  // dist<0>
      add r7.w, r4.w, c130.y
      cmp r6.w, r7.w, c130.y, c130.x
      mov r3.w, -r4.w
      mov r5.z, -r3.w
      add r1.w, r4.w, r5.z
      cmp r15.w, r1.w, r4.w, r3.w
      add r14.w, r15.w, c130.z
      cmp r2.w, r14.w, c130.x, r15.w
      mov r2.w, -r2.w
      add r13.w, r2.w, c130.x  // opacityAcc<0>
      mov r6.w, -r6.w
      cmp r0.w, r6.w, r0.w, r13.w  // opacityAcc<0>

#line 24
    endif

//snipped 63 blocks of unrolled loop code

#line 33
    mov r1.w, -r0.w
    add r15.w, r1.w, c130.y
    cmp r14.w, r15.w, c130.y, r0.w  // opacityAcc<0>
    texld r0, v0, s0  // outPix<0,1,2,3>
    mul r2.x, r14.w, r0.w  // outPix<3>
    mov oC0.xyz, r0  // ::RenderSolidCircles<0,1,2>
    mov oC0.w, r2.x  // ::RenderSolidCircles<3>

// approximately 1866 instruction slots used (1 texture, 1865 arithmetic)

这是我的 Game 类中 Draw 函数的相关部分:

GraphicsDevice.Clear(Color.CornflowerBlue);
overlayEffect.Parameters["PositionData"].SetValue(Positions.ToArray());
overlayEffect.Parameters["Radii"].SetValue(Radii.ToArray());
overlayEffect.Parameters["DataSize"].SetValue(64);
overlayEffect.Parameters["TextureSize"].SetValue(new Vector2(500));

spriteBatch.Begin(SpriteBlendMode.AlphaBlend,
    SpriteSortMode.Immediate,
    SaveStateMode.None);

overlayEffect.Begin();
overlayEffect.CurrentTechnique.Passes[0].Begin();

spriteBatch.Draw(pixTex, new Rectangle(0, 0, 500, 500), Color.White);
spriteBatch.End();

overlayEffect.CurrentTechnique.Passes[0].End();
overlayEffect.End();

base.Draw(gameTime);

最后,这是我构建位置和半径列表的函数:

private void RebuildPositionsList()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    Positions = new List<Vector2>();
    Radii = new List<float>();
    for (int i = 0; i < 64; i++)
    {
        Positions.Add(
            new Vector2(
                (float)r.NextDouble(),
                (float)r.NextDouble())
                );
        Radii.Add(((float)r.NextDouble() * 100) + 40);
    }
}

构成我纹理的线条:

pixTex = new Texture2D(GraphicsDevice, 1, 1);
pixTex.SetData<Color>(new Color[] { new Color(0f, 0f, 0f, 1f) });

PositionsRadii 分别是向量列表和浮点数列表,大小为 64。pixTex 是 1 像素纯黑色纹理。

为什么着色器不起作用?

【问题讨论】:

  • 只是为了理智:你的纹理真的是 1px x 1px 的颜色值 (0,0,0,255),对吧?
  • 绝对:我在制作它的行中进行了编辑。

标签: xna effects hlsl pixel-shader


【解决方案1】:

所以在this answer 的 cmets 中确定还有一个问题是 XNA 3.1 中 Sprite Batch 的顶点着色器是 vs_1_1。并且strictly speaking 您不能将 SM 3.0 顶点或像素着色器与不同版本的着色器混合使用。看来,实际上,大多数显卡都可以让你侥幸逃脱,但显然 RCIX 的显卡(Radeon HD 4850)不会。

(这就是为什么值得拥有 DirectX 调试运行时(来自 SDK)的原因,因为它们会警告您此类事情。您可以使用 DebugView 查看其输出。)

这个问题有很多解决方案:

1) 到目前为止,最简单的解决方案是升级到 XNA 4.0(缺点是重大更改以及它目前仅处于测试阶段的事实)。在这个版本的 XNA 中你可以easily specify your own vertex shader for SpriteBatch

2) 您可以在 XNA 3.1 中使用带有 SpriteBatch 的自定义顶点着色器,但这并不容易。一个好的起点是source code for the vertex shader used by XNA(直到 XNA 4.0,见上文)。

3) 最后:也许您可以让您的着色器使用 ps_2_0?每个纹理真的需要 64 个切口吗?

【讨论】:

  • 我最终将其切成每次执行 5 个切口,并手动展开循环(这使我可以将其塞入像素着色器 2)。直到我可以到达 XNA 4 :)
【解决方案2】:

好的,首先你需要在我的other answer 中应用两个修复(在结束效果之前结束批处理,并且(来自 cmets)使用SpriteBlendMode.AlphaBlend)。

现在 - 您的着色器中的问题。它实际上正在工作 - 但可能不是您期望的方式。您似乎混淆了您的精灵在(宽度 = 500,高度 = 500)处绘制的屏幕空间坐标与像素着色器在其中工作的纹理空间坐标(宽度 = 1,高度 = 1)。

所以首先 - 当你在你的 sprite 中打洞时,你需要在纹理空间中进行,如下所示:

Positions.Add(new Vector2(0.5f, 0.5f));
Radii.Add(0.25f);

Positions.Add(new Vector2(0.25f, 0.25f));
Radii.Add(0.1f);

其次,看起来像抗锯齿的尝试会导致您的剪裁更像是淡出。它需要考虑纹理在屏幕上绘制时的大小。最简单的解决方法是更改​​此行:

opacityAcc -= min(abs(dist), 1);

到这里:

opacityAcc -= min(abs(dist * 500), 1);

当然 - 这假设您的精灵是在 500 x 500 处绘制的。您应该将实际值作为着色器参数传递。

如果您要绘制非正方形的精灵,那么您需要做一些额外的数学运算以使坐标系“对齐”。我会把它留作练习。

【讨论】:

  • 感谢所有帮助!我正在实施您建议的更改,并将对结果发表评论。 :)
  • sigh 还是不行。我再次尝试通过着色器,但没有成功,我修改的覆盖着色器代码也没有。我会用我最新的代码更新问题,让你检查我错过了什么。
  • @RCIX:据我所知(通过将其放入新的 XNA 项目并运行它),您更新的代码正在运行。但是,您已将半径设置得如此之大,以至于纹理很容易被完全雕刻出来。将半径设置得更小(或使用更少的圆圈),你应该没问题。顺便说一句:您可能想查看 DirectX SDK 中的 PIX 以调试此类问题。
  • 这太疯狂了。我将圈数减少到 5 个,减小了 thair 半径,但它仍然不起作用。我要去寻找其他代码如何绘制效果。
  • 我知道它不是在绘图,因为我禁用了不透明度减法(这应该会导致纹理的直接渲染),但我仍然一无所获。也许您可以为我上传您的项目版本?我将不胜感激。
【解决方案3】:

在拨打effect.End()之前,您需要先拨打spriteBatch.End()

this article on Shawn Hargreaves' blog 中描述了这个原因(以及它在 XNA 4.0 中“修复”的事实)。 (This entry 可能也值得一读。)

基本上(在 XNA 3.1 中):SpriteSortMode.Immediate 并不像您预期​​的那样直接。您需要调用 spriteBatch.End() 将最后一批精灵实际推送到 GPU,结束效果之前。

Sprite Effects sample 展示了如何正确地将效果应用于精灵。

【讨论】:

  • 这可能是一个问题,但似乎并不是唯一的问题。 ://
  • 我尝试了直接通过着色器,但它不起作用,请参阅我的更新。
  • 题外话:我真的购买并喜欢你的游戏! :)
  • @RCIX: SpriteBlendMode.None 应该是 AlphaBlend。我仍然不确定这是您唯一的问题 - 但这会导致您的效果无法按预期工作。
  • 我是新手,但 effect.Begin 调用不应该在 SpriteBatch.Begin 调用之前吗?
猜你喜欢
  • 2012-09-09
  • 2011-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多