Шум Перлина на графическом процессоре Часть 1

Шум Перлина на графическом процессоре
(статья Patrick McCarthy)

Введение

Если Вы когда-либо сталкивались с процедурной генерацией в целом, то, возможно, Вы уже слышали о Шуме Перлина. Для тех, кто не знает, это разновидность градиента шума, которая была выявлена Кеном Перлином. Самый простой способ описать данное явление, это, грубо говоря, соотнести его с генератором псевдослучайных чисел. Он распространяется и таким образом каждый раз выдает один и тот же результат, что весьма привлекательно.

Шум Перлина становится намного более значимым, когда его результаты собраны вместе и просуммированы. Существует несколько способов, чтобы осуществить это, и один из таких способов называется фрактальным Броуновским движением или fBm. Результаты этого явления могут быть использованы во многих ситуациях, в том числе при создании эффектов облаков, воды и земли.

Существует много вариантов реализации Шума Перлина и fBm на центральном процессоре. Однако очень мало вариантов подобной реализации на графическом процессоре. Графический процессор намного лучше центрального по части параллельного представления большого количества подобных вычислений. Следовательно, реализация Шума Перлина на графическом процессоре происходит намного быстрее, чем на центральном.

Я не буду дальше в деталях объяснять суть Шума Перлина, так как это выходит за рамки данной статьи. В открытом доступе существует множество других статьей, описывающих основы Шума Перлина, с которыми Вы можете ознакомиться, если желаете лучше разобраться в данном вопросе.

Я бы порекомендовал тщательно изучить 5 главу из первой книги GPU Gems. В этой главе сам Кен Перлин пишет о своих разработках и реализации данного процесса, а также об улучшениях в реализации, произошедших с 1985 по 2002 год. Данная книга доступна для онлайн-чтения на веб-сайте Nvidia.

Книга: http://developer.nvidia.com/object/GPU_Gems_Home.html

Глава 5: http://http.developer.nvidia.com/GPUGems/gpugems_ch05.html

Реализация Nvidia

Чтобы не разбирать все наспех, давайте возьмем реализацию Шума Перлина через HLSL, которое уже использовала Nvidia, а затем «наладила» под XNA, что нам и нужно. Эта реализация была написана для главы 26 второй книги GPU Gems, и напрямую связана с главой 5 книги, упомянутой ранее. Тогда как Кен Перлин просто рассуждает о переносе Шума Перлина на графический процессор, Симон Грин представляет полную версию реализации двух версий: оригинальной 1985 года и улучшенной 2002 года. Книга вторая также доступна для онлайн чтения на веб-сайте Nvidia.

Книга: http://developer.nvidia.com/object/gpu_gems_2_home.html

Глава 26: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter26.html

Шум Перлина HLSL:

http://http.download.nvidia.com/developer/GPU_Gems_2/CD/Content/26.zip

Наибольший интерес представляет zip-файл inoise.fxh. Он содержит реализации как оригинального Шума Перлина, так и улучшенной его версии, которая быстрее оригинальной. У нас вызывает особый интерес улучшенная версия, однако, очень хорошо, что Nvidia оставила реализацию оригинальной версии, так как благодаря этому, если у Вас есть желание, есть возможность сравнить скорости. Лично я предпочел убрать их из примеров, чтобы было все проще и наглядней. Файл также содержит методы вычисления и суммирования Шума Перлина — фрактальное Броуновское движение (fBm), градиент (производную), турбуленцию, хребтообразный и многофрактальный хребтообразный. Они невероятно полезны при создании различных моделей.

Nvidia также предоставила 4D реализацию улучшенной версии Шума Перлина, что очень удобно при создании «анимационного» шума, где время используется как четвертое измерение.

Текстурная генерация в С#

Благодаря всему, что предоставила Nvidia, Вы можете задаться вопросом, а какие дополнительные задачи требуется решить, чтобы это работало в XNA. Как Вы увидите далее, существует несколько механизмов наладки с целью применение шума именно к XNA. Я также покажу несколько улучшений, которые докажут полезность этого.

Как Вы уже могли где-либо читать, Шум Перлина требует таблицу переменных и таблицу градиентов для точности расчетов. К сожалению, мы ограничены в количестве постоянной памяти в HLSL, поэтому мы не можем создать массив (матрицу) подобно тому, как это возможно в С++ или в С#. Мы вынуждены создавать текстуры, которые содержат конкретные данные.

