Прозрачность
|
Страница в процессе перевода. |
Чтобы сделать график прозрачным, необходимо добавить альфа-значение к его атрибуту color или colormap.
using CairoMakie
using FileIO
# color
fig, ax, p = image(0..11, -1..11, rotr90(FileIO.load(Makie.assetpath("cow.png"))))
scatter!(ax, 1:10,fill(10, 10), markersize = 40, color = :red)
scatter!(ax, 1:10, fill(9, 10), markersize = 40, color = (:red, 0.5))
scatter!(ax, 1:10, fill(8, 10), markersize = 40, color = RGBf(0.8, 0.6, 0.1))
scatter!(ax, 1:10, fill(7, 10), markersize = 40, color = RGBAf(0.8, 0.6, 0.1, 0.5))
# colormap
scatter!(ax, 1:10, fill(5, 10), markersize = 40, color = 1:10, colormap = :viridis)
scatter!(ax, 1:10, fill(4, 10), markersize = 40, color = 1:10, colormap = (:viridis, 0.5),)
scatter!(ax, 1:10, fill(3, 10), markersize = 40, color = 1:10, colormap = [:red, :orange],)
scatter!(ax, 1:10, fill(2, 10), markersize = 40, color = 1:10, colormap = [(:red, 0.5), (:orange, 0.5)])
cm = [RGBf(x^2, 1 - x^2, 0.2) for x in range(0, 1, length=100)]
scatter!(ax, 1:10, fill(1, 10), markersize = 40, color = 1:10, colormap = cm)
cm = [RGBAf(x^2, 1 - x^2, 0.2, 0.5) for x in range(0, 1, length=100)]
scatter!(ax, 1:10, fill(0, 10), markersize = 40, color = 1:10, colormap = cm)
fig
Проблемы с прозрачностью
Цвет, получаемый при перекрытии двух прозрачных объектов, зависит от их порядка. Возьмем, например, красный и синий маркеры с одинаковым уровнем прозрачности. Если синий маркер находится впереди, в месте их перекрытия следует ожидать более синего цвета. Если красный маркер находится впереди, следует ожидать более красного цвета.
using CairoMakie
scene = Scene(size = (400, 275))
campixel!(scene)
scatter!(
scene, [100, 200, 300], [100, 100, 100],
color = [RGBAf(1,0,0,0.5), RGBAf(0,0,1,0.5), RGBAf(1,0,0,0.5)],
markersize=200
)
scatter!(scene, Point2f(150, 175), color = (:green, 0.5), markersize=200)
p = scatter!(scene, Point2f(250, 175), color = (:green, 0.5), markersize=200)
translate!(p, 0, 0, -1)
scene
На рисунке выше соблюдаются три правила прозрачности.
-
Если два графика находятся на разных z-уровнях, график с более высоким z-уровнем будет располагаться перед графиком с более низким уровнем. (Зеленый круг справа находится позади всех остальных графиков.)
-
Порядок отрисовки графиков на одном z-уровне соответствует очередности их создания. (Красный и синий круги находятся за левым зеленым кругом.)
-
Элементы графика отрисовываются по порядку. (Левый красный круг находится за средним синим кругом, который находится за правым красным кругом.)
Первое правило следует из явной сортировки графиков. Оно соблюдается только в двух измерениях, так как в трех измерениях график может иметь переменную глубину. Второе и третье правила применяются в обоих случаях. Однако в трех измерениях они часто дают неверные результаты. Возьмем для примера две плоскости, повернутые так, что значение глубины будет разным.
using CairoMakie
fig = Figure()
ax = LScene(fig[1, 1], show_axis=false)
p1 = mesh!(ax, Rect2f(-1.5, -1, 3, 3), color = (:red, 0.5), shading = NoShading)
p2 = mesh!(ax, Rect2f(-1.5, -2, 3, 3), color = (:blue, 0.5), shading = NoShading)
rotate!(p1, Vec3f(0, 1, 0), 0.1)
rotate!(p2, Vec3f(0, 1, 0), -0.1)
fig
using GLMakie
fig = Figure()
ax = LScene(fig[1, 1], show_axis=false)
p1 = mesh!(ax, Rect2f(-1.5, -1, 3, 3), color = (:red, 0.5), shading = NoShading)
p2 = mesh!(ax, Rect2f(-1.5, -2, 3, 3), color = (:blue, 0.5), shading = NoShading)
rotate!(p1, Vec3f(0, 1, 0), 0.1)
rotate!(p2, Vec3f(0, 1, 0), -0.1)
fig
Оба бэкенда обрабатывают этот случай неправильно. Кажется, что CairoMakie игнорирует глубину и просто отрисовывает плоскости в порядке их построения. Однако это не совсем так: бэкенд CairoMakie учитывает глубину для каждого графика, а в некоторых случаях и для каждого элемента (например, треугольников в трехмерной сетке). Но он не может обрабатывать глубину на уровне пикселей.
В свою очередь, GLMakie может обрабатывать глубину на уровне пикселей, как видно из правильного порядка выше. Проблема с прозрачностью здесь заключается в том, что очередность применения цветов к пикселю заранее неизвестна. GLMakie сначала отрисует красную плоскость и зарегистрирует значения глубины для каждого пикселя. Затем он отрисует синюю плоскость, если она находится перед красной. Точное решение этой задачи потребовало бы сбора цветов и значений глубины для каждого пикселя, их сортировки, а затем смешивания в определенном порядке. Это было бы очень затратно и поэтому редко применяется.
Прозрачность, независимая от порядка
В GLMakie реализована приблизительная схема смешения прозрачных цветов — прозрачность, независимая от порядка (OIT). Вместо использования обычного зависимого от порядка смешения alpha * color + (1 - alpha) * background_color здесь применяется взвешенная сумма с весами, основанными на глубине и альфа-значении. Вы можете включить OIT, задав transparency = true для определенного графика.
using GLMakie
fig = Figure()
ax = LScene(fig[1, 1], show_axis=false)
p1 = mesh!(ax, Rect2f(-2, -2, 4, 4), color = (:red, 0.5), shading = NoShading, transparency = true)
p2 = mesh!(ax, Rect2f(-2, -2, 4, 4), color = (:blue, 0.5), shading = NoShading, transparency = true)
p3 = mesh!(ax, Rect2f(-2, -2, 4, 4), color = (:red, 0.5), shading = NoShading, transparency = true)
for (dz, p) in zip((-1, 0, 1), (p1, p2, p3))
translate!(p, 0, 0, dz)
end
fig
Будучи приблизительной схемой, OIT имеет свои сильные и слабые стороны. У OIT есть два существенных недостатка.
-
Смешение происходит всегда, даже если полностью непрозрачный цвет (альфа-значение = 1) должен скрывать другой.
-
Смешение не является четким — когда два цвета с одинаковым альфа-значением смешиваются при схожих значениях глубины, их выходной цвет будет схожим.
using GLMakie
fig = Figure(size = (800, 400))
ax1 = LScene(fig[1, 1], show_axis=false)
p1 = mesh!(ax1, Rect2f(-2, -2, 4, 4), color = :red, shading = NoShading, transparency = true)
p2 = mesh!(ax1, Rect2f(-2, -2, 4, 4), color = :blue, shading = NoShading, transparency = true)
p3 = mesh!(ax1, Rect2f(-2, -2, 4, 4), color = :red, shading = NoShading, transparency = true)
for (dz, p) in zip((-1, 0, 1), (p1, p2, p3))
translate!(p, 0, 0, dz)
end
ax2 = LScene(fig[1, 2], show_axis=false)
p1 = mesh!(ax2, Rect2f(-1.5, -1, 3, 3), color = (:red, 0.5), shading = NoShading, transparency=true)
p2 = mesh!(ax2, Rect2f(-1.5, -2, 3, 3), color = (:blue, 0.5), shading = NoShading, transparency=true)
rotate!(p1, Vec3f(0, 1, 0), 0.1)
rotate!(p2, Vec3f(0, 1, 0), -0.1)
fig
Обратите внимание, что вы можете без проблем смешивать непрозрачные графики с атрибутом transparency = false с прозрачными графиками OIT. Таким образом, первая проблема на самом деле актуальна не для действительно непрозрачных графиков, а скорее для графиков, близких к непрозрачным.
Еще одна возможная проблема заключается в том, что часть графика может стать черной или белой. Это происходит из-за того, что во время расчета OIT числа с плавающей запятой становятся бесконечными из-за добавления большого количества прозрачных цветов вблизи камеры. Это можно исправить двумя способами.
-
Переместите плоскость отсечения
nearближе к камере. Дляax::LSceneэто можно сделать, приблизивax.scene.camera_controls.near[]к 0 и вызвавupdate_cam!(ax.scene, ax.scene.camera_controls). -
Уменьшите значение
GLMakie.transparency_weight_scale[], которое контролирует максимальный вес, придаваемый прозрачному цвету. Значение по умолчанию —1000f0.
Настройка второго из этих значений также может помочь повысить резкость.