четверг, 3 мая 2012 г.

Simply Compute Shader Example

Одной из главных особенностей DirectX 11 является наличие Compute Shader. У меня появилась идея написать простой пример на эту тему. Прежде всего позвольте мне сказать, что вычислительные шейдеры являются чем-то потрясающим! Они открывают так много возможностей, что сразу теряешься в идеях, где бы их опробовать в действии. В этом примере будет показано, как создать Compute Shader и затем использовать его для запуска потоков, которые просто выведут идентификатор потока в текстуру.
 Device device = new Device(DriverType.Hardware, DeviceCreationFlags.Debug, FeatureLevel.Level_11_0);
ComputeShader compute = Helper.LoadComputeShader(device, "SimpleCompute.hlsl", "main"); Texture2D uavTexture;
UnorderedAccessView computeResult = Helper.CreateUnorderedAccessView(device, 1024, 1024, Format.R8G8B8A8_UNorm, out uavTexture);
device.ImmediateContext.ComputeShader.Set(compute);
device.ImmediateContext.ComputeShader.SetUnorderedAccessView(computeResult, 0);device.ImmediateContext.Dispatch(32, 32, 1);
Texture2D.ToFile(device.ImmediateContext, uavTexture, ImageFileFormat.Png, "uav.png");

Верьте или нет, но это весь код обработчика.

Вот что именно нужно сделать в коде:
1) Создать графическое устройство DX11, с тем чтобы использовать Compute Shaders 5.0
2) Загрузить/скомпилить код HLSL в объект ComputeShader.
3) Создать объект UnorderedAccesdView  1024 x 1024, который будет использоваться для хранения данных.
4) Передать ComputeShader и UnorderedAccesdView на графическое устройство.
5) Запустить ComputeShader путем вызова метода Dispatch (32 x 32 x 1).
6) Сохранить текстуру на жесткий диск.

Код HLSL еще проще:

RWTexture2D<float4> Output;

[numthreads(32, 32, 1)]
void main( uint3 threadID : SV_DispatchThreadID )
{
    Output[threadID.xy] = float4(threadID.xy / 1024.0f, 0, 1);
}

Как вы можете видеть объект RWTexture2D используется для хранения данных. Шейдер -настроен для запуска 32 x 32 x 1 потоков в группе потоков. Это означает, что процессор запустит 32 x 32 x 1 группу  потоков, то есть  (32x32) x (32x32) x 1 потоки запустятся по отдельности. Это равняется одному потоку на пиксел в выходных данных UAV. Так в UAV, цвет устанавливается равным идентификатору потока - результат работы этого кода приводится на следующем изображении :



Довольно просто, да? Но не очень интересно. Мы могли бы легко сделать что-то подобное с pixel Shader (хотя нам пришлось бы растрировать полноэкранный квад для этого).

Мы должны попытаться сделать то, что показывает силу вычислительного шейдера. Что-то, что нельзя было сделать в шейдерах раньше. Как насчет рисования некоторых примитивов, таких как линии или круги? Для рисования линий, давайте использовать алгоритм ЦДА. Он переводится на HLSL очень легко.

void Plot(int x, int y)
{
   Output[uint2(x, y)] = float4(0, 0, 1, 1);
}

void DrawLine(float2 start, float2 end)
{
    float dydx = (end.y - start.y) / (end.x - start.x);
    float y = start.y;
    for (int x = start.x; x <= end.x; x++)    
   {
        Plot(x, round(y));        y = y + dydx;   
   }
}

Для рисования кругов давайте использовать Midpoint Circle алгоритм. Для краткости я не буду обьяснять его сейчас. Для этого в моей главной функции CS, я просто добавлю этот код:

if (threadID.x == 1023 && threadID.y == 1023)
{
    DrawLine(float2(0, 0), float2(1024, 1024));
    DrawLine(float2(0, 1023), float2(1023, 0));
    DrawCircle(512, 512, 250);
    DrawCircle(0, 512, 250);
}

Результат этого кода приводится на следующем рисунке:



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

Комментариев нет:

Отправить комментарий

Physically Based Rendering