netlib.narod.ru< Назад | Оглавление | Далее >

Эффекты расширенного динамического диапазона

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

Ваш первый HDR-шейдер: засветка!

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

Эффект засветки объясняется тем, что энергия источника света влияет не только на ту область, с которой контактирует непосредственно, но и на близлежащие области. Эти формирующие данное явление утечки могут быть легко воссозданы с помощью правильно подобранной последовательности фильтров размытия.

Сначала надо решить, какой фильтр размытия использовать. Поскольку нам надо, чтобы размытие было гладким и последовательным, хорошо подойдет 9-точечный фильтр Гаусса. Если помните, мы использовали этот фильтр в главе 7. Другим преимуществом использования фильтра Гаусса является возможность контролировать степень размытия итогового изображения путем подбора количества проходов фильтра, применяемых к вашей сцене.

Поскольку необходимо, чтобы размытие сцены выполнялось с минимальной нагрузкой на графический процессор, все размытие для засветки будет выполняться в цели визуализации с размером 1/4 от исходного изображения. Когда оригинальная цель визуализации будет достаточно размыта, останется только смешать это размытое изображение с экранным в тех местах, где расположены наиболее яркие фрагменты.

ПРИМЕЧАНИЕ
Поскольку RenderMonkey не обладает функциональностью, позволяющей выбирать размер цели визуализации пропорционально размеру экрана, вы должны аппроксимировать его, сделав предварительную оценку — которая должна быть достаточной — и вручную установить эти значения в RenderMonkey.

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

float fInverseViewportWidth;
float fInverseViewportHeight;

sampler Texture0;

const float4 samples[9] = {
        -1.0,  1.0, 0, 1.0/16.0,
        -1.0,  1.0, 0, 1.0/16.0,
         1.0, -1.0, 0, 1.0/16.0,
         1.0,  1.0, 0, 1.0/16.0,
        -1.0,  0.0, 0, 2.0/16.0,
         1.0,  0.0, 0, 2.0/16.0,
         0.0, -1.0, 0, 2.0/16.0,
         0.0,  1.0, 0, 2.0/16.0,
         0.0,  0.0, 0, 4.0/16.0
};

float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR
{
    float4 color = float4(0,0,0,0);

    // Выборка и вывод среднего цвета
    for(int i = 0; i < 9; i++)
    {
        float4 col = samples[i].w*tex2D(Texture0,texCoord+
                     float2(samples[i].x*fInverseViewportWidth,
                            samples[i].y*fInverseViewportHeight));
        color += col;
    }
    return color;
}

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


Рис. 8.4. Различные уровни размытия для использования в эффекте засветки

Рис. 8.4. Различные уровни размытия для использования в эффекте засветки


Поскольку единственного прохода размытия недостаточно для выполнения поставленной задачи, вам необходимо повторить процесс несколько раз. Для этого шейдера я выполняю шесть проходов, сохраняя результаты размытия двух последних. Эти промежуточные результаты размытия будут использоваться в шейдере засветки чуть позже. Имейте в виду, что вам необходимо создать несколько целей визуализации для выполнения процесса размытия и для сохранения необходимых промежуточных результатов.

Последний отсутствующий компонент данного шейдера — завершающий проход, где комбинируются цели визуализации с обычной сценой и с результатом размытия. Хотя это может быть сделано в виде отдельных проходов альфа-смешивания для каждой из наших исходных текстур, проще сделать это в совместимом с версией 2.0 пиксельном шейдере, осуществляя выборку из обоих целей визуализации в одном проходе и используя код пиксельного шейдера.

Чтобы скомбинировать три размытых версии ваших целей визуализации HDR, вам просто надо скомбинировать три значения внутри пиксельного шейдера. Для этого вы должны определить три константы с именами Glow_Factor, Glow_Factor2 и Glow_Factor3. Они определяют степень участия каждой размытой цели визуализации. Приведенный ниже код пиксельного шейдера показывает, как выполняется поставленная задача:

