Собираем генератор данных на Blender. Часть 2: Камера
Всем привет! На связи Глеб, в предыдущей статье мы рассмотрели работу с объектами на Blender. Но для того, чтобы создать минимально жизнеспособный генератор, нужно разобраться в том, как работают камеры.
Получение доступа к камере
На сцене камера отображается, как обычный объект «bpy.types.Object». Мы можем менять стандартные свойства, которые рассмотрели в предыдущей статье. Но для камеры также определен набор специальных настроек: фокусное расстояние, размер сенсорной матрицы и прочие. Доступ к ним мы можем получить через класс «bpy.types.Camera».
В прошлой статье мы увидели, что у экземпляра «bpy.types.Object» есть свойство «data», в котором лежит экземпляр класса, предоставляющий доступ к специальным свойствам. Это как раз то, что нам нужно. Ведь в случае с камерой внутри «data» лежит тот самый объект с типом «bpy.types.Camera».
Поэтому, чтобы получить доступ к свойствам, которые мы упомянули ранее, нужно сделать следующее:
camera_object = bpy.data.objects['Camera']
camera = camera_object.data
Теперь через переменную «camera» мы можем получить доступ к следующим свойствам и методам. Часть из которых мы рассмотрим чуть позже.
Выбор основной камеры
Так как на сцене может быть несколько камер, нам нужно выбрать основную, с которой будет вестись съемка.
# Получаем сцену, с которой работаем
scene = bpy.context.scene
# Получаем камеру, которую будет использовать, как основную
camera = bpy.data.objects['Camera']
# Устанавливаем камеру, как основную для этой сцены
scene.camera = camera
Что видит камера?
Для настройки камеры полезно понять какие объекты попадают в её поле зрения. Для этого мы можем переключиться в режим «Camera view», которой покажет, что видит основная камера сцены.
Разрешение
В blender нет свойства, которое бы отвечало за разрешение конкретной камеры. Для этой цели мы будем использовать свойство рендеринга.
# Получаем настройки рендеринга для конкретной сцены
render = bpy.context.scene.render
# Меняем разрешение относительно осей x и y в пикселях
render.resolution_x = 1920
render.resolution_y = 1080
Важно! Новое разрешение автоматически применится ко всем камерам.
На данный момент мы научились получать доступ к камере и менять разрешение. Пришло время разобраться со свойствами, которые предоставляет класс «bpy.types.Camera».
lens
Это свойство отвечает за фокусное расстояние. Единицей измерения является миллиметр.
camera = bpy.data.cameras['Camera']
camera.lens = 10
sensor_width / sensor_height
Помимо фокусного расстояния мы можем взаимодействовать с размером сенсорной матрицы. Как и в случае с фокусным расстоянием, единицей измерения является миллиметр. Размер матрицы удобно использовать, когда у нас есть реальная камера, свойства которой мы хотим воспроизвести при генерации данных.
camera = bpy.data.cameras['Camera']
camera.sensor_width = 32
shift_x / shift_y
Смысл этих свойств легче всего объяснить с помощью наглядного примера.
Единицы измерения этих свойств рассчитываются относительно наибольшей стороны кадра:
shift_in_pixels / max(resolution_x, resolution_y) <- Это формула
Допустим, разрешение 1920x1080 и мы хотим сместить кадр на 100 пикселей вправо и 200 пикселей вниз.
resolution = (
bpy.context.scene.render.resolution_x,
bpy.context.scene.render.resolution_y
)
shift_x_in_px = 100
shift_y_in_px = -200
camera = bpy.data.cameras[‘Camera’]
camera.shift_x = shift_x_in_px / max(resolution)
camera.shift_y = shift_y_in_px / max(resolution)
Проецирование точек на кадр
Зачастую, нам требуются получить координаты объектов изображенных на кадре. Для этой цели мы будем использовать функцию «world_to_camera_view», находящуюся в библиотеке «bpy_extras».
Эта функция получает на вход следующие аргументы:
scene [ bpy.types.Scene ]: используется для определения размера кадра
obj [ bpy.types.Object ]: Камера, с которой происходит съемка
coord [ mathutils.Vector ]: Координаты точки, которую мы хотим отобразить на кадр
В результате использования «world_to_camera_view» мы получим вектор, состоящий из 3 значений: координата по оси х, координата по оси y, дистанция от камеры до объекта.
from bpy_extras.object_utils import world_to_camera_view
cube = bpy.data.objects['Cube']
scene = bpy.context.scene
camera = bpy.data.objects['Camera']
world_to_camera_view(scene, camera, cube.location)
# Vector((0.4990274906158447, 0.513164222240448, 11.256155967712402))
Наведение на объект
На этом этапе, мы хотим навести камеру на интересующий нас объект. Для решения этой задачи я нашел два решения.
Constraints
В blender есть встроенная возможность «приказать» камере отслеживать какой-то объект. Но стоит заметить, что камера будет наводиться на центр выбранного объекта.
# Получение доступа к объектам
camera = bpy.data.objects[‘Camera’]
target = bpy.data.objects[‘Cube’]
# Создание ограничения
constraint = camera.constraints.new(type=’TRACK_TO’)
constraint.target = target
Есть и другие интересные ограничения, которые можно использовать при создании своего генератора. Подробнее об этом можно прочитать в документации.
mathutils.vector
Суть этого подхода в том, что мы рассчитываем угол, на который нужно повернуть камеру, чтобы она смотрела на точку, а затем производим поворот. В отличии от предыдущего подхода, мы передаем координату, а не объект. Это добавляет нам пространство для маневра. К примеру, мы можем навести камеру на один из углов куба.
# Функция для наведения
def point_camera_at(
camera: bpy.types.Object,
target: mathutils.Vector,
track_axis: str = 'Z',
up_axis: str = 'Y'
):
vector = camera.location - target
camera.rotation_euler = vector.to_track_quat(
track_axis,
up_axis
).to_euler()
# Получаем доступ к объектам
camera = bpy.data.objects['Camera']
cube = bpy.data.objects['Cube']
# Получаем точки контура объекта cube. Используем функцию из предыдущей статьи.
bbox = bounding_box_world(cube)
# Наводим камеру на угол
point_camera_at(camera, bbox[0])
Важно! Изменяя положение камеры через api, стоит обратить внимание на одну небольшую хитрость: после трансформации нужно вручную обновить состояние сцены. Если пропустить этот шаг, то функция «world_to_camera_view» будет возвращать проекции относительно старого положения и поворота.
# Обновляем состояние сцены
bpy.context.view_layer.update()
The end
Blender мы в Friflex используем в рамках разработки наших продуктов по оцифровке спорта, в том числе idChess. И в серии статей на Хабр я делюсь своим опытом. В следующих материалах мы рассмотрим свойства источников света и работу с материалами через пользовательские свойства и драйверы и научимся загружать объекты из разных файлов и запускать рендеринг сцены через консоль.
Если вы работали с blender и хотите обсудить прочитанное или поделиться своим опытом и знаниями, жду вас в комментариях! Сделаем статью более полной и полезной для новичков.