О Слоях.
Слой — это временная цель рендеринга, на которой вы рисуете отдельно, а затем результат накладывается на основную цель рендеринга. В Direct2D слой представлен интерфейсом ID2D1Layer. Подобно кистям, слои создаются рендер-таргетом и являются ресурсами, зависимыми от устройства. Слой может использоваться только одной целью рендеринга в один момент времени. Если размер слоя не указан при создании, метод PushLayer автоматически определяет минимальный необходимый размер на основе содержимого и маски. Пример работы со слоями (сначала общий план):
Пример работы со слоями (сначала общий план):
-
Создаём слой через CreateLayer
-
Добавляем слой через PushLayer — следующие команды(рисования) будут направляться в слой
-
Достаём слой через 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/