float Glow_Factor;
float Glow_Factor2;
float Glow_Factor3;

sampler Texture0;
sampler Texture1;
sampler Texture2;

float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR
{
    // Выбираем три уровня размытия и комбинируем их вместе
    return
        float4((tex2D(Texture0,texCoord).xyz)*Glow_Factor, 1.0) +
        float4((tex2D(Texture1,texCoord).xyz)*Glow_Factor2,0) +
        float4((tex2D(Texture2,texCoord).xyz)*Glow_Factor3,0);
}

Итоговый результат визуализации для данного шейдера показан на рис. 8.5. Финальная версия шейдера находится на CD-ROM в файле shader_2.rfx. В первом упражнении в конце этой главы вам будет предложено усовершенствовать этот шейдер путем использования более сложных фильтров размытия, чем 9-точечный фильтр Гаусса.


Рис. 8.5. Итоговый результат визуализации для HDR-эффекта засветки

Рис. 8.5. Итоговый результат визуализации для HDR-эффекта засветки


А теперь несколько полос!

Возможно, вы замечали, что источники света, на которые вы смотрите через стекло автомобиля двигаясь ночью, приобретают звездообразную форму. Это результат наличия крошечных царапин на поверхности стекла, вызывающих отражение и преломление света. Физика, лежащая в основе данного явления, очень сложна, но для наших целей не требуется обсуждать лежащие в основе причины. Мы будем лишь разрабатывать аппроксимацию, которая даст достаточно похожие результаты.

Для достижения этого эффекта необходимо создать звездообразный шаблон. Это может быть достигнуто путем использования специального фильтра размытия, который действует в заданном направлении, формируя идущую в этом направлении полосу. Этот фильтр прост и применяет четыре выборки, идущие вдоль заданной диагонали. Работу фильтра иллюстрирует рис. 8.6.


Рис. 8.6. Диагональный фильтр, используемый для создания эффекта полос

Рис. 8.6. Диагональный фильтр, используемый для создания эффекта полос


Как видно на рисунке, позиция выборки для каждого прохода берется со смещением, пропорциональным проходу визуализации и номеру выборки. Также у каждой выборки есть вес blurFactor, степень которого повышается пропорционально номеру выборки и прохода. Это позволяет приписывать меньший вес выборкам, которые расположены дальше от визуализируемого пикселя. Такая комбинация позволяет создавать фильтры, формирующие прогрессивно увеличивающуюся с каждым проходом в заданном направлении полосу. Например, после одного прохода длина полосы составит 4 пикселя, а после двух проходов — 16 пикселей.

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

float fInverseViewportWidth;
float fInverseViewportHeight;

sampler Texture0;

const float blurFactor   = 0.96;
const float offsetFactor = 1;

const float4 samples[4] = {
     0.0, -0.0, 0, 0,
     1.0, -1.0, 0, 1,
     2.0, -2.0, 0, 2,
     3.0, -3.0, 0, 3
};

float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR
{
    float4 color = float4(0,0,0,0);

    // Выборка и вывод среднего цвета
    for(int i = 0; i < 4; i++)
    {
        float4 col = pow(blurFactor,offsetFactor*samples[i].w)*
              tex2D(Texture0,texCoord+
              offsetFactor*float2(samples[i].x*fInverseViewportWidth,
                                  samples[i].y*fInverseViewportHeight));
        color += col;
    }
return color;
}

На рис. 8.7 показан результат размытия для одной полосы. Вам необходимо выполнить тот же самый процесс для всех четырех направлений, меняя знаки в таблице фильтра и выбирая подходящий offsetFactor.


Рис. 8.7. Визуализация результатов шейдера для одной полосы

Рис. 8.7. Визуализация результатов шейдера для одной полосы


Затем вам остается только скомбинировать четыре полосы тем же способом, что и в шейдере засветки. Обратите внимание, что в данном случае вам нужна единственная переменная коэффициента засветки, поскольку каждая полоса использует одну и ту же комбинацию. Вы также можете уменьшить альфа-смешивание в итоговом результате, поскольку оно может чрезмерно осветлить сцену. Сделав все это вы придете к следующему коду шейдера:

