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

Эффект для ландшафта

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

Мультитекстурирование

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


Рис. 10.8. Мультитекстурирование - комбинирование трех различных текстур в одну новую

Рис. 10.8. Мультитекстурирование — комбинирование трех различных текстур в одну новую


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

Наложение нормалей

Используя технику наложения нормалей вы можете добавлять мелкомасштабные детали к сетке ландшафта без необходимости модифицировать сетку или увеличивать ее сложность. Для еще большего увеличения детализации ландшафта вы можете также использовать другие, более точные и сложные, техники, такие как наложение рельефа (relief mapping), наложение параллакса с учетом видимости (parallax occlusion mapping) и пошаговое конусное наложение (cone step mapping).

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


Рис. 10.9. Только текстурирование (слева). Текстурирование и наложение нормалей (справа)

Рис. 10.9. Только текстурирование (слева). Текстурирование и наложение нормалей (справа)


Карта нормалей, используемая в технике наложения нормалей, это RGB-текстура, где каждая точка текстуры представляет компоненты X, Y и Z новой нормали поверхности. Обратите внимание, что оси X, Y и Z нормали не являются осями мировой системы координат. Координаты нормали задаются в касательной системе координат. Благодаря этому карта нормалей становится независимой от поверхности и может быть применена к любому типу объектов. Кстати, это причина, по которой вам надо вычислять векторы касательной, бинормали и нормали для каждой вершины.

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

Эффект для ландшафта — обработка вершин

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

Начнем констуирование эффекта с его вершинного шейдера. Вершинный шейдер получает структуру, содержащую местоположение вершины, координаты текстуры и касательную систему координат (векторы касательной, бинормали и нормали):

struct a2v
{
    float4 position : POSITION;
    float2 uv0      : TEXCOORD0;
    float3 tangen t : TANGENT;
    float3 binormal : BINORMAL;
    float3 normal   : NORMAL;
};

Выходные данные вершинного шейдера содержат итоговую позицию вершины, координаты шести используемых текстур, вектор вида и два вектора освещения (все векторы в касательном пространстве):

struct v2f
{
    float4 hposition : POSITION;
    float4 uv1_2     : TEXCOORD0;
    float4 uv3_4     : TEXCOORD1;
    float4 uv5_6     : TEXCOORD2;
    float3 eyeVec    : TEXCOORD4;
    float3 lightVec1 : TEXCOORD5;
    float3 lightVec2 : TEXCOORD6;
};

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

// Местоположение вершины в однородном простанстве
OUT.hposition = mul(IN.position, matWVP); 

Затем вы формируете касательную систему координат, комбинируете ее с мировой матрицей и вычисляете ее инвертированную матрицу (в данном случае инвертированная матрица равна транспонированной матрице). Матрицу tangentSpace вы будете использовать для преобразования вектора из мирового пространства в касательное:

float3x3 tangentSpace = float3x3(IN.tangent, IN.binormal, IN.normal);
tangentSpace = mul(tangentSpace, matW);
tangentSpace = transpose(tangentSpace);

Теперь вы должны вычислить вектор вида, два вектора освещения, и преобразовать их координаты в касательное пространство (преобразовать их, используя матрицу tangentSpace):

float3 worldPosition = mul(IN.position, matW).xyz;
OUT.eyeVec = mul(matVI[3].xyz - worldPosition, tangentSpace);
OUT.lightVec1 = mul(light1Position - worldPosition, tangentSpace);
OUT.lightVec2 = mul(light2Position - worldPosition, tangentSpace);

И, наконец, вычисляем все координаты текстур, используя координаты текстур по умолчанию и некоторые коэффициенты блочности:

OUT.uv1_2 = float4(IN.uv0 * uv1Tile, IN.uv0 * uv2Tile);
OUT.uv3_4 = float4(IN.uv0 * uv3Tile, IN.uv0 * uv4Tile);
OUT.uv5_6 = float4(IN.uv0 * uvBumpTile, IN.uv0);

Итак, вот полный код для обработки вершин:

