Проблемы с XNA + Pixel Shader

Я написал базовый двухмерный пиксельный шейдер, и мне кажется, что он не работает. Если я рисую с активным эффектом, на экране ничего не отображается. Но если я отключу его, текстура будет отображаться на экране, как и ожидалось.

Моя цель - нарисовать на экране произвольную текстуру, а затем заставить этот пиксельный шейдер «вырезать» из нее круглые куски пикселей для использования в системе наложения для диапазонов и тому подобного.

Вот мой код пиксельного шейдера:

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 psGraphicsDevice.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);
0 RenderSolidCircles(); } } float4 PassThrough(float2 texCoord : TEXCOORD0) : COLOR { return tex2D(TextureSampler, texCoord); } technique PassThrough { pass P0 { PixelShader = compile psGraphicsDevice.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); 0 PassThrough(); } }

Вот ASM-версия техники SolidCircles:

//
// 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

    psGraphicsDevice.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);
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)

и вот соответствующая часть моей функции Draw в моем классе Game:

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

Positions и Radii - это списки векторов и чисел с плавающей запятой, соответственно, размером 64. pixTex - это сплошная черная текстура размером 1 пиксель.

Почему не работает шейдер?


person RCIX    schedule 21.07.2010    source источник
comment
Просто для здравомыслия: ваша текстура действительно имеет размер 1px на 1px со значением Color (0,0,0,255), верно?   -  person Andrew Russell    schedule 22.07.2010
comment
Абсолютно верно: я редактировал строки, из которых он состоит.   -  person RCIX    schedule 23.07.2010


Ответы (3)


Поэтому в комментариях к этому ответу было определено, что еще один проблема в том, что вершинный шейдер для Sprite Batch в XNA 3.1 - vs_1_1. И строго говоря вы не можете смешивать SM 3.0 вершинные или пиксельные шейдеры с шейдерами разных версий. Кажется, что на практике большинство карт позволят вам избежать неприятностей, но, очевидно, карта RCIX (Radeon HD 4850) - нет.

(Вот почему стоит иметь под рукой среды выполнения отладки DirectX (из SDK), так как они будут предупреждать вас о подобных вещах. Вы можете использовать DebugView, чтобы просмотреть его вывод.)

Есть несколько решений этой проблемы:

1) Безусловно, самым простым решением является обновление до XNA 4.0 (недостатком являются критические изменения и тот факт, что в настоящее время он находится только в стадии бета-тестирования). В этой версии XNA вы можете легко указать свой собственный вершинный шейдер для SpriteBatch.

2) Вы можете использовать собственный вершинный шейдер с SpriteBatch в XNA 3.1, это не так просто. Хорошей отправной точкой будет исходный код вершинного шейдера, используемого XNA (до XNA 4.0, см. выше).

3) Наконец: возможно, вы могли бы просто использовать в шейдере ps_2_0? Вам действительно нужно 64 вырезки на текстуру?

person Andrew Russell    schedule 24.07.2010
comment
В итоге я разделил его на 5 вырезов на выполнение и развернул цикл вручную (что позволило мне втиснуть его в пиксельный шейдер 2). Так будет, пока я не доберусь до XNA 4 :) - person RCIX; 25.07.2010

Хорошо, прежде всего вам нужно применить два исправления в моем другом ответе (завершите пакет до завершения эффекта и (из комментариев) используйте SpriteBlendMode.AlphaBlend).

Теперь - проблема в вашем шейдере. Это действительно работает - но, возможно, не так, как вы ожидаете. Похоже, вы путаете координаты экранного пространства, в которых нарисован ваш спрайт (ширина = 500, высота = 500), с координатами текстурного пространства, в которых работает ваш пиксельный шейдер (ширина = 1, высота = 1).

Итак, прежде всего - когда вы вырезаете дыры в своем спрайте, вам нужно делать это в пространстве текстур, например:

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 на 500. Вы должны передать фактическое значение в качестве параметра шейдера.

Если вы собираетесь рисовать свой спрайт неквадратным, вам нужно проделать небольшую дополнительную математику, чтобы системы координат «выровнялись». Я оставлю это как упражнение.

