Adobe PhotoshopRhino建筑表现Grasshopper建筑软件

这张图的效果是如何实现的?

问题描述

地铁风
不会撸程序的设计狮不是好科研狗

Blender 实现。先放成品:

<更新>
这是 40k 个点的:

</更新>

用插件什么的太无聊了,自己写程序才是王道。Delaunay triangulation 了解一下?当然这个算法写起来挺麻烦,所以我直接找了现成的⋯⋯⋯⋯⋯⋯⋯⋯ 有这个以后剩下的都是体力活了。既然是 triangulation,首先要有一坨点,所以步骤大概是这样的:

  1. 生成一坨随机的坐标
  2. 用 triangulation 生成三角形
  3. 用三角形生成 mesh
  4. 对 mesh 做合适的变形
  5. Blender 的 cycle 渲染器不能渲染「点」这种东西,所以要把这一坨点变成一坨球体
  6. Blender 也不能直接渲染边(edge),所以要把边变成 mesh
  7. 渲染
  8. 后期

首先生成随机坐标。在一个范围里随机生成点有很多方法,其中一些算法可以让生成的点看起来比较「均匀」,这样出来的效果是坠吼的。但是我懒,所以我直接用了随机数⋯⋯

class Point2D(object):
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def toVert(self, z=0):
        return (self.x, self.y, z)

def genRandomPoints(count, xrange, yrange):
    for i in range(count):
        yield Point2D(random.uniform(*xrange), random.uniform(*yrange))

然后生成三角形并生成 mesh

def createMesh(points):
    me = bpy.data.meshes.new("TheMesh")
    ob = bpy.data.objects.new("TheObject", me)
    ob.location = Vector((0,0,0))
    ob.show_name = True

    # Link object to scene and make active
    scn = bpy.context.scene
    scn.objects.link(ob)
    scn.objects.active = ob
    ob.select = True

    # Create mesh from given verts, faces.
    verts = tuple(point.toVert() for point in points)
    faces = computeDelaunayTriangulation(points)
    me.from_pydata(verts, [], faces)
    # Update mesh with new data
    me.update()
    return ob

createMesh(tuple(genRandomPoints(10000, (-4, 4), (-2, 2))))

我这里只用了 10000 个点。本来计划只是试一下,成品应该用 40000 个点的,但是最后我懒得再搞一次了⋯⋯ 出来的效果是这样的:

这个 mesh 还是平平的一坨,不好看,所以要加置换和变形

然后就可以放小球了。

def createBallsFromMesh(mesh):
    Verts = mesh.vertices
    bpy.ops.object.select_all(action='DESELECT')
    bpy.ops.mesh.primitive_uv_sphere_add(segments=4, ring_count=4, size=0.002,
                                   location=(0,0,0))
    RefBall = bpy.context.object                               

    Balls = []
    i = 0
    for Vert in Verts:
        if i % 100 == 0:
            print('.', end="", flush=True)
        Ball = RefBall.copy()
Ball.data = RefBall.data.copy()
bpy.context.scene.objects.link(Ball)
        Ball.location = Vert.co.to_tuple()
        Balls.append(Ball)
        i += 1
    return Balls

Balls = createBallsFromMesh(bpy.context.selected_objects[0].data)

bpy.ops.object.select_all(action='DESELECT')
for Ball in Balls:
Ball.select = True
bpy.context.scene.objects.active = Balls[0]
bpy.ops.object.join()

这里有个坑。添加小球的时候不要用 10000 次 primitive_uv_sphere_add(),而是要建一个样本然后 copy 10000 次。(思考题:这是为什么呢?)

球长这样:

接下来就没 Python 什么事了(当然你完全可以写个 Python 让整个过程全自动,但是我懒啊⋯⋯)。在 mesh 上加个 Wireframe 修改器,把 mesh 和球的材质都设成自发光,然后就可以渲染了。

直接渲染出来效果是比较愣的,还需要后期一下。大概这样:

然后加字出图就可以了。

整个流程有很多可以玩的地方:

  • 随机生成坐标的算法对最终效果影响很大,有很多 non-trivial 的算法可选。
  • 可以生成更密的 mesh。
  • 可以生成多个 mesh 放在一起。
  • 后期的时候有各种效果可以玩,比如这种杀马特效果:

<更新>
玩了点别的花样,高斯分布的点,按距离随机生成 edge:

Blender 源文件:drive.google.com/open?,注意里面有两个 scenes。
</更新>

51 赞同 5 感谢 69 收藏 5 条已备份评论 发布于 2018-05-04 知乎原页 ↗

评论 5