float Glow_Factor;

sampler Texture0;
sampler Texture1;
sampler Texture2;
sampler Texture3;

float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR
{
    // Комбинируем полосы в 4 направлениях
    return
        min(1.0,
        float4((tex2D(Texture0,texCoord).xyz)*Glow_Factor,0.15) +
        float4((tex2D(Texture1,texCoord).xyz)*Glow_Factor,0.15) +
        float4((tex2D(Texture2,texCoord).xyz)*Glow_Factor,0.15) +
        float4((tex2D(Texture3,texCoord).xyz)*Glow_Factor,0.15));
}

Итоговые результаты работы шейдера показаны на рис. 8.8. Также финальная версия шейдера записана на CD-ROM в файле shader_3.rfx.


Рис. 8.8. Визуализация HDR-шейдера полос

Рис. 8.8. Визуализация HDR-шейдера полос


Светорассеяние в объективе для всех

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

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

Помня об этом, вы должны получить доступ к цели визуализации способом, позволяющим ее масштабирование и переворот. Это легко сделать, изменив способ передачи координат текстур в пиксельный шейдер. Помня, что координаты центра текстуры (0.5, 0.5), вы можете масштабировать и переворачивать текстуру используя следующий код вершинного шейдера:

texCoord = (texCoord-0.5)*(Scale) + 0.5;

В этом коде вы видите переменную Scale, управляющую масштабированием текстуры. Если вы хотите перевернуть текстуру, вам просто надо подставить в это выражение отрицательный коэффициент масштабирования.

ПРИМЕЧАНИЕ
Имейте в виду, что масштабирование текстуры может привести к обращению за пределы текстуры, поэтому вы должны установить режим адресации текстуры CLAMP, чтобы избежать повторения цели визуализации.

Чтобы эффект выглядел хорошо, надо повторить процесс несколько раз. Здесь мы будем делать это четыре раза для каждого прохода. Вам надо также подобрать различные коэффициенты масштабирования. В рассматриваемом примере я использую 2.0, –2.0, 0.6 и –0.6. Вы можете поэкспериментировать и подобрать свои собственные значения.

Выполнение этих настроек в вершинном шейдере приводит к следующему коду:

float4x4 matViewProjection;

struct VS_OUTPUT
{
    float4 Pos: POSITION;
    float2 texCoord: TEXCOORD0;
    float2 texCoord1: TEXCOORD1;
    float2 texCoord2: TEXCOORD2;
    float2 texCoord3: TEXCOORD3;
    float2 texCoord4: TEXCOORD4;
};

VS_OUTPUT vs_main(float4 Pos: POSITION)
{
    VS_OUTPUT Out;

    // Просто выводим местоположение без преобразования
    Out.Pos = float4(Pos.xy, 0, 1);

    // Координаты текстуры устанавливаются так, чтобы
    // вся текстура полностью отображалась на экран
    float2 texCoord;
    texCoord.x = 0.5 * (1 + Pos.x - 1/128);
    texCoord.y = 0.5 * (1 - Pos.y - 1/128);
    Out.texCoord = texCoord;

    // Вычисляем масштабированные координаты текстур
    // для призрачных изображений
    Out.texCoord1 = (texCoord-0.5)*(-2.0) + 0.5;
    Out.texCoord2 = (texCoord-0.5)*(2.0) + 0.5;
    Out.texCoord3 = (texCoord-0.5)*(-0.6) + 0.5;
    Out.texCoord4 = (texCoord-0.5)*(0.6) + 0.5;

    return Out;
}

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


Рис. 8.9. Текстура, используемая для маскирования резких краев в эффекте светорассеяния в объективе

Рис. 8.9. Текстура, используемая для маскирования резких краев в эффекте светорассеяния в объективе


