О Слоях.
Слой - это временная цель рендеринга, на которой вы рисуете отдельно, а затем результат накладывается на основную цель рендеринга. В Direct2D слой представлен интерфейсом ID2D1Layer. Подобно кистям, слои создаются рендер-таргетом и являются ресурсами, зависимыми от устройства. Слой может использоваться только одной целью рендеринга в один момент времени. Если размер слоя не указан при создании, метод PushLayer автоматически определяет минимальный необходимый размер на основе содержимого и маски. Пример работы со слоями (сначала общий план):

Пример работы со слоями (сначала общий план):

  1. Создаём слой через CreateLayer

  2. Добавляем слой через PushLayer - следующие команды(рисования) будут направляться в слой

  3. Достаём слой через PopLayer - содержимое слоя смешивается с основной целью рендера

Выполнение первого пункта:

Для создания слоя используют CreateLayer:

ID2D1Layer* pLayer = nullptr;
HRESULT hr = pRenderTarget->CreateLayer(nullptr, &pLayer);
if (FAILED(hr)) {
    // Обработка ошибки
}

Первый параметр - размер слоя в пикселях. При передаче nullptr размер будет определён автоматически при вызове PushLayer. Слой автоматически расширяется при необходимости, но никогда не уменьшается

Выполнение второго пункта:

D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
pRenderTarget->PushLayer(layerParams, pLayer);

О структуре D2D1_LAYER_PARAMETERS:

struct D2D1_LAYER_PARAMETERS {
    D2D1_RECT_F contentBounds;          // Область рисования на слое
    ID2D1Geometry *geometricMask;       // Маска-геометрия
    D2D1_ANTIALIAS_MODE maskAntialiasMode; // Сглаживание маски
    D2D1_MATRIX_3X2_F maskTransform;    // Трансформация маски
    FLOAT opacity;                      // Прозрачность слоя (0.0 - 1.0)
    ID2D1Brush *opacityBrush;           // Кисть для прозрачности
    D2D1_LAYER_OPTIONS layerOptions;    // Дополнительные опции. Начиная с Windows 8 используется D2D1_LAYER_OPTIONS1.
};

layerOptions - дополнительные опции слоя. Начиная с Windows 8 используется D2D1_LAYER_OPTIONS1 с новыми опциями, которые могут улучшить производительность:

D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND - Direct2D не очищает слой прозрачным чёрным, что в большинстве случаев даёт лучшую производительность.

D2D1_LAYER_OPTIONS1_IGNORE_ALPHA - позволяет не изменять альфа-канал слоя (используйте только когда альфа-канал не нужен).

// Параметры по умолчанию, но с установленной прозрачностью
D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
layerParams.opacity = 0.5f; // 50% прозрачности

m_pRenderTarget->PushLayer(layerParams, m_pLayer);
// ... здесь рисуем на слое ...
m_pRenderTarget->PopLayer();

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

// Предположим, у нас есть созданная геометрия pCircleGeometry
D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
layerParams.geometricMask = pCircleGeometry;
layerParams.maskAntialiasMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;

m_pRenderTarget->PushLayer(layerParams, m_pLayer);
// Рисуем, например, текстуру. Она будет видна только внутри круга.
m_pRenderTarget->PopLayer();

Выполнение третьего шага:

Когда вы закончили рисовать на слое:

m_pRenderTarget->PopLayer();

В этот момент содержимое слоя смешивается с основным рендер-таргетом с учётом параметров прозрачности, которые вы задали.

Начиная с Windows 8, появилась возможность пропустить вызов CreateLayer и передать nullptr в PushLayer - Direct2D автоматически управляет ресурсами слоя:

// В Windows 8+ можно вообще не создавать слой явно
D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
layerParams.opacity = 0.6f;

// Передаём nullptr — Direct2D сам создаст и уничтожит слой
pRenderTarget->PushLayer(layerParams, nullptr);

// ... рисование ...

pRenderTarget->PopLayer();

Важное предостережение о производительности. Каждый слой требует: очистки своей области, выделения памяти под буфер, смешивания с основным изображением. Поэтому не создавайте слои без необходимости (текстура обрезается через маску, слои для этого не нужны). И НЕ ЗАБЫВАЙТЕ ОСВОБОЖДАТЬ СЛОЙ!

Вот пример функции рендера, которая использует слои

HRESULT RenderWithLayer(ID2D1RenderTarget* pRT, ID2D1Brush* pFillBrush, ID2D1Brush* pStrokeBrush)
{
    HRESULT hr = S_OK;
    
    // Создаём слой 
    ID2D1Layer* pLayer = nullptr;
    hr = pRT->CreateLayer(nullptr, &pLayer);
    if (FAILED(hr)) return hr;
    
    // Настраиваем параметры слоя
    D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
    layerParams.opacity = 0.6f;  // 60% непрозрачности
    layerParams.contentBounds = D2D1::RectF(0, 0, 500, 400);
    
    // Запускаем слой
    pRT->PushLayer(layerParams, pLayer);
    
    // Рисуем внутри слоя
    D2D1_RECT_F rect1 = D2D1::RectF(50.0f, 50.0f, 250.0f, 200.0f);
    pRT->FillRectangle(rect1, pFillBrush);
    pRT->DrawRectangle(rect1, pStrokeBrush, 2.0f);
    
    D2D1_RECT_F rect2 = D2D1::RectF(150.0f, 100.0f, 350.0f, 250.0f);
    pRT->FillRectangle(rect2, pFillBrush);
    pRT->DrawRectangle(rect2, pStrokeBrush, 2.0f);
    
    // Завершаем слой
    pRT->PopLayer();
    
    // Освобождаем слой
    pLayer->Release();
    
    return hr;
}