v2f TerrainVS(a2v IN)
{
    v2f OUT;

    // Местоположение вершины в однородном пространстве
    OUT.hposition = mul(IN.position, matWVP);

    // Формируем касательную систему координат
    float3x3 tangentSpace = float3x3(IN.tangent, IN.binormal, IN.normal);
    tangentSpace = mul(tangentSpace, matW);
    tangentSpace = transpose(tangentSpace);

    // Вычисляем векторы вида и освещения в тангенциальном пространстве
    float3 worldPosition = mul(IN.position, matW).xyz;
    OUT.eyeVec = mul(matVI[3].xyz - worldPosition, tangentSpace);
    OUT.lightVec1 = mul(light1Position - worldPosition, tangentSpace);
    OUT.lightVec2 = mul(light2Position - worldPosition, tangentSpace);

    // Мультитекстурирование
    OUT.uv1_2 = float4(IN.uv0 * uv1Tile, IN.uv0 * uv2Tile);
    OUT.uv3_4 = float4(IN.uv0 * uv3Tile, IN.uv0 * uv4Tile);
    OUT.uv5_6 = float4(IN.uv0 * uvBumpTile, IN.uv0);

    return OUT;
}

Эффект для ландшафта — обработка пикселей

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

float3 eyeVec = normalize(IN.eyeVec);
float3 lightVec1 = normalize(IN.lightVec1);
float3 lightVec2 = normalize(IN.lightVec2);
float3 halfwayVec1 = normalize(lightVec1 + eyeVec);
float3 halfwayVec2 = normalize(lightVec2 + eyeVec);

Затем прочитаем значение из карты нормалей и нормализуем его, чтобы оно находилось в интервале от –1 до 1. Это необходимо потому что значения, хранимые в текстуре, находятся в диапазоне от 0.0 до 1.0, но они должны использоваться для представления отрицательных и положительных значений:

float3 normal = tex2D(normalSampler, IN.uv5_6.xy);
normal.xy = normal.xy * 2.0 - 1.0;
normal.z = sqrt(1.0 - dot(normal.xy, normal.xy));

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

float3 diffuseColor1, diffuseColor2,
       specularColor1, specularColor2;
phongShading(normal, lightVec1, halfwayVec1, light1Color,
             diffuseColor1, specularColor1);
phongShading(normal, lightVec2, halfwayVec2, light2Color,
             diffuseColor2, specularColor2);

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

float3 color1 = tex2D(diffuseSampler1, IN.uv1_2.xy);
float3 color2 = tex2D(diffuseSampler2, IN.uv1_2.zw);
float3 color3 = tex2D(diffuseSampler3, IN.uv3_4.xy);
float3 color4 = tex2D(diffuseSampler4, IN.uv3_4.zw);
float4 alpha  = tex2D(alphaSampler, IN.uv5_6.zw);

// Комбинируем, используя альфа-карту
float3 combinedColor = lerp(color1, color2, alpha.x);
combinedColor = lerp(combinedColor , color3, alpha.y);
combinedColor = lerp(combinedColor , color4, alpha.z);

И в конце вы вычисляете итоговый цвет каждого пикселя, комбинируя его цвет текстуры с рассеиваемым и отражаемым светом, который отражается от пикселя в соответствии с его материалом:

float4 finalColor;
finalColor.a = 1.0f;
finalColor.rgb = combinedColor * ( (diffuseColor1 + diffuseColor2) *
       materialDiffuseColor + ambientLightColor) + (specularColor1 +
       specularColor2) * materialSpecularColor;

Итоговый код пиксельного шейдера и функции для расчетов по методу Фонга показан ниже. Хорошим справочником для более глубокого понимания алгоритма Фонга и техники наложения нормалей может служить 2-е издание книги «Визуализация в реальном времени» (Tomas Akenine-Moller, Eric Haines, Real-Time Rendering, 2nd ed., AK Peters, Ltd., 2002).

void phongShading(in float3 normal,     in float3 lightVec,
                  in float3 halfwayVec, in float3 lightColor,
                  out float3 diffuseColor, out float3 specularColor)
{
    float diffuseInt = saturate(dot(normal, lightVec));
    diffuseColor = diffuseInt * lightColor;
    float specularInt = saturate(dot(normal, halfwayVec));
    specularInt = pow(specularInt, specularPower);
    specularColor = specularInt * lightColor;
}