В реализации, которую представила Nvidia, используются функциональные возможности в DirectX, под названием «Fill Texture», которые позволяют разработчикам писать специальные шейдеры, которые наполняют текстуру данными. Однако, данная функциональная возможность не реализуется в XNA. Вместо этого приходится создавать текстуры с помощью программирования в С#, а затем пересылать их в графический процессор.

Это довольно простая процедура, в основном состоящая из копирования кода текстурной генерации HLS в С#. Самая сложная часть состоит в определении, какой формат поверхности использовать для каждой текстуры. К счастью, у Nvidia есть атрибуты для каждой текстуры, которые задают идентификатор формата. Используя детализированную документацию для SurfaceFormat, мы можем установить каждой текстуре конкретный формат.

SurfaceFormat MSDN:


http://msdn.microsoft.com/enus/library/microsoft.xna.framework.graphics.surfaceformat.aspx


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

Ниже представлен код на С# для генерации четырех текстур.

 private void GeneratePermTexture() 
 { 
 permTexture = new Texture2D(GraphicsDevice, 256, 1, 1, 
 TextureUsage.None, SurfaceFormat.Luminance8); 
 byte[] data = new byte[256 * 1]; 
 for (int x = 0; x < 256; x++) 
 for (int y = 0; y < 1; y++) 
 data[x + (y * 256)] = (byte)(perm[x]); 
 permTexture.SetData<byte>(data); 
 } 
 int perm2d(int i) 
 { 
 return perm[i % 256]; 
 } 
 private void GeneratePermTexture2d() 
 { 
 permTexture2d = new Texture2D(GraphicsDevice, 256, 256, 1, 
 TextureUsage.None, SurfaceFormat.Color); 
 Color[] data = new Color[256 * 256]; 
 for (int x = 0; x < 256; x++) 
 { 
 for (int y = 0; y < 256; y++) 
 { 
 int A = perm2d(x) + y; 
 int AA = perm2d(A); 
 int AB = perm2d(A + 1); 
 int B = perm2d(x + 1) + y; 
 int BA = perm2d(B); 
 int BB = perm2d(B + 1); 
 data[x + (y * 256)] = new Color((byte)(AA), 
 (byte)(AB), 
 (byte)(BA), 
 (byte)(BB)); 
 } 
 } 
 permTexture2d.SetData<Color>(data); 
 } 
 private void GeneratePermGradTexture() 
 { 
 permGradTexture = new Texture2D(GraphicsDevice, 256, 1, 1, 
 TextureUsage.None, SurfaceFormat.NormalizedByte4); 
 NormalizedByte4[] data = new NormalizedByte4[256 * 1]; 
 for (int x = 0; x < 256; x++) 
 for (int y = 0; y < 1; y++) 
 data[x + (y * 256)] = new NormalizedByte4( 
 g3[perm[x] % 16, 0], 
 g3[perm[x] % 16, 1], 
 g3[perm[x] % 16, 2], 1); 
 permGradTexture.SetData<NormalizedByte4>(data); 
 } 
 private void GenerateGradTexture4d() 
 { 
 gradTexture4d = new Texture2D(GraphicsDevice, 32, 1, 1, 
 TextureUsage.None, SurfaceFormat.NormalizedByte4); 
 NormalizedByte4[] data = new NormalizedByte4[32 * 1]; 
 for (int x = 0; x < 32; x++) 
 for (int y = 0; y < 1; y++) 
 data[x + (y * 32)] = new NormalizedByte4( 
 g4[x, 0], 
 g4[x, 1], 
 g4[x, 2], 
 g4[x, 3]); 
 gradTexture4d.SetData<NormalizedByte4>(data); 
 } 

После того, как текстуры были созданы, они отправляются в графический процессор в качестве параметра шейдера перед вызовом отрисовки, как и любая другая текстура.

perlinPixelShader.Parameters["permTexture"].SetValue(permTexture); 
perlinPixelShader.Parameters["permTexture2d"].SetValue(permTexture2d); 
perlinPixelShader.Parameters["permGradTexture"].SetValue(permGradTexture); 
perlinPixelShader.Parameters["gradTexture4d"].SetValue(gradTexture4d); 

Шум Перлина в пиксельном шейдере.

