【blender开发】网格基础3:正二十面体与细分 | 黄金比例

https://sinestesia.co/blog/tutorials/python-icospheres/

系列教程

1:生成2D 网格

2:立方体与矩阵 | Matrix函数

3:正二十面体与细分 | 黄金比例

4:圆角立方体

5:圆与圆柱

完整代码

import bpy
from math import sqrt

# -----------------------------------------------------------------------------
# 设置

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

# -----------------------------------------------------------------------------
# 创建基础二十面体
# 正二十面体,可以由3组黄金数生成,具体自己百度

# 黄金比例
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

# -----------------------------------------------------------------------------
# 添加对象到场景

mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, [], faces)

obj = bpy.data.objects.new(name, mesh)
bpy.context.scene.collection.objects.link(obj)

# BL2.8后的物体选择
obj.select_set(True)
bpy.context.view_layer.objects.active = obj

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

#bpy.ops.object.shade_smooth()

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

具体步骤

什么是二十面体 icosahedron

是一种有20个面的多面体。二十面体有几种类型。为了制作球,我们只关注凸正二十面体(也是最著名的一种)。更多特性请戳维基百科

为什么用二十面体?与UV球体相比,二十面体拥有更均匀的几何分布。由于几何体的密度较高,在两极附近变形UV球体往往会产生奇怪的结果,而二十面体则会产生一个更均匀和有机的结果。

最重要的是,二十面体是不对称的,这有助于有机变形。

本教程是基于Andreas Kahler的原始二十面体代码,适用于Python 3和Blender。

【blender开发】网格基础3:正二十面体与细分 | 黄金比例

设置

让我们开始导入,然后转到通常设置。

import bpy
from math import sqrt

# -----------------------------------------------------------------------------
# 设置

scale = 1
subdiv = 5
name = 'Icosomething'

# -----------------------------------------------------------------------------
# 添加对象到场景

mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, [], faces)

obj = bpy.data.objects.new(name, mesh)
bpy.context.scene.collection.objects.link(obj)

# BL2.8后的物体选择
obj.select_set(True)
bpy.context.view_layer.objects.active = obj

在设置中,subdiv将控制细分网格的次数,scale是缩放参数,与上一教程中的参数非常相似。subdiv设置为零将创建一个二十面体(而不是 icosphere)。

请注意, subdiv设置为 9 ,会有超过 500 万个面。具体取决于您的硬件(或者是否想让 Blender 崩溃)。

将球体放入 ico-sphere

简单地细分二十面体只会得到一个精致的二十面体。我们需要确保顶点以类似于球体的方式聚集在一起。

为了实现这一点,需要确保添加的顶点位于单位球体上。单位球体是一个半径为 1 的“假想”球体。

可以用一个简单的公式确定单位球面上每个点(顶点)的位置,然后将坐标固定到它上面。更多维基百科

为此,将有一个固定到单位球体的vertex()函数(并且也进行缩放)。

def vertex(x, y, z):
    """ 返回固定在单位球体上的顶点坐标 """
    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个三角形相交。请看下图(下面的面列表中,点是用数字表示的)。

【blender开发】网格基础3:正二十面体与细分 | 黄金比例

然而,如果分割所有的边,会遇到已经分割过的相同边。这将导致大量重复线段,建面时候会让人头痛。

为了防止这种情况,保留一个已分割过的边的列表(一个缓存),并在分割前检查它。这个缓存将是一个字典。键是顶点的索引,从小到大排序。这样一来,传递边的顶点,键都会保持不变。

middle_point_cache = {}
def middle_point(point_1, point_2):
    """ 找到一个中间点并投射到单位球体上 """

    # 首先检查我们是否已经切割了这条边,以避免重复的顶点
    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]

    # 如果它不在缓存中,可以把它剪掉
    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,最后将其添加到缓存中,并返回索引以形成面列表。

细分

有了mid_point()函数,就可以转到循环并进行细分。

在每个细分级别,我们创建一个新的空的面列表,并在最后用新的面替换原来的面列表。然后遍历每个面,找到它的三条边的中间点,存储索引并从中创建四个新的面

# 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

平滑

现在有一个非常接近球体的网格,但看起来仍然是面状的。是时候让它变得平滑了。

平滑阴影是面的属性。

因此,要将整个网格设置为平滑,需要给所有的面启用平滑阴影。你也可以使用bl自带的平滑操作项完成

bpy.ops.object.shade_smooth()

操作项的上下文正确可以使用这个。
在其他情况下,可能由于 "不正确的上下文 "而拒绝运行。
Blender的上下文是一种神变量,包含了关于应用程序当前状态的信息。包括诸如鼠标光标位置、活动区域等。
可以在调用一个操作项时覆盖上下文,但目前没有简单方法知道每个操作项上下文是什么。

幸运的是,可以通过循环设置每个面来完成这个 "更低级 "的工作。

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

在Blender脚本的世界里,"低级 "是指跳过操作项,直接进入对象的方法和属性。 from_pydata()也是一个"低级"工作的例子。

低级的好处是,它不依赖于上下文,通常更灵活,并节省通过操作符系统的开销。在这种情况下,你也可以决定只对某些面进行平滑处理。

给TA充电
共{{data.count}}人
人已充电
BlenderBlender开发

【Blender开发】网格基础2: 立方体与矩阵 | Matrix函数

2022-1-15 9:30:45

BlenderBlender开发

【Blender开发】UI:使用Blender自带图标

2022-1-15 20:54:55

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
搜索