Всем привет! На связи Глеб, в предыдущей статье мы рассмотрели работу с объектами на 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 и хотите обсудить прочитанное или поделиться своим опытом и знаниями, жду вас в комментариях! Сделаем статью более полной и полезной для новичков.