Теперь, когда мы создали переменные и градиентные текстуры, мы можем начать писать свой пиксельный шейдер, который использует Шум Перлина. К счастью, код Nvidia оформлен как заголовочный файл, который может быть легко включен в программу построения теней, а функции шума таким образом станут доступными.

В проекте PerlinPixelShader есть пример пиксельного шейдера, который представляет результат суммирования Шума Перлина в пикселях. Ниже представлен пиксельный шейдер из этого примера.

#include "inoise.fxh" 
float4 PS_Perlin(float2 p : TEXCOORD): COLOR 
{ 
 // scale the input coordinates 
 float3 pos = float3(p * 10, 0); 
 // convert the result from [-1, +1] to [0, +1] 
 return inoise(pos)*0.5+0.5; 
} 

Как видите, пиксельный шейдер очень прост. Он складывает входные текстурные координаты для лучшего представления результатов шума, а затем выполняет вычисление самого Шума Перлина. Результат шума варьируется от -1 до 1, который выражается в большом количестве темных цветов, однако, в большинстве случаев он приводится к стандартному от 0 до 1.

Этот код выдает следующий результат:

Любое суммирование функциональных возможностей может быть использовано в пиксельном шейдере. Ниже представлен пример пиксельного шейдера, который считает 12 октав fBm. В приложении PerlinPixelShader нажмите F2, чтобы перейти к результату fBm. Вы также можете нажать F1, чтобы вернуться к стандартному Шуму Перлина.

float4 PS_fBm(float2 p : TEXCOORD): COLOR 
{ 
 // scale the input coordinates 
 float3 pos = float3(p * 10, 0); 
 // convert the result from [-1, +1] to [0, +1] 
 return fBm(pos, 12)*0.5+0.5; 
} 

Суммирование fBm очень похоже на нормальное функционирование Шума Перлина. Единственное различие в том, что второй параметр, который показывает количество раз суммирования Шума Перлина, передается и суммируется сам. Это число передается как число октав. Результат суммирования fBm на картинке схож с картой:

Шум Перлина в вершинном шейдере

Реализация Шума Перлина компанией Nvidia была разработана для Shader Model 2.0. Факт того, что Xbox 360 и XNA поддерживают Shader Model 3.0, предоставляет уникальные архитектурные решения. Главной его новой особенностью становится Vertex Texture Fetch (VTF), позволяющая вершинному шейдеру производить поиск в текстуре. Это значит, что мы можем вызывать шумовые функции внутри вершинного шейдера, так же как это делалось в пиксельном.
К сожалению, просто так воспользоваться этой функциональностью нельзя. Существующие шумовые функции должны быть немного изменены для включения этой возможности. К счастью, это довольно простые изменения, состоящие в переключения поиска текстур на уровень детального поиска (level of details — LOD). Точнее, мы должны заменить вызовы tex1D и tex2D на tex1Dlod и tex2Dlod соответственно. Второй параметр к обеим этим функциям также должен быть заменен на использование float4 для координат, для возможности указания в четвертом поле (w) уровня мипмапа образца. Это значение должно всегда равняться нулю (0) в нашем случае. Файл inoise.fxh в проекте PerlinVertexShader изменен соответствующим образом, поэтому на него можно взглянуть, если интересно.

Вот и все! Теперь у Вас есть функции шума, которые можно вызвать как из пиксельного шейдера, так и из вершинного.
Проект PerlinVertexShader демонстрирует использование Шума Перлина в вершинном шейдере. Ниже приведен вершинный шейдер из демо-примера. Он демонстрирует каркасную сферу с вершинами, расположенными на 12 октавах fBm. Стрелками можно вращать сферу. По нажатию F2 происходит переключение в режим сплошной отрисовки, а по F1 – обратно в режим каркасной.

VertexShaderOutput VertexShaderFunction(VertexShaderInput input) 
{ 
 VertexShaderOutput output; 
 // calc the height displacement using 12 octaves of fBm 
 float heightValue = fBm(input.Position.xyz, 12); 
 // add the height displacement to the vertex position 
 input.Position.xyz = input.Position.xyz + heightValue; 
 // multiply by the WVP matrices 
 float4 worldPosition = mul(input.Position, World); 
 float4 viewPosition = mul(worldPosition, View); 
 output.Position = mul(viewPosition, Projection); 
 return output; 
} 

Результат работы кода:

Реклама
Запись опубликована в рубрике Uncategorized. Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s