Мыльные пузыри в Delphi #3. Растровая маска прозрачности
Опубликовано 23.08.2011 г. 00:05
В этот раз я попытался ускорить процесс отрисовки пузырей, ибо в прошлый все это добросовестно подтормаживало.
На самом деле даже не знаю стало ли лучше. Хотя вобще стало, при запуске загрузка процессора около 20 процентов. В прошлый раз было ближе к 40. Что изменилось: ранее для отрисовки пузыря использовались слои, как утверждает документация это сложно и медленно. А у нас в каждом кадре использовалось число слоев по количеству пузырей. Идея оптимизации была в том, что можно использовать маску прозрачности на основе изображения пузыря. Т.е. если раньше мы рисовали пузырь так: брали два эллипса (контур и вложенный), создавали кисть с прозрачностью в центре, заливали оба эллипса градиентом, с использованием маски из этой кисти с помощью слоя, то теперь рисование строится по следующему принципу. В основе пузыря всегда лежит залитый градиентом эллипс. Все что нам надо - наложить на эллипс маску прозрачности. Маска эта будет как и выше строится на основе эллипсов. Но суть в том, что маска такая создается только один раз, и просто применяется, при выводе пузыря. Т.е. слои вообще не используются в коде, даже при создании этой самой маски. Вот тестовая картинка того, как применяется маска. В данном случае растровая маска прозрачности, применена к кругу сплошной заливкой желтого цвета.
к
ак можно заметить, качество отрисовки значительно страдает. Связано это с тем, что для использования растровой маски необходимо использовать D2D1_ANTIALIAS_MODE_ALIASED в качестве настройки сглаживания. Ведет этом к тому, что сравнение прозрачности происходит попиксельно, и на резкой границе прозрачности у нас получается весьма резкий край. Чтобы замаскировать этот эффект, после отрисовки маски, мы возвращаем режим D2D1_ANTIALIAS_MODE_PER_PRIMITIVE и отрисовываем эллипс, в результате восстанавливая грань (рисуем эллипс, а не закрашиваем). Картинка с MSDN об использовании растра в качестве маски прозрачности:
Здесь в центре нарисована маска. Черным цветом изображены пикселы у которых прозрачность равна нулю, белым - единице. Такая маска накладывается на изображение слева, и после отрисовки на канве белого цвета, получается изображение справа. Приведу полный код создания растровой маски пузыря. Отчасти он повторяет прошлый код, в моментах создания кисти с круговым градентом, и геометрий для эллипса.
Но кажется выглядит неплохо. Следующим шагом могла бы стать доработка изменения цвета пузыря - ибо цвет до сих пор меняется произвольно при столкновении с гранью. Также возможна реализация алгоритма столкновения пузырей, это в общем то может представлять интерес. Помимо данных изменений был решен вопрос с падением программы при выходе (: глупый косяк (: а также все что касалось TBubble было переведено с integer/TPoint на double/TD2DPoint2F. Для интересующихся последний вариант исходного кода пузырей можно скачать здесь.

ак можно заметить, качество отрисовки значительно страдает. Связано это с тем, что для использования растровой маски необходимо использовать D2D1_ANTIALIAS_MODE_ALIASED в качестве настройки сглаживания. Ведет этом к тому, что сравнение прозрачности происходит попиксельно, и на резкой границе прозрачности у нас получается весьма резкий край. Чтобы замаскировать этот эффект, после отрисовки маски, мы возвращаем режим D2D1_ANTIALIAS_MODE_PER_PRIMITIVE и отрисовываем эллипс, в результате восстанавливая грань (рисуем эллипс, а не закрашиваем). Картинка с MSDN об использовании растра в качестве маски прозрачности:

Здесь в центре нарисована маска. Черным цветом изображены пикселы у которых прозрачность равна нулю, белым - единице. Такая маска накладывается на изображение слева, и после отрисовки на канве белого цвета, получается изображение справа. Приведу полный код создания растровой маски пузыря. Отчасти он повторяет прошлый код, в моментах создания кисти с круговым градентом, и геометрий для эллипса.
procedure CreateBubbleOpacityMask(); var gStops : array[0..2] of TD2D1GradientStop; gStopsCollection : ID2D1GradientStopCollection; bp : TD2D1BrushProperties; bob : ID2D1RadialGradientBrush; bmrt : ID2D1BitmapRenderTarget; sf : TD2D1SizeF; geometries : array[0..1] of ID2D1EllipseGeometry; gg : ID2D1GeometryGroup; begin //radial brush gStops[0] := D2D1GradientStop(0, D2D1ColorF(clWhite, 0)); gStops[1] := D2D1GradientStop(0.7, D2D1ColorF(clBlack, 0.2)); gStops[2] := D2D1GradientStop(1, D2D1ColorF(clBlack, 1)); FRenderTarget.CreateGradientStopCollection(@gStops, 3, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, gStopsCollection); FRenderTarget.CreateRadialGradientBrush( D2D1RadialGradientBrushProperties(point(0,0), point(0,0), 100, 100), nil, gStopsCollection, bob); //ellipse geometries with ellipse do begin point := D2D1PointF(0,0); radiusX := 100; radiusY := 100; end; FBubble := ellipse; Factory.CreateEllipseGeometry(ellipse, Geometries[0]); with ellipse do begin point := D2D1PointF(0,-33); radiusX := 84; radiusY := 65; end; Factory.CreateEllipseGeometry(ellipse, Geometries[1]); Factory.CreateGeometryGroup(D2D1_FILL_MODE_ALTERNATE, @Geometries,2, gg); // bitmap mask sf.width := 200; sf.height := 200; FRenderTarget.CreateCompatibleRenderTarget(@sf, nil,nil, 0, bmrt); bmrt.BeginDraw(); try bmrt.SetTransform(TD2DMatrix3x2F.Translation(100,100)); bob.SetOpacity(1); bmrt.Clear(D2D1ColorF(clWhite,0)); bmrt.FillGeometry(gg, bob); bob.SetOpacity(0.5); bmrt.FillGeometry(geometries[1], bob); finally bmrt.EndDraw() end; bmrt.GetBitmap(FBubbleOpacityMask); end;В результате мы получаем растр FBubbleOpacityMask : ID2D1Bitmap. Процесс создания ее прост. Сначала с помощью исхдной канвы мы создаем совместимую растровую канву bmrt : ID2D1BitmapRenderTarget. Рисование проводим на ней. Цвет для нас не имеет значения. Внимание стоит обращать только на прозрачность. Как и в прошлые разы заливаем сначала фигуру из двух эллипсов. Потом внутренний эллипс, уменьшив прозрачность. Нарисовав маску на канве мы получаем соответствующий растр. Теперь, чтобы нарисовать искомый пузырь поступаем следующим образом:
mt := TD2DMatrix3x2F.Translation(c.x - 100, c.y - 100); FRenderTarget.SetTransform(mt); rc := D2D1RectF(0,0,200,200); FRenderTarget.SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); FRenderTarget.FillOpacityMask(FBubbleOpacityMask, FBubbleBrush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS, @rc, @rc); FRenderTArget.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); //bubble border mt := TD2DMatrix3x2F.Translation(c.x, c.y); FRenderTarget.SetTransform(mt); FRenderTarget.DrawEllipse(FBubble,FBubbleBrush, 2);меняем режим сглаживания, используя метод FillOpacityMask применяем нашу маску и кисть с линейным градентом для заливки прямоугольной области. В итоге получаем пузырь. Чтобы сделать грань более красивой рисуем эллипс, с толщиной линии в 2 пикселя. Это правда немного изменяет вид пузыря:

Но кажется выглядит неплохо. Следующим шагом могла бы стать доработка изменения цвета пузыря - ибо цвет до сих пор меняется произвольно при столкновении с гранью. Также возможна реализация алгоритма столкновения пузырей, это в общем то может представлять интерес. Помимо данных изменений был решен вопрос с падением программы при выходе (: глупый косяк (: а также все что касалось TBubble было переведено с integer/TPoint на double/TD2DPoint2F. Для интересующихся последний вариант исходного кода пузырей можно скачать здесь.
Комментарии
Нет комментариев