Меши с помощью Python и Blender: кубики и матрицы

17 октября 2017 15 комментариев Артем Слаква Скриптинг на Python

cubes-and-matrices-1

Добро пожаловать во вторую часть данной серии уроков. Пришло время заняться математикой и научиться контролировать положение, вращение и масштаб мешей.

Этот урок основывается на первой части. Если вы слишком часто теряетесь, попробуйте вернуться и пройти сначала первую часть. Сегодня мы быстро рассмотрим создание кубиков, а затем перейдем к управлению мешами и центрами объектов. В конце урока, мы погрузимся в матрицы преобразования и научимся делать эти самые преобразования быстро и красиво.

Обычная настройка


Давайте начнем с импорта пакетов, которые нам нужны. Помимо обычного bpy, мы также будем использовать функцию radians() из математического пакета Python, а также класс Matrix из mathutils (это из Blender).

import bpy
import math
from mathutils import Matrix

Как и ранее, у меня будет раздел для размещения переменных, раздел служебных функций и, наконец, раздел основной части кода. Функция vert() выглядит бесполезной, но она понадобится чуть позже.

# -----------------------------------------------------------------------------
# Settings
name = 'Cubert'
 
# -----------------------------------------------------------------------------
# Utility Functions
 
def vert(x,y,z):
    """ Make a vertex """
 
    return (x, y, z)
 
 
# -----------------------------------------------------------------------------
# Cube Code
 
verts = []
faces = []
 
 
# -----------------------------------------------------------------------------
# Add Object to Scene
 
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, [], faces)
 
obj = bpy.data.objects.new(name, mesh)
bpy.context.scene.objects.link(obj)
 
bpy.context.scene.objects.active = obj
obj.select = True

Создание куба


Создание куба менее гламурно, чем вы могли ожидать, потому что нет никакого фантастического алгоритма для их создания. Вместо этого мы должны вводить позиции вершин и граней вручную. К счастью, кубы имеют только 6 граней и 8 вершин.

verts = [vert(1.0, 1.0, -1.0),
         vert(1.0, -1.0, -1.0),
         vert(-1.0, -1.0, -1.0),
         vert(-1.0, 1.0, -1.0),
         vert(1.0, 1.0, 1.0),
         vert(1.0, -1.0, 1.0),
         vert(-1.0, -1.0, 1.0),
         vert(-1.0, 1.0, 1.0)]
 
 
faces = [(0, 1, 2, 3),
         (4, 7, 6, 5),
         (0, 4, 5, 1),
         (1, 5, 6, 2),
         (2, 6, 7, 3),
         (4, 0, 3, 7)]

Запустите скрипт сейчас, и вы получите стандартный куб. Это было легко! Давайте посмотрим, что с этим можно сделать.
cubes-and-matrices-2

Перемещение центра объекта


Каждый объект в Blender имеет свой центр, который определяет его положение в трехмерном пространстве. Когда мы говорим о позиции объекта, мы фактически говорим о позиции его центральной точки (origin point). С другой стороны, когда мы говорим о центральной точке, мы фактически говорим о центральной точке относительно меша.

Запутались? Посмотрите на изображение ниже.
cubes-and-matrices-3
Как вы можете видеть, когда оранжевая точка (центр объекта) находится на сетке, позиция объекта (0, 0, 0), хотя сам меш поднят на 1 единицу по оси Z и его позиция находится в положении (0, 0, 1). Но интерес заключается в позиции меша. В обоих случаях меш смещен на 1, но во втором случае (если быть точным) он смещен на -1. Если бы позиция меша была (0, 0, 0), он был бы в воздухе, в том же месте, где и его центр объекта. Как вы можете видеть, позиция меша соотносится с положением центра объекта, но не зависит от положения начала оси координат.

Зная это, мы можем сделать две вещи:

  • Изменить положение меша относительно центральной точки. Это то же самое, что перемещение меша в режиме редактирования.
  • Изменить положение центральной точки, оставив меш в том же месте. В этом уроке это будет центр оси координат.

Мы начнем с изменения положения меша относительно его центра. Я буду называть это смещением, чтобы не запутаться.

offset = (0, 0, 1)
 
def vert(x,y,z):
    """ Make a vertex """
 
    return (x + offset[0], y + offset[1], z + offset[2])

Мы можем использовать функцию vert для перемещения меша с простым добавлением.

Смещение на 1 по оси Z разместит куб на сетке оставив его центр внизу.
cubes-and-matrices-4
Теперь давайте попробуем переместить его центральную точку, при этом оставив куб в центре сцены. Есть только одна небольшая проблема: мы не можем перенести исключительно сам центр объекта. Перемещение центра объект переместит и сам объект в пространстве.