float4 TerrainPS(v2f IN) : COLOR0
{
    float3 eyeVec = normalize(IN.eyeVec);
    float3 lightVec1 = normalize(IN.lightVec1);
    float3 lightVec2 = normalize(IN.lightVec2);
    float3 halfwayVec1 = normalize(lightVec1 + eyeVec);
    float3 halfwayVec2 = normalize(lightVec2 + eyeVec);

    // Наложение нормалей
    float3 normal = tex2D(normalSampler, IN.uv5_6.xy);
    normal.xy = normal.xy * 2.0 - 1.0;
    normal.z = sqrt(1.0 - dot(normal.xy, normal.xy));

    // Вычисление рассеиваемого и отражаемого цвета
    // для каждого источника света
    float3 diffuseColor1, diffuseColor2,
           specularColor1, specularColor2;
    phongShading(normal, lightVec1, halfwayVec1, light1Color,
                 diffuseColor1, specularColor1);
    phongShading(normal, lightVec2, halfwayVec2, light2Color,
                 diffuseColor2, specularColor2);

    // Комбинирование текстур рассеивания с использованием альфа-карты
    float3 color1 = tex2D(diffuseSampler1, IN.uv1_2.xy);
    float3 color2 = tex2D(diffuseSampler2, IN.uv1_2.zw);
    float3 color3 = tex2D(diffuseSampler3, IN.uv3_4.xy);
    float3 color4 = tex2D(diffuseSampler4, IN.uv3_4.zw);
    float4 alpha = tex2D(alphaSampler, IN.uv5_6.zw);

    float3 combinedColor = lerp(color1, color2, alpha.x);
    combinedColor = lerp(combinedColor , color3, alpha.y);
    combinedColor = lerp(combinedColor , color4, alpha.z);

    // Вычисление итогового цвета
    float4 finalColor;
    finalColor.a = 1.0f;
    finalColor.rgb = combinedColor * ( (diffuseColor1 + diffuseColor2) *
           materialDiffuseColor + ambientLightColor) + (specularColor1 +
           specularColor2) * materialSpecularColor;

    return finalColor;
}

Установка материала эффекта

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

Класс TerrainMaterial хранит материал поверхности как атрибут типа LightMaterial, и текстуры поверхности как несколько атрибутов типа TextureMaterial. Вот код класса TerrainMaterial:

public class TerrainMaterial
{
    // Материал поверхности
    LightMaterial lightMaterial;

    // Текстуры рассеивания
    TextureMaterial diffuseTexture1;
    TextureMaterial diffuseTexture2;
    TextureMaterial diffuseTexture3;
    TextureMaterial diffuseTexture4;

    // Альфа-карта
    TextureMaterial alphaMapTexture;

    // Карта нормалей
    TextureMaterial normalMapTexture;

    // Свойства
    public LightMaterial LightMaterial
    {
        get { return lightMaterial; }
        set { lightMaterial = value; }
    }
    public TextureMaterial DiffuseTexture1
    {
        get { return diffuseTexture1; }
        set { diffuseTexture1 = value; }
    }
    public TextureMaterial DiffuseTexture2
    {
        get { return diffuseTexture2; }
        set { diffuseTexture2 = value; }
    }
    public TextureMaterial DiffuseTexture3
    {
        get { return diffuseTexture3; }
        set { diffuseTexture3 = value; }
    }
    public TextureMaterial DiffuseTexture4
    {
        get { return diffuseTexture4; }
        set { diffuseTexture4 = value; }
    }
    public TextureMaterial AlphaMapTexture
    {
        get { return alphaMapTexture; }
        set { alphaMapTexture = value; }
    }
    public TextureMaterial NormalMapTexture
    {
        get { return normalMapTexture; }
        set { normalMapTexture = value; }
    }

    public TerrainMaterial()
    {
    }
}

Для конфигурирования эффекта ландшафта вы создаете метод SetEffectMaterial внутри класса Terrain. Вы используете этот метод для конфигурирования всех параметров эффекта через вспомогательный класс TerrainEffect перед визуализацией ландшафта.

