10/25/2019

Сетки с Python & Blender: икосферы



Икосаэдр - это многогранник с 20 гранями. Существует несколько видов икосаэдров. Однако, чтобы сделать икосферы, мы будем смотреть только на выпуклые правильные икосаэдры (также самый известный вид). 
Вы можете найти больше о них и их свойствах в Википедии
Итак, почему икосферы? Икосферы имеют более равномерное распределение геометрии, чем УФ- сферы. 

Сетки с Python & Blender: икосферы

теги 14.09.2017 @ Учебники Blender , Meshes , Python )

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

Серия учебников

Что такое икосаэдр

Икосаэдр - это многогранник с 20 гранями. Существует несколько видов икосаэдров. Однако, чтобы сделать икосферы, мы будем смотреть только на выпуклые правильные икосаэдры (также самый известный вид). Вы можете найти больше о них и их свойствах в Википедии
Итак, почему икосферы? Икосферы имеют более равномерное распределение геометрии, чем УФ- сферы. Деформирование УФ- сфер часто дает странные результаты вблизи полюсов из-за более высокой плотности геометрии, в то время как икосферы дают более ровный и органично выглядящий результат. Кроме того, икосферы асимметричны, что помогает продавать органическую деформацию.
Это руководство основано на оригинальном коде икосаэдра от Andreas Kahler, адаптированном для Python 3 и Blender.





Настройка его

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




import bpy
from math import sqrt

# -----------------------------------------------------------------------------
# Settings
name = 'Icosomething'
scale = 1
subdiv = 5

# -----------------------------------------------------------------------------
# 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

В настройках subdivбудет контролировать, сколько раз подразделить сетку, и scaleбудет простой равномерный масштабный параметр, очень похожий на тот, что был в предыдущем уроке. Установка subdivна ноль создаст икосаэдр (вместо икосферы). Обратите внимание, что  subdivзначение 9 приведет к созданию сетки с более чем 5 миллионами граней . Возможно, вы захотите остаться ниже этого значения в зависимости от вашего оборудования (или от того, насколько сильно вы хотите сбить Blender!).

Ввод сферы в икосферу

Простое подразделение икосаэдра даст нам только усовершенствованный икосаэдр. Нам нужно убедиться, что вершины сходятся таким образом, что напоминает сферу.
Чтобы это произошло, нам нужно убедиться, что добавляемые нами вершины лежат на единичной сфере. Единичная сфера является «мнимой» сферой радиуса 1. Мы можем определить положение каждой точки (вершины) на единичной сфере с помощью простой формулы, а затем зафиксировать в ней координаты. Больше на Википедии
Для этого у нас будет  vertex()функция, которая фиксирует сферу юнитов (и выполняет масштабирование тоже).




def vertex(x, y, z):
    """ Return vertex coordinates fixed to the unit sphere """

    length = sqrt(x**2 + y**2 + z**2)

    return [(i * scale) / length for i in (x,y,z)]

Сделать базовый икосаэдр

Теперь, когда мы знаем, что вершины падают на единичную сферу, мы можем пойти дальше и создать базовый икосаэдр. Как и в случае с кубом, проще всего вводить вершины и грани вручную.
Одним из способов построения икосаэдра является рассмотрение его вершин в качестве углов трех ортогональных золотых плоскостей. Они золотые самолеты , потому что они следуют за золотой пропорции . Вершины этих плоскостей лежат на координатах (0, ± 1, ± φ), (± φ, 0, ± 1) и (± 1, ± φ, 0). Обратите внимание, что буква φ (phi) представляет значение золотого сечения, а ± означает «отрицательный или положительный».
Эти комбинации приводят к 12 вершинам, которые создают 20 равносторонних треугольников с 5 треугольниками, встречающимися в каждой вершине. Проверьте следующую диаграмму (точки нумеруются для списка граней ниже).





Блин, это много математики! Тем не менее, все это довольно просто (и повторяется), когда вы думаете об этом. Математический спортсмен наверняка найдет это скучным. Вот код для создания икосаэдра.




# --------------------------------------------------------------
# Make the base icosahedron

# Golden ratio
PHI = (1 + sqrt(5)) / 2

verts = [
          vertex(-1,  PHI, 0),
          vertex( 1,  PHI, 0),
          vertex(-1, -PHI, 0),
          vertex( 1, -PHI, 0),

          vertex(0, -1, PHI),
          vertex(0,  1, PHI),
          vertex(0, -1, -PHI),
          vertex(0,  1, -PHI),

          vertex( PHI, 0, -1),
          vertex( PHI, 0,  1),
          vertex(-PHI, 0, -1),
          vertex(-PHI, 0,  1),
        ]


faces = [
         # 5 faces around point 0
         [0, 11, 5],
         [0, 5, 1],
         [0, 1, 7],
         [0, 7, 10],
         [0, 10, 11],

         # Adjacent faces
         [1, 5, 9],
         [5, 11, 4],
         [11, 10, 2],
         [10, 7, 6],
         [7, 1, 8],

         # 5 faces around 3
         [3, 9, 4],
         [3, 4, 2],
         [3, 2, 6],
         [3, 6, 8],
         [3, 8, 9],

         # Adjacent faces
         [4, 9, 5],
         [2, 4, 11],
         [6, 2, 10],
         [8, 6, 7],
         [9, 8, 1],
]

Стратегия подразделения

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





Однако, если бы мы разбили все ребра, мы бы быстро столкнулись с теми же ребрами, которые мы уже разбили. Это приведет к большому количеству дублированных вертов и головной боли при попытке построить лица.
Чтобы предотвратить это, давайте сохраним список уже разделенных ребер (кеш) и проверим его перед разбиением. Этот кеш будет словарем. Ключи будут индексом вершин, упорядоченным от меньшего к большему. Таким образом, ключ останется неизменным, независимо от того, как мы пройдем вершины ребра.




middle_point_cache = {}

def middle_point(point_1, point_2):
    """ Find a middle point and project to the unit sphere """

    # We check if we have already cut this edge first
    # to avoid duplicated verts
    smaller_index = min(point_1, point_2)
    greater_index = max(point_1, point_2)

    key = '{0}-{1}'.format(smaller_index, greater_index)

    if key in middle_point_cache:
        return middle_point_cache[key]

    # If it's not in cache, then we can cut it
    vert_1 = verts[point_1]
    vert_2 = verts[point_2]
    middle = [sum(i)/2 for i in zip(vert_1, vert_2)]

    verts.append(vertex(*middle))

    index = len(verts) - 1
    middle_point_cache[key] = index

    return index

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

Подразбивая

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




# Subdivisions
# --------------------------------------------------------------

for i in range(subdiv):
    faces_subdiv = []

    for tri in faces:
        v1 = middle_point(tri[0], tri[1])
        v2 = middle_point(tri[1], tri[2])
        v3 = middle_point(tri[2], tri[0])

        faces_subdiv.append([tri[0], v1, v3])
        faces_subdiv.append([tri[1], v2, v1])
        faces_subdiv.append([tri[2], v3, v2])
        faces_subdiv.append([v1, v2, v3])

    faces = faces_subdiv

Сделай это гладким

Теперь у нас есть сетка, которая очень хорошо аппроксимирует сферу, но все еще выглядит ограненной. Время сгладить это.
Плавное затенение является атрибутом лица. Итак, чтобы сделать всю сетку гладкой, нам нужно включить плавное затенение на всех ее гранях. Вы можете сделать это, используя тот же оператор, который мы используем при нажатии кнопки в пользовательском интерфейсе :Set smooth




bpy.ops.object.shade_smooth()

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




for face in mesh.polygons:
    face.use_smooth = True

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

Окончательный код





import bpy
from math import sqrt

# -----------------------------------------------------------------------------
# Settings