Нам нужно изменить исходное положение центра, тем самым переместив положение меша, а затем переместить меш в обратном направлении относительно смещение центра.

obj.location = [i * -1 for i in offset]

Помните, что расположение — это кортеж, поэтому мы должны использовать выражение для его установки.

Попробуйте запустить код сейчас. Меш снова находится в центре сцены. Но центральная точка (и объект) теперь находится в положении (0, 0, -1).
cubes-and-matrices-5
Поскольку мы меняем местоположение используя отрицательные значения смещения, результирующая позиция объекта будет отрицательна. Соответственно, использование отрицательного смещения приводит к положительному положению объекта.

offset = (0, 0, -5)

cubes-and-matrices-6
Итак, что, если вы хотите изменить положение центра объекта, а также изменить позицию меша произвольным образом, чтобы он не находились всегда в центре сцены? Мы можем добавить другое смещение, чтобы представить это. И тогда мы можем добавить это смещение в выражении местоположения, чтобы переместить меш. Давайте также переименуем предыдущее смещение, чтобы все было ясно и понятно.

origin_offset = (0, 0, -5)
mesh_offset = (1, 0, 0)
 
def vert(x,y,z):
    """ Make a vertex """
 
    return (x + origin_offset[0], y + origin_offset[1], z + origin_offset[2])
 
 
obj.location = [(i * -1) + mesh_offset[j] for j, i in enumerate(origin_offset)]

Обратите внимание, что теперь мы должны использовать enumerate(), чтобы получить индекс для добавления.

Есть несколько других вещей, которые мы могли бы сделать с выражениями и функцией vert(). Но есть другой способ преобразования мешей и объектов. Способ, который является более чистым и быстрым.

Войдите в матрицу

Матрицы


cubes-and-matrices-7
В математике матрицы представляют из себя прямоугольные массивы чисел (а иногда и других вещей). Их можно добавлять, вычитать и умножать между собой. Вы найдете матрицы почти в каждой сфере, где задействована математика.

Единственная сфера, которая нас беспокоит, это компьютерная графика, и в этом контексте матрицы часто используются для представления преобразований. Сюда относятся такие вещи, как перемещение, масштабирование или вращение. Матрицы, представляющие линейные преобразования, называются «Матрицы преобразования».

Положение объектов, поворот и масштаб определяются как матрицы преобразования по отношению к системе координат. Даже если вы думаете, что нет никаких трансформации!

Например, давайте представим себе «объект по умолчанию». Этот объект находится в центре оси координат (0, 0, 0), имеет масштаб 1 и поворот 0 (по всем осям). Мы можем представлять местоположение, масштаб или поворот любого объекта в качестве матрицы преобразования этого объекта по умолчанию. В Blender это называется Всемирной матрицей, и это свойство доступно для всех объектов.

Кстати, «координаты сцены» на самом деле называются «World Coordinates» в Blender. Существуют и другие координатные пространства, а также матрицы для них, но мы рассмотрим это в другом уроке данной серии уроков.

Обратите внимание, что вам не нужно быть мастером матрицы, чтобы использовать их. Разработчики blender благословили нас классом Matrix, который делает почти всю работу за нас, и вам даже не придется видеть матрицы, пока вы работаете с ними.

Хорошо, давайте поиграем с этой матричной концепцией. Добавляем новый объект (используя Shift + A). Выберите его, затем вставьте этот скрипт в текстовый редактор и запустите его.

import bpy
 
print('-' * 80)
print('World Matrix \n', bpy.context.object.matrix_world)

Вы должны увидеть примерно следующее в консоли Blender:

--------------------------------------------------------------------------------
World Matrix
<Matrix 4x4 (1.0000, 0.0000, 0.0000, 0.0000)
            (0.0000, 1.0000, 0.0000, 0.0000)
            (0.0000, 0.0000, 1.0000, 0.0000)
            (0.0000, 0.0000, 0.0000, 1.0000)>

Поскольку мы не перемещали, не вращали и ничего не делали с объектом, его значения совпадали с нашим воображаемым «объектом по умолчанию». Матрица, подобная этой, называется Матрицей Идентичности в математике, и она означает, что нет никаких трансформаций.

Теперь переместите объект куда-нибудь и снова запустите сценарий.

--------------------------------------------------------------------------------
World Matrix
<Matrix 4x4 (1.0000, 0.0000, 0.0000, -8.8360)
            (0.0000, 1.0000, 0.0000, -1.1350)
            (0.0000, 0.0000, 1.0000,  8.9390)
            (0.0000, 0.0000, 0.0000,  1.0000)>