В вашей сцене вы управляете камерами и источниками света, используя созданные в главе 9 классы CameraManager и LightManager. Вы можете добавить эти классы к контейнеру служб класса Game. Благодаря этому, любой класс, у которого есть ссылка на класс Game, может запросить эти ресурсы в любой момент выполнения программы. Контейнер служб помогает управлять слабыми связями между объектами. Используя контейнер служб вы можете получить диспетчер источников света и обратиться к источникам света сцены, используемым эффектом, взяв первые два источника света из диспетчера источников света (LightManager):

// Получаем диспетчер источников света
LightManager lightManager = Game.Services.GetService(
                          typeof(LightManager)) as LightManager;

// Получаем первые два источника света из диспетчера источников света
PointLight light0 = lightManager[0] as PointLight;
PointLight light1 = lightManager[1] as PointLight;

// Освещение
effect.AmbientLightColor = lightManager.AmbientLightColor;
effect.Light1Position    = light0.Position;
effect.Light1Color       = light0.Color;
effect.Light2Position    = light1.Position;
effect.Light2Color       = light1.Color;

Используя контейнер служб вы можете получить диспетчер камер (CameraManager), запросить из него активную камеру, и прочитать преобразования ландшафта из ее атрибута transformation типа Transformation:

// Получаем диспетчер камер
cameraManager = Game.Services.GetService(
typeof(CameraManager)) as CameraManager;

// Устанавливаем вид и проекцию камеры
effect.View = cameraManager.ActiveCamera.View;
effect.Projection = cameraManager.ActiveCamera.Projection;

// Устанавливаем преобразование для ландшафта
effect.World = transformation.Matrix;

В конце вы конфигурируете материал ландшафта и текстуры через атрибуты LightMaterial и TextureMaterial класса TerrainMaterial. Ниже показан код метода SetEffectMaterial:

private void SetEffectMaterial()
{
    // Получаем диспетчер источников света
    LightManager lightManager = Game.Services.GetService(
                               typeof(LightManager)) as LightManager;

    // Получаем первые два источника света
    // из диспетчера источников света
    PointLight light0 = lightManager[0] as PointLight;
    PointLight light1 = lightManager[1] as PointLight;

    // Освещение
    effect.AmbientLightColor = lightManager.AmbientLightColor;
    effect.Light1Position = light0.Position;
    effect.Light1Color = light0.Color;
    effect.Light2Position = light1.Position;
    effect.Light2Color = light1.Color;

    // Получаем диспетчер камер
    cameraManager = Game.Services.GetService(
                         typeof(CameraManager)) as CameraManager;

    // Устанавливаем вид и проекцию камеры
    effect.View = cameraManager.ActiveCamera.View;
    effect.Projection = cameraManager.ActiveCamera.Projection;

    // Устанавливаем преобразование ландшафта
    effect.World = transformation.Matrix;

    // Материал
    effect.DiffuseColor = terrainMaterial.LightMaterial.DiffuseColor;
    effect.SpecularColor = terrainMaterial.LightMaterial.SpecularColor;
    effect.SpecularPower = terrainMaterial.LightMaterial.SpecularPower;

    // Текстуры
    effect.DiffuseTexture1 = terrainMaterial.DiffuseTexture1.Texture;
    effect.DiffuseTexture2 = terrainMaterial.DiffuseTexture2.Texture;
    effect.DiffuseTexture3 = terrainMaterial.DiffuseTexture3.Texture;
    effect.DiffuseTexture4 = terrainMaterial.DiffuseTexture4.Texture;
    effect.NormalMapTexture = terrainMaterial.NormalMapTexture.Texture;
    effect.AlphaMapTexture = terrainMaterial.AlphaMapTexture.Texture;

    // Текстурные UV
    effect.TextureUV1Tile = terrainMaterial.DiffuseTexture1.UVTile;
    effect.TextureUV2Tile = terrainMaterial.DiffuseTexture2.UVTile;
    effect.TextureUV3Tile = terrainMaterial.DiffuseTexture3.UVTile;
    effect.TextureUV4Tile = terrainMaterial.DiffuseTexture4.UVTile;
    effect.TextureUVNormalTile = material.NormalMapTexture.UVTile;
}

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

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