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

Комментарии (0)