С учетом вышесказанного вам необходимо выполнить выборку из четырех целей визуализации и четыре значения маски. Значения маски модулируются цветом цели визуализации для каждой выборки, итоговые результаты складываются и умножаются на Glow_Factor. Этот коэффициент помогает управлять интенсивностью засвечивания и определяется экспериментальным путем. Применение этих изменений к пиксельному шейдеру дает следующий код:

float fInverseViewportWidth;
float fInverseViewportHeight;

float Glow_Factor;

sampler Texture0;
sampler Texture1;

float4 ps_main (float2 texCoord: TEXCOORD0,
                float2 texCoord1: TEXCOORD1,
                float2 texCoord2: TEXCOORD2,
                float2 texCoord3: TEXCOORD3,
                float2 texCoord4: TEXCOORD4) : COLOR
{
    // Выборка всех изображений призраков
    float4 col1 = tex2D(Texture0, texCoord1)*tex2D(Texture1, texCoord1).a;
    float4 col2 = tex2D(Texture0, texCoord2)*tex2D(Texture1, texCoord2).a;
    float4 col3 = tex2D(Texture0, texCoord3)*tex2D(Texture1, texCoord3).a;
    float4 col4 = tex2D(Texture0, texCoord4)*tex2D(Texture1, texCoord4).a;

    // Комбинирование изображений призраков воедино
    return (col1+col2+col3+col4)*Glow_Factor;
}

Поскольку вы хотите получить несколько призраков, необходимо повторить этот процесс второй раз, используя в качестве исходного изображения результат первого прохода. Комбинация двух проходов дает 16 призрачных изображений, что вполне достаточно.

Последний необходимый шаг — визуализация результатов на сцене. Это делается путем использования альфа-смешивания и следующего кода пиксельного шейдера:

float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR
{
    return float4((tex2D(Texture0,texCoord).xyz),0.8);
}

Внеся все изменения, скомпилируйте шейдер и посмотрите на результат (рис. 8.10). Финальная версия данного шейдера находится на CD-ROM в файле shader_4.rfx.


Рис. 8.10. Визуализация HDR-шейдера светорассеяния в объективе

Рис. 8.10. Визуализация HDR-шейдера светорассеяния в объективе


Объединяем все вместе

Совмещение всех эффектов воедино заключается в простой комбинации всех целей визуализации и проходов визуализации каждого отдельного эффекта. Это выполняется с помощью команд Copy и Paste контекстного меню.

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

float Streak_Factor;

float Ghost_Factor;

float Glow_Factor;
float Glow_Factor2;
float Glow_Factor3;

sampler Texture0;
sampler Texture1;
sampler Texture2;
sampler Texture3;
sampler Texture4;
sampler Texture5;
sampler Texture6;
sampler Texture7;

float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR
{
    float4 col;

    // Засветка
    col = float4((tex2D(Texture1,texCoord).xyz)*Glow_Factor,1.0) +
          float4((tex2D(Texture2,texCoord).xyz)*Glow_Factor2,0) +
          float4((tex2D(Texture3,texCoord).xyz)*Glow_Factor3,0);

    // Призраки
    col += float4((tex2D(Texture0,texCoord).xyz),0);

    // Полосы
    col +=
         float4((tex2D(Texture4,texCoord).xyz)*Streak_Factor,0) +
         float4((tex2D(Texture5,texCoord).xyz)*Streak_Factor,0) +
         float4((tex2D(Texture6,texCoord).xyz)*Streak_Factor,0) +
         float4((tex2D(Texture7,texCoord).xyz)*Streak_Factor,0);

    return col;
}

Вот и все необходимое для работы. Вам, возможно, придется подстроить некоторые переменные, чтобы отдельные эффекты хорошо соединялись вместе. Настроенная версия выходного результата показана на рис. 8.11. Шейдер содержится на CD-ROM в файле shader_5.rfx.


Рис. 8.11. Визуализация финального HDR-шейдера

Рис. 8.11. Визуализация финального HDR-шейдера



netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz