О Слоях.
Слой - это временная цель рендеринга, на которой вы рисуете отдельно, а затем результат накладывается на основную цель рендеринга. В 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();
А вот пример использования геометрической маски - например, чтобы нарисовать текстуру только внутри круга:
// Предположим, у нас есть созданная геометрия 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 (будем использовать, когда дойдём до создания движка игры), разделение ресурсов между контекстами, сжатие блоков (по сути, текстур), профилирование и отладка, отрисовка на сервере.
Всем спокойной ночи или удачного дня, и удачи!
При желании материально поддержать перевод и структурирование информации - средства можете отправить через сбор в ЮМани.