person Andrew Russell    schedule 22.07.2010
comment
Спасибо за помощь! Я внедряю предложенные вами изменения и прокомментирую результаты. :) - person RCIX; 22.07.2010
comment
вздох он все еще не работает. Я снова попробовал пройти через шейдер, и это не сработало, как и мой обновленный код для шейдера наложения. Я обновлю вопрос своим последним кодом, чтобы вы могли проверить, что я пропустил. - person RCIX; 22.07.2010
comment
@RCIX: Насколько я могу судить (поместив его в новый проект XNA и запустив его), ваш обновленный код работает. Однако вы установили настолько большие радиусы, чтобы текстуру можно было полностью вырезать. Установите меньшие радиусы (или используйте меньше кругов), и все будет в порядке. Между прочим: вы можете посмотреть на PIX из DirectX SDK для отладки такого рода проблем. - person Andrew Russell; 22.07.2010
comment
Это безумие. Я сократил количество кругов до 5, уменьшил их радиус, но это все равно не работает. Я собираюсь поохотиться, как другой код рисует эффекты. - person RCIX; 23.07.2010
comment
Я знаю, что это не рисунок, потому что я отключил вычитание непрозрачности (что должно привести к прямому рендерингу текстуры), и я все еще ничего не получаю. Может быть, если бы вы могли загрузить свою версию проекта для меня? Буду очень признателен. - person RCIX; 23.07.2010
comment
@RCIX: я разместил его на andrewrussell.net/junk/3295418.zip для время. Но это в значительной степени свежий проект XNA, в который скопирован ваш код (и я сделал круги меньше). - person Andrew Russell; 23.07.2010
comment
Я попробовал этот проект на своем компьютере, но он все еще не работает. Однако, когда друг протестировал его на своем ноутбуке, он работал нормально. У меня Radeon HD 4850, а у него nVidia Quadro NVS 3100M. Почему это должно работать на вашей и его, но не на моей системе? - person RCIX; 23.07.2010
comment
@RCIX: Я не уверен. На этом этапе я бы загрузил DirectX SDK и: попробовал его со средой выполнения отладки DirectX (и посмотрел, выводит ли он какие-либо предупреждения в DebugView), попробовал его в PIX, попробовал скомпилировать шейдер с fxc и посмотреть, получите ли вы какие-либо предупреждения, также попробуйте скомпилировать его на сборку (или разобрать с помощью ShaderCompiler.Disassemble) и поищите проблемы. Я бы сказал, что ваш шейдер может превышать ограничения некоторых устройств (слишком много инструкций / циклов / регистров / чего угодно). И если все это не сработает, возможно, стоит связаться с Radeon. - person Andrew Russell; 23.07.2010
comment
Я проверил asm-код шейдера для своего шейдера, и ничего не выглядело неуместным. Я пробовал fxc, но он не жаловался на основной метод. Пикс говорит, что шейдер настраивается, так что это работает. Я выложил обрезанный пиксельный шейдер ASM, может, это поможет. Я также проверил максимальное количество слотов инструкций пиксельного шейдера 3, которое составляет 32 768 (так что у меня нет этой проблемы) - person RCIX; 23.07.2010
comment
FWIW, образец не работает и на моей Radeon HD 5750. Я просто получаю экран CornflowerBlue. - person jasonh; 23.07.2010
comment
Думаю, это как-то связано с поддержкой пиксельных шейдеров 3.0. Если я переключу его на метод PassThrough, я снова получу только CornflowerBlue. Но если я изменю PassThrough на compile ps_2_0 PassThrough(), я получу следующее: i29.tinypic.com/2q07sqt.png - person jasonh; 23.07.2010
comment
Спасибо за вашу помощь! После того, как я включил отладку и получил debugview, он пожаловался, что мне не нравится, что я использую шейдер пиксельного шейдера 3 с шейдером вершинного шейдера 1.1 (который xna использует по умолчанию для рендеринга треугольников, составляющих экран). Единственное решение, которое я могу придумать, - это создать собственный вершинный шейдер. Однако мне очень хотелось избежать этого, если возможно ... - person RCIX; 24.07.2010

Перед звонком effect.End() вам необходимо позвонить spriteBatch.End().

Причина этого (и тот факт, что это исправлено в XNA 4.0) описана в эта статья в блоге Шона Харгривза. (Эту запись тоже стоит прочитать.)

В основном (в XNA 3.1): SpriteSortMode.Immediate происходит не так быстро, как вы могли бы ожидать. Вам нужно вызвать spriteBatch.End(), чтобы отправить последнюю партию спрайтов в GPU до завершения эффекта.

Пример Sprite Effects показывает, как правильно применять эффекты к спрайтам.

person Andrew Russell    schedule 21.07.2010
comment
Возможно, это была одна проблема, но, похоже, не единственная. : / - person RCIX; 21.07.2010
comment
Я попробовал пройти через шейдер напрямую, но ничего не вышло, см. Мое обновление. - person RCIX; 21.07.2010
comment
Примечание не по теме: я действительно купил и полюбил вашу игру! :) - person RCIX; 21.07.2010
comment
@RCIX: ‹3 - Также - я обнаружил еще одну ошибку: SpriteBlendMode.None должно быть AlphaBlend. Я все еще не уверен, что это ваша единственная проблема, но это может привести к тому, что ваш эффект не сработает так, как ожидалось. - person Andrew Russell; 21.07.2010
comment
Я новичок в этом, но разве не должно быть эффекта. Вызов Begin должен предшествовать вызову SpriteBatch.Begin? - person jasonh; 21.07.2010
comment
@ Эндрю Рассел: спасибо, но я пробовал это, и если это была проблема, то она тоже была не единственной :( - person RCIX; 22.07.2010
comment
Хорошо, сдвинув spriteBatch.Begin за пределы метода begin overlayEffect, я получил что-то отрендерить. Однако код, похоже, не дает желаемого эффекта (я просто получаю сплошную черную текстуру без дырок), вы можете сказать, почему? - person RCIX; 22.07.2010
comment
@jasonh: Нет. spriteBatch.Begin() установит различные состояния рендеринга, включая пиксельный шейдер. Вам нужно вызвать effect.Begin() second, чтобы его настройки действовали во время рендеринга. Да, этот API не интуитивно понятен - он исправлен в XNA 4.0 (см. Ссылки, которые я опубликовал). - person Andrew Russell; 22.07.2010
comment
@RCIX: См. Мой ответ jasonh - то, что вы делаете, имеет тот же эффект, что и вовсе не использовать этот эффект. - person Andrew Russell; 22.07.2010