这张图的效果是如何实现的?
问题描述
Blender 实现。先放成品:

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

</更新>
用插件什么的太无聊了,自己写程序才是王道。Delaunay triangulation 了解一下?当然这个算法写起来挺麻烦,所以我直接找了现成的⋯⋯⋯⋯⋯⋯⋯⋯ 有这个以后剩下的都是体力活了。既然是 triangulation,首先要有一坨点,所以步骤大概是这样的:
- 生成一坨随机的坐标
- 用 triangulation 生成三角形
- 用三角形生成 mesh
- 对 mesh 做合适的变形
- Blender 的 cycle 渲染器不能渲染「点」这种东西,所以要把这一坨点变成一坨球体
- Blender 也不能直接渲染边(edge),所以要把边变成 mesh
- 渲染
- 后期
首先生成随机坐标。在一个范围里随机生成点有很多方法,其中一些算法可以让生成的点看起来比较「均匀」,这样出来的效果是坠吼的。但是我懒,所以我直接用了随机数⋯⋯
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 源文件:https://drive.google.com/open?id=11RbB4x1lXi3c7944thgkpEOIbCG43DGM,注意里面有两个 scenes。
</更新>

评论 5
只会砌积木的blenderer路过
animation node可能更加无痛一点