О ЭФФЕКТАХ.

Что такое эффект в Direct2D? Эффект - это способ модификации изображения (принимает изображение и изменяет его). Например, позволяет изменить прозрачность, применить сглаживание и множество других преобразований.

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

Примеры эффектов (часть из большого списка):

Эффекты размытия:

Эффект

CLSID

Что делает

Гауссово размытие

CLSID_D2D1GaussianBlur

Размывает изображение — основа для многих эффектов

Направленное размытие

CLSID_D2D1DirectionalBlur

Размытие под заданным углом (эффект движения)

Эффекты цветокоррекции:

Эффект

CLSID

Что делает

Цветовая матрица

CLSID_D2D1ColorMatrix

Преобразование цветов (градации серого, сепия)

Яркость

CLSID_D2D1Brightness

Регулировка яркости

Контрастность

CLSID_D2D1Contrast

Регулировка контраста

Насыщенность

CLSID_D2D1Saturation

Изменение насыщенности цветов

Оттенки серого

CLSID_D2D1Grayscale

Преобразование в чёрно-белое

Эффекты композитинга:

Эффект

CLSID

Что делает

Тень

CLSID_D2D1Shadow

Создаёт тень от изображения

Смешивание

CLSID_D2D1Blend

Различные режимы смешивания

Альфа-маска

CLSID_D2D1AlphaMask

Применение альфа-маски

Арифметический композит

CLSID_D2D1ArithmeticComposite

Арифметическое смешивание

Другие эффекты:

Эффект

CLSID

Что делает

Морфология

CLSID_D2D1Morphology

Расширение/сужение (дилатация/эрозия)

Турбулентность

CLSID_D2D1Turbulence

Генерация шума (например, для эффекта облаков)

Обнаружение границ

CLSID_D2D1EdgeDetection

Выделение границ на изображении

Полный список всех встроенных эффектов с CLSID можно найти в документации Microsoft.

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

ComPtr<ID2D1Effect> gaussianBlurEffect;
hr = d2dContext->CreateEffect(CLSID_D2D1GaussianBlur, &gaussianBlurEffect);
if (FAILED(hr)) {
    // Обработка ошибки
}

Теперь - настройка свойств. Каждый эффект обладает собственным набором параметров. Для гауссова размытия необходимо задать StandardDeviation (стандартное отклонение):

gaussianBlurEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 5.0f);

Свойства эффекта гауссова размытия:

  • D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION (float) - стандартное отклонение, диапазон 0.0–20.0, по умолчанию 3.0

  • D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION - режим оптимизации: SPEED, BALANCED (по умолчанию), QUALITY

  • D2D1_GAUSSIANBLUR_PROP_BORDER_MODE - режим границ: SOFT (по умолчанию), HARD

Теперь установка входного изображения. Входом может быть: ID2D1Bitmap , другой ID2D1Effect (через SetInputEffect) , ID2D1Image (обобщённый интерфейс для изображений)

// Если у вас есть битмап
gaussianBlurEffect->SetInput(0, pBitmap);

// Или если вы хотите подать на вход другой эффект
// gaussianBlurEffect->SetInputEffect(0, previousEffect.Get());

Рисование результата. Выход эффекта - это тоже изображение, и его можно нарисовать через DrawImage:

d2dContext->BeginDraw();

// Применяем преобразование к результату (опционально)
D2D1_POINT_2F targetOffset = D2D1::Point2F(100.0f, 100.0f);
d2dContext->DrawImage(gaussianBlurEffect.Get(), targetOffset);

d2dContext->EndDraw();

И теперь примерно(набросок кода) то, как можно использовать эффект:

HRESULT LoadBitmapFromFile(
    ID2D1DeviceContext* pDC,
    IWICImagingFactory* pWICFactory,
    PCWSTR uri,
    ID2D1Bitmap1** ppBitmap
) {
    IWICBitmapDecoder* pDecoder = nullptr;
    HRESULT hr = pWICFactory->CreateDecoderFromFilename(
        uri, nullptr, GENERIC_READ,
        WICDecodeMetadataCacheOnLoad, &pDecoder
    );
    if (FAILED(hr)) return hr;

    IWICBitmapFrameDecode* pFrame = nullptr;
    hr = pDecoder->GetFrame(0, &pFrame);
    if (FAILED(hr)) {
        pDecoder->Release();
        return hr;
    }

    IWICFormatConverter* pConverter = nullptr;
    hr = pWICFactory->CreateFormatConverter(&pConverter);
    if (FAILED(hr)) {
        pFrame->Release();
        pDecoder->Release();
        return hr;
    }

    hr = pConverter->Initialize(
        pFrame,
        GUID_WICPixelFormat32bppPBGRA,
        WICBitmapDitherTypeNone,
        nullptr,
        0.0f,
        WICBitmapPaletteTypeMedianCut
    );

    if (SUCCEEDED(hr)) {
        D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
            D2D1_BITMAP_OPTIONS_NONE,
            D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
        );
        hr = pDC->CreateBitmapFromWicBitmap(pConverter, props, ppBitmap);
    }

    pConverter->Release();
    pFrame->Release();
    pDecoder->Release();
    return hr;
}

