Direct 2D #12. Слои и эффекты

от автора

О Слоях.
Слой — это временная цель рендеринга, на которой вы рисуете отдельно, а затем результат накладывается на основную цель рендеринга. В 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();

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

// Предположим, у нас есть созданная геометрия pCircleGeometryD2D1_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 (будем использовать, когда дойдём до создания движка игры), разделение ресурсов между контекстами, сжатие блоков (по сути, текстур), профилирование и отладка, отрисовка на сервере.

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

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

ссылка на оригинал статьи https://habr.com/ru/articles/1053086/