scale = 1
subdiv = 5
name = 'Icosomething'


# -----------------------------------------------------------------------------
# Functions

middle_point_cache = {}


def vertex(x, y, z):
    """ Return vertex coordinates fixed to the unit sphere """

    length = sqrt(x**2 + y**2 + z**2)

    return [(i * scale) / length for i in (x,y,z)]


def middle_point(point_1, point_2):
    """ Find a middle point and project to the unit sphere """

    # We check if we have already cut this edge first
    # to avoid duplicated verts
    smaller_index = min(point_1, point_2)
    greater_index = max(point_1, point_2)

    key = '{0}-{1}'.format(smaller_index, greater_index)

    if key in middle_point_cache:
        return middle_point_cache[key]

    # If it's not in cache, then we can cut it
    vert_1 = verts[point_1]
    vert_2 = verts[point_2]
    middle = [sum(i)/2 for i in zip(vert_1, vert_2)]

    verts.append(vertex(*middle))

    index = len(verts) - 1
    middle_point_cache[key] = index

    return index


# -----------------------------------------------------------------------------
# Make the base icosahedron

# Golden ratio
PHI = (1 + sqrt(5)) / 2

verts = [
          vertex(-1,  PHI, 0),
          vertex( 1,  PHI, 0),
          vertex(-1, -PHI, 0),
          vertex( 1, -PHI, 0),

          vertex(0, -1, PHI),
          vertex(0,  1, PHI),
          vertex(0, -1, -PHI),
          vertex(0,  1, -PHI),

          vertex( PHI, 0, -1),
          vertex( PHI, 0,  1),
          vertex(-PHI, 0, -1),
          vertex(-PHI, 0,  1),
        ]


faces = [
         # 5 faces around point 0
         [0, 11, 5],
         [0, 5, 1],
         [0, 1, 7],
         [0, 7, 10],
         [0, 10, 11],

         # Adjacent faces
         [1, 5, 9],
         [5, 11, 4],
         [11, 10, 2],
         [10, 7, 6],
         [7, 1, 8],

         # 5 faces around 3
         [3, 9, 4],
         [3, 4, 2],
         [3, 2, 6],
         [3, 6, 8],
         [3, 8, 9],

         # Adjacent faces
         [4, 9, 5],
         [2, 4, 11],
         [6, 2, 10],
         [8, 6, 7],
         [9, 8, 1],
        ]


# -----------------------------------------------------------------------------
# Subdivisions

for i in range(subdiv):
    faces_subdiv = []

    for tri in faces:
        v1 = middle_point(tri[0], tri[1])
        v2 = middle_point(tri[1], tri[2])
        v3 = middle_point(tri[2], tri[0])

        faces_subdiv.append([tri[0], v1, v3])
        faces_subdiv.append([tri[1], v2, v1])
        faces_subdiv.append([tri[2], v3, v2])
        faces_subdiv.append([v1, v2, v3])

    faces = faces_subdiv


# -----------------------------------------------------------------------------
# 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


# -----------------------------------------------------------------------------
# Smoothing

#bpy.ops.object.shade_smooth()

for face in mesh.polygons:
    face.use_smooth = True

Заворачивать

Это завершает третью часть этой серии. Если вы заинтересованы в создании сфер или применении точек / объектов сферическим образом, подумайте о том, чтобы узнать больше о единичной сфере и о том, как применять ее по нормали.
Что вы можете сделать для себя:
  • Оптимизируйте его (подсказка: вам не нужно хранить ключ в виде строки)
  • Принесите матрицы вращения и перевода из предыдущей части
  • Удалите настройку масштаба и используйте вместо нее матрицу
В следующий раз мы вернемся к кубам и узнаем, как сделать круглый куб, применить модификаторы и многое другое. У вас есть какие-либо вопросы или предложения для следующих уроков? Оставьте комментарий ниже!

Комментариев нет:

Отправить комментарий