// Использование:
ID2D1Bitmap1* pBitmap = nullptr;
LoadBitmapFromFile(d2dContext.Get(), pWICFactory.Get(), L"image.jpg", &pBitmap);

// Создаём и применяем эффект
ComPtr<ID2D1Effect> blurEffect;
d2dContext->CreateEffect(CLSID_D2D1GaussianBlur, &blurEffect);
blurEffect->SetInput(0, pBitmap);
blurEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 5.0f);

d2dContext->BeginDraw();
d2dContext->DrawImage(blurEffect.Get());
d2dContext->EndDraw();

Один из особых вариантов - цепочка эффектов:

// Эффект 1: Размытие
ComPtr<ID2D1Effect> blurEffect;
d2dContext->CreateEffect(CLSID_D2D1GaussianBlur, &blurEffect);
blurEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 3.0f);
blurEffect->SetInput(0, pBitmap);

// Эффект 2: Цветовая матрица (делаем изображение теплее)
ComPtr<ID2D1Effect> colorEffect;
d2dContext->CreateEffect(CLSID_D2D1ColorMatrix, &colorEffect);

// Матрица для "тёплого" оттенка
D2D1_MATRIX_5X4_F warmMatrix = {
    1.2f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.8f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 0.0f, 0.0f
};
colorEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, warmMatrix);
colorEffect->SetInputEffect(0, blurEffect.Get()); // Вход = выход blurEffect

// Эффект 3: Тень
ComPtr<ID2D1Effect> shadowEffect;
d2dContext->CreateEffect(CLSID_D2D1Shadow, &shadowEffect);
shadowEffect->SetInputEffect(0, colorEffect.Get());
shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, 5.0f);
shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, D2D1::Vector4F(0.0f, 0.0f, 0.0f, 0.5f));

// Рисуем результат всей цепочки
d2dContext->BeginDraw();
d2dContext->DrawImage(shadowEffect.Get());
d2dContext->EndDraw();

И ещё один пример, слой "Матовое стекло":

void RenderFrostedGlass(ID2D1DeviceContext* pDC, ID2D1Bitmap1* pBackground)
{
    // 1. Рисуем фон
    pDC->DrawBitmap(pBackground);

    // 2. Определяем область стекла
    D2D1_RECT_F glassRect = D2D1::RectF(200.0f, 150.0f, 600.0f, 350.0f);

    // 3. Создаём битмап с содержимым фона в этой области
    // Вычисляем размер области в пикселях
    D2D1_SIZE_U size = D2D1::SizeU(
        static_cast<UINT32>(glassRect.right - glassRect.left),
        static_cast<UINT32>(glassRect.bottom - glassRect.top)
    );

    ComPtr<ID2D1Bitmap1> pAreaBitmap;
    D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
        D2D1_BITMAP_OPTIONS_NONE,
        D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
    );
    HRESULT hr = pDC->CreateBitmap(size, nullptr, 0, props, &pAreaBitmap);
    if (FAILED(hr)) return;

    // Копируем прямоугольную область из фонового битмапа
    D2D1_POINT_2U destPoint = D2D1::Point2U(0, 0);
    D2D1_RECT_U srcRect = D2D1::RectU(
        static_cast<UINT32>(glassRect.left),
        static_cast<UINT32>(glassRect.top),
        static_cast<UINT32>(glassRect.right),
        static_cast<UINT32>(glassRect.bottom)
    );
    hr = pAreaBitmap->CopyFromBitmap(&destPoint, pBackground, &srcRect);
    if (FAILED(hr)) return;

    // 4. Создаём эффект размытия
    ComPtr<ID2D1Effect> blurEffect;
    hr = pDC->CreateEffect(CLSID_D2D1GaussianBlur, &blurEffect);
    if (FAILED(hr)) return;
    
    blurEffect->SetInput(0, pAreaBitmap.Get());
    blurEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 15.0f);

    // 5. Создаём слой с прозрачностью 0.8 и запускаем его
    // (передаём nullptr — Direct2D сам управляет ресурсами слоя)
    D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters(glassRect);
    layerParams.opacity = 0.8f;
    pDC->PushLayer(layerParams, nullptr);

    // 6. Рисуем результат эффекта внутри слоя
    pDC->DrawImage(blurEffect.Get());

    // 7. Завершаем слой
    pDC->PopLayer();
}

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

Всем спокойной ночи или удачного дня, и удачи!

При желании материально поддержать перевод и структурирование информации - средства можете отправить через сбор в ЮМани.

Комментарии (0)