Ага! Теперь матрица содержит некоторое изменение. Значения будут разными в зависимости от того, куда вы переместите ваш объект. Как видите, последний столбец содержит координаты X, Y, Z по отношению к центру сцены (мировое пространство).

Что будет, если мы сбросим местоположение (Alt + G) и попробуем масштабирование?

--------------------------------------------------------------------------------
World Matrix
<Matrix 4x4 (0.7280,  0.0000, 0.0000, 0.0000)
            (0.0000, -1.4031, 0.0000, 0.0000)
            (0.0000,  0.0000, 1.7441, 0.0000)
            (0.0000,  0.0000, 0.0000, 1.0000)>

Теперь последний столбец не изменился, так как объект вернулся в координаты (0, 0, 0). Однако мы можем видеть, что три значения изменились, которые отвечают за масштабирование по X, Y и Z.

Вращение сложнее, так как вы можете вращаться вокруг трех осей, и каждое вращение представлено преобразованиями на двух других осях. Это немного выходит за рамки этого урока, поэтому я оставлю несколько ссылок в конце, если вы захотите углубиться в математику.

--------------------------------------------------------------------------------
World Matrix
<Matrix 4x4 (-0.9182,  0.3398, -0.2037, 0.0000)
            (-0.2168, -0.8612, -0.4597, 0.0000)
            (-0.3316, -0.3780,  0.8644, 0.0000)
            ( 0.0000,  0.0000,  0.0000, 1.0000)>

Вы также можете задаться вопросом о последней строке. Матрицы преобразования для трехмерного пространства фактически четырехмерные. Последняя строка — дополнительное измерение. Это математический «трюк», позволяющий матрице выполнять преобразования. Опять же, я не буду слишком углубляться в это. Проверьте ссылки в конце для получения дополнительной информации. В любом случае, это неважно для наших целей, поскольку этот ряд никогда не изменится.

Ниже наглядное изображение различных преобразований:
cubes-and-matrices-8

Использование матриц


Матрицы преобразования можно объединить, чтобы создать единую матрицу, которая включает в себя все результаты всех преобразований. Это делается путем их умножения. Это означает, что мы можем взять глобальную матрицу объекта и умножить ее на матрицу преобразования, чтобы получить новую матрицу, которая включает изменения обеих матриц. Затем мы можем присвоить ее как матрицу мира объекта и, таким образом, преобразовать объект.

obj.matrix_world *= some_transformation_matrix

Пришло время вникнуть в этот класс Matrix и посмотреть, как мы можем использовать его для генерации матриц.

Перемещение

Начнем с самых простых: движущихся вещей. Все, что нам нужно сделать, это вызвать метод перемещения класса Matrix с вектором (или кортежем) значений для каждой оси.

translation_matrix = Matrix.Translation((0, 0, 2))
obj.matrix_world *= translation_matrix
Масштабирование

Масштабирование принимает три аргумента. Первый — фактор масштаба. Второй — размер матрицы, он может быть либо 2 (2×2), либо 4 (4×4). Но поскольку мы работаем с 3D-объектами, то всегда должно быть 4. Конечные аргументы — это вектор, определяющий ось для масштабирования. Это может быть либо нулевое значение без масштабирования, либо 1 для масштабирования.

scale_matrix = Matrix.Scale(2, 4, (0, 0, 1)) # Scale by 2 on Z
obj.matrix_world *= scale_matrix
Вращение

Вращение принимает почти те же аргументы, что и масштаб. Первый — угол поворота в радианах. Второй — размер матрицы (такой же, как и раньше). Третий — ось вращения. Вы можете передать строку, такую как «X», «Y» или «Z», или вектор.

rotation_mat = Matrix.Rotation(math.radians(20), 4, 'X')
obj.matrix_world *= rotation_mat
Объединяя все это

Мы можем соединять преобразования вместе, умножая их один за другим. Но будьте осторожны, умножение матрицы не является коммутативным. Порядок имеет значение. Начните с перемещения, затем поворот и масштаб. Если вы получаете странные значения, которые выглядят как ошибки округления, посмотрите на порядок, в котором вы умножаете.

translation = (0, 0, 2)
scale_factor = 2
scale_axis = (0, 0, 1)
rotation_angle = math.radians(20)
rotation_axis = 'X'
 
translation_matrix = Matrix.Translation(translation)
scale_matrix = Matrix.Scale(scale_factor, 4, scale_axis)
rotation_mat = Matrix.Rotation(rotation_angle, 4, rotation_axis)
 
obj.matrix_world *= translation_matrix * rotation_mat * scale_matrix

cubes-and-matrices-9
Матрицы также могут использоваться для изменения меша вместо объекта. Для этого мы можем использовать метод transform(). Все, что он просит, это матрицу.

obj.data.transform(Matrix.Translation(translation))

Не забывайте, что мы также можем комбинировать матрицы путем умножения, чтобы вы могли сделать несколько преобразований за один раз.

obj.data.transform(translation_matrix * scale_matrix)

cubes-and-matrices-10
Матрицы не только быстрее, но этот метод также выполняется прямо на C, поэтому он работает значительно лучше, нежели вычисление положения каждой вершины на Python (что довольно медленно и ресурсозатратно). Кроме того, мы можем сделать это в одну строку.

Потрясающе!

Финальный код


import bpy
import math
from mathutils import Matrix
 
# -----------------------------------------------------------------------------
# Settings
 
name = 'Cubert'
 
# Origin point transformation settings
mesh_offset = (0, 0, 0)
origin_offset = (0, 0, 0)
 
# Matrices settings
translation = (0, 0, 0)
scale_factor = 1
scale_axis = (1, 1, 1)
rotation_angle = math.radians(0)
rotation_axis = 'X'
 
 
# -----------------------------------------------------------------------------
# Utility Functions
 
def vert(x,y,z):
    """ Make a vertex """
 
    return (x + origin_offset[0], y + origin_offset[1], z + origin_offset[2])
 
 
# -----------------------------------------------------------------------------
# Cube Code
 
verts = [vert(1.0, 1.0, -1.0),
         vert(1.0, -1.0, -1.0),
         vert(-1.0, -1.0, -1.0),
         vert(-1.0, 1.0, -1.0),
         vert(1.0, 1.0, 1.0),
         vert(1.0, -1.0, 1.0),
         vert(-1.0, -1.0, 1.0),
         vert(-1.0, 1.0, 1.0)]
 
 
faces = [(0, 1, 2, 3),
         (4, 7, 6, 5),
         (0, 4, 5, 1),
         (1, 5, 6, 2),
         (2, 6, 7, 3),
         (4, 0, 3, 7)]
 
 
# -----------------------------------------------------------------------------
# Add Object to Scene
 
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, [], faces)
 
obj = bpy.data.objects.new(name, mesh)
bpy.context.scene.objects.link(obj)
 
bpy.context.scene.objects.active = obj
obj.select = True
 
 
# -----------------------------------------------------------------------------
# Offset mesh to move origin point
 
obj.location = [(i * -1) + mesh_offset[j] for j, i in enumerate(origin_offset)]
 
 
# -----------------------------------------------------------------------------
# Matrix Magic
 
translation_matrix = Matrix.Translation(translation)
scale_matrix = Matrix.Scale(scale_factor, 4, scale_axis)
rotation_mat = Matrix.Rotation(rotation_angle, 4, rotation_axis)
 
obj.matrix_world *= translation_matrix * rotation_mat * scale_matrix
 
 
# -----------------------------------------------------------------------------
# Matrix Magic (in the mesh)
 
# Uncomment this to change the mesh
# obj.data.transform(translation_matrix * scale_matrix)

Завершение


На этом данная часть серии подошла к концу. Если матрицы вас заинтересовали, вот несколько ссылок, чтобы узнать о них больше. Ссылки находятся в порядке сложности.

Несколько вещей, которые вы можете попробовать сделать сами:

  • Поместите это все в цикл и сделайте волну из несколько кубов или поверните каждый вокруг своей оси.
  • Используйте матрицы, чтобы переместить центр куба, используя метод из этого урока.
  • Попробуйте масштабировать меш без матриц или изменить координаты объекта (подсказка: не забудьте функцию vert())
  • Попробуйте применить матрицу мира одного объекта другому.

На этом данный урок закончен, а в следующем мы продолжим изучение создания мешей с помощью Python и работе с ними.

источник урока

О сайте

На данном сайте Вы сможете найти множество уроков и материалов по графическому
редактору Blender.

Контакты

Для связи с администрацией сайта Вы можете воспользоваться следующими контактами:

Email:
info@blender3d.com.ua

Следите за нами

Подписывайтесь на наши страницы в социальных сетях.

На сайте Blender3D собрано огромное количество уроков по программе трехмерного моделирования Blender. Обучающие материалы представлены как в формате видеоуроков, так и в текстовом виде. Здесь затронуты все аспекты, связанные с Blender, начиная от моделирования и заканчивая созданием игр с применением языка программирования Python.

Помимо уроков по Blender, Вы сможете найти готовые 3D-модели, материалы и архивы высококачественных текстур. Сайт регулярно пополняется новым контентом и следит за развитием Blender.