如何用很短的代码创造「秋日氛围感」?
缘起
两三个月前,我和匡冶去上海出差,周日下午坐高铁回北京。忙活了一周,我们俩都精疲力竭地坐在高铁上。还有 3 个小时才到北京,不如写点代码消磨时间,也算放松一下身心。正好聊到 Minecraft (“别人的世界”) 和 MagicaVoxel,于是我们决定来个 Hackathon,用 Taichi 整个 GPU 光线追踪体素渲染器。在快到北京南站之前,我们有了第一版雏形:
高铁上的第一版渲染器原型
Taichi 是嵌入在 Python 中的并行编程语言,这使得我们的渲染器几乎可以在任何操作系统上运行,并且和 Python 很容易地交互。据我们所知,目前 Python 生态系统里面还没有工具能够实现跨平台的 GPU 光线追踪体素渲染器。Taichi 只需要大约 300 行代码就可以实现这个渲染器 ¹。
写完代码,我们才发现一个更严峻的问题:对我们老图形程序员来说,造个光线追踪渲染器容易,渲染出好看的图反而更难。加上写 UI 是个工作量很大的事情,我们只支持一个体素一个体素地编辑,这得猴年马月才能做出想要的结果...
好在 “批量操作体素” 这个事情本身也是可以写代码完成的。Minecraft 大神们可以用各种搭建技法创建自己的体素世界,咱没那个技法,要不直接写代码生成吧!开动!
愿景
做任何事情之前先要有个美好的愿景,才能确保自己在正确的方向上。在网上找了一些 Voxel 大佬的艺术作品,发现森林是一个常见题材。于是我找了找森林的照片,找到一张秋天的,意境很不错:
(图片来源于网络)
于是我决定照着这张照片和一些体素艺术家的作品,还原一个秋天的场景,再用光线追踪渲染出来,应该会挺有意思~
以下内容只需要一些基础的 Python 知识就可以阅读。Taichi 是一个嵌入在 Python 中的编程语言,能够让你的程序被 GPU 加速。如果对 Taichi 还不是很了解,可以参考这篇文章👉🏻搭建体素世界,真没想象中那么难… | Taichi 入门秘籍
运行代码,3D 漫游!
Follow 完这篇教程,你会得到一个 3D 场景并在里面漫游。代码是跨平台的,我的 Macbook 笔记本上也可以运行(20 FPS)。如果你有 RTX 3090 之类的核武器那运行得会更流畅。我的笔记本上移动相机的时候会稍有噪点,停下来很快就收敛了:
小笔记本录制,两倍速播放视频
如果你想体验这个场景,浏览完整的 91 行代码,可以 clone 作品仓库 ²。如果你想创建自己的场景,可以从我们的模板仓库 ³ 开始。
首届 Taichi 体素创意大赛正在如火如荼地进行,更多规则请见 GitHub,欢迎有兴趣的同学参加,一起切磋代码技能。
*另外有同学反馈表示 1.0.1 在某些环境上有些 Vulkan 兼容性的问题,使用 1.0.0 就可以解决。输入命令行 pip install taichi==1.0.0 即可。同时,这个 bug 已经修复了,下一个版本会 ship 这个 bug fix。
*相关 issue:https://github.com/taichi-dev/taichi/issues/4891
用体素搭建秋日小树林
先分析一下“愿景图”,里面有几个关键元素:枫(?)树,铺满落叶的地面和体积雾。我们的渲染器并不支持体积雾,好在我们可以用一个 45° 的斜阳(directional_light)加上偏黄的色温来模拟这个场景。
scene
import Scene
import taichi
as ti
from taichi.math
import *
scene = Scene(voxel_edges=
0, exposure=
2)
# 创建场景,指定体素描边宽度和曝光值scene.set_floor(
0, (
1.0,
1.0,
1.0))
# 地面高度scene.set_background_color((
0.5,
0.5,
0.4))
# 天空颜色scene.set_directional_light((
1,
1,
-1),
0.2, (
1,
0.8,
0.6))
# 光线方向和颜色@ti.kerneldefinitialize_voxels(): scene.set_voxel(vec3(
0),
1, vec3(
1))
# 在 (0, 0, 0) 加入一个白色 (1, 1, 1) 的体素!initialize_voxels()
scene.finish()
你就能得到如下场景:
先来一个小方块,调下光的方向和色温,来点秋天的感觉。
(不要问我为啥 voxel_edge=0 了还有描边,问就是浮点误差,离远点就看不见了...)
基座
看了几个大佬的体素作品,发现他们常用的一个技法是 “蛋糕切块”,也就是通过截面表现一些平时不容易看到的东西,比如说土地内部的结构,作为作品的基座:
蛋糕切块效果...(图片来源于网络)
那我们依葫芦画瓢,做一个泥土的基座。
画个饼:读完这一节你就能做出如上的底座,看起来还有点泥土的味道...
其实也不是太难,我们一层一层来。如果只考虑相同颜色的一层,那其实就是一个立方体,带上一些随机性。
我们首先要实现一个函数,来生成一个从 (pos[0], pos[1], pos[2]) 开始,大小是 size[0] x size[1] x size[2] 的立方体。当然,我们可以指定颜色 color。为了增加真实感,我们不能让这个立方体看起来太完美,所以加上一些噪声 color_noise:
I
in ti.grouped(
ti.ndrange((pos[
0], pos[
0] + size[
0]),
(pos[
1], pos[
1] + size[
1]),
(pos[
2], pos[
2] + size[
2]))):
scene.set_voxel(I,
1, color + color_noise * ti.random())
这里利用过了一个技巧:ti.ndrange,它能够在一行中实现一个多层 for 循环(否则要写 3 层 for loop,比较麻烦)。配上 ti.grouped,我们将 i, j, k 三个循环变量塞到一个向量 I = (i, j, k) 里面。
有了这个函数,我们就可以绘制一个大大的方块:
create_block(pos=ivec3(
0,
0,
0),
size=ivec3(
20,
40,
30),
color=vec3(
0.3,
0.5,
0.3),
color_noise=vec3(
0.1))
注意
从 (0, 0, 0) 开始长出来的一个 20x40x30 的方块
紧接着,我们只要用一个 4 层的 for 循环,把每一层绘制出来:
i
in range(
4):
create_block(ivec3(
-60, -(i +
1)**
2 -
40,
-60),
ivec3(
120,
2 * i +
1,
120),
vec3(
0.5 - i *
0.1) * vec3(
1.0,
0.8,
0.6),
vec3(
0.05 * (
3 - i)))
这样你就得到了一个淡黄的提拉米苏...
现在有个问题:地表颜色有点淡,我们来加一层地表:
看起来更美味了...
好的,说回来,我们并不是在做蛋糕,但是这个蛋糕形状的底座作为整个作品的基座还是不错的。于是,我们的基座就这样完成了!
树和落叶
有了基座,我们来点树。
新的饼:读完这一节会知道这个树怎么搞出来 :-)
首先,因为我们有很多树,代码行数也有限,我们需要有个画树的函数:
四个参数:
pos: 树根的位置
height:树的高度
radius:树叶半径 color:树叶颜色
我们一步步来。先搞个树干。这个比较简单,reuse 我们之前的 create_block 就好:
只有光秃秃的树干好像还有些枯燥。我们来加点叶子。这里我们加一个函数。先假设一棵树的叶子分布的区域是一个圆柱,圆柱中的每一个 voxel 都有一定概率存在或者不存在。我们希望这个概率离圆柱重心越远,就越小。于是我们得到了下面这个函数:
I
in ti.grouped(
ti.ndrange((-radius, radius), (-radius, radius),
(-radius, +radius))):
f = I / radius
d = vec2(f[
0], f[
2]).norm()
# 到圆柱中轴线的距离 prob = max(
0,
1 - d)**
2if ti.random() < prob:
scene.set_voxel(pos + I,
1, color + (ti.random() -
0.5) *
0.2)
这个树长得有点像冰棍...
好吧,看起来不是很自然... 通过一些脑补出来的数学函数和一些噪声,我们稍微优化一下:
@ti.func
def create_leaves(pos, radius, color):
for I in ti.grouped(
ti.ndrange((-radius, radius), (-radius, radius),
(-radius, +radius))):
f = I / radius
h =
0.5 -
max(
f[
1], -
0.5) *
0.5 d = vec2(
f[
0],
f[
2]).
norm()
prob =
max(
0,
1 - d)**
2 * h
# xz mask prob *= h
# y mask# noise prob += ti.
sin(
f[
0] *
5 + pos[
0]) *
0.02 prob += ti.
sin(
f[
1] *
9 + pos[
1]) *
0.01 prob += ti.
sin(
f[
2] *
10 + pos[
2]) *
0.03if prob <
0.1:
prob =
0.0if ti.random() <
prob: scene.set_voxel(pos + I,
1, color + (ti.random() -
0.5) *
0.2)
这个函数还有些复杂,但是整体的思路是对于树叶区域的每个体素,计算一个概率,概率和这个体素在空间中的位置有一些关系,还有一些噪声。加上其他细节,就大功告成啦!
加上一些细节,更像树了!
(同事:你这明明是更像一个炸鸡腿..)
因为我们在做秋天,应该有一些落叶。其实也很简单,就是在树下的圆盘区域里面随机撒一些点即可:
i,
j in ti.ndrange((-radius, radius), (-radius, radius)):
prob =
max((radius - vec2(i,
j).
norm()) / radius,
0)
prob = prob * prob
if ti.random() < prob *
prob: scene.set_voxel(pos + ivec3(i,
1,
j),
1,
color + ti.random() * vec3(
0.1))
在底部圆盘里面加一些随机的体素,模拟落叶,显得我们并不是炸鸡腿...
我们只要多调用几次 create_tree,就有了一片小树林。注意优化一下每棵树的位置、高度、颜色:
(
ivec3(
-20,
-40, 25), 65, 35,
vec3(1
.0, 0
.3, 0
.15))
create_tree(
ivec3(45,
-40,
-45), 15, 10,
vec3(0
.8, 0
.4, 0
.1))
create_tree(
ivec3(20,
-40, 0), 45, 25,
vec3(1
.0, 0
.4, 0
.1))
create_tree(
ivec3(30,
-40,
-20), 25, 15,
vec3(1
.0, 0
.4, 0
.1))
create_tree(
ivec3(30,
-40, 30), 45, 25,
vec3(1
.0, 0
.4, 0
.1))
树的部分收工!
围栏
最后一步了,其实也比较简单,就是沿着一个方向加入一段长条,然后每隔一段距离加个小竖条就行:
@ti.func
def make_fence(
start, direction,
length):
color = vec3(
0.5,
0.3,
0.2)
create_block(
start, direction *
length + ivec3(
3,
2,
3), color, vec3(
0.1))
fence_dist =
3for i
inrange(
length // fence_dist +
1):
create_block(
start + direction * i * fence_dist + ivec3(
1,
-3,
1),
ivec3(
1,
5,
1), color, vec3(
0))
调用一次 make_fence...
直接乘四,大功告成
至此,我们完成了基座、树和落叶、围栏,收工!
选个好的角度,按 P 截图...
用 W/S/A/D/Q/E 开启摄影漫游模式,按 P 截图。
好久不摸相机了,在体素世界里安慰一下自己作为摄影爱好者受伤的心灵吧 :-)
我也要玩!
参赛用到的体素渲染器只需要 Python 环境,支持 Windows、Mac、Linux,没有 GPU 的话也可以运行。
复制文末链接⁴,即可查看更多参赛教程及内部测试时候的作品集 ⁵(都有源代码)。
以上。
99 行代码的体素大赛截止时间是 5 月 18 日,优秀作品有纪念奖品哦!(可惜我不能参赛,要不然 Switch 一定是我的 hhh)
只要会 Python,并且有一颗喜欢创造的心,相信你一定能创建出自己喜欢的体素作品!
References:
1. https://github.com/taichi-dev/voxel-challenge/blob/main/renderer.py
2. https://github.com/yuanming-hu/voxel-art
3. https://github.com/taichi-dev/voxel-challenge/
4. https://github.com/taichi-dev/community/blob/main/events/voxel-challenge/reference-zh_cn.md
5. https://github.com/taichi-dev/voxel-challenge/issues/1
编后语:本文转载自胡渊鸣的知乎文章《99 行代码能干啥?造个体素小世界!》,摘录了创造体素秋景的教程,完整内容请点击“阅读原文”查看。也期待大家参与比赛、创造自己的体素小世界!
欢迎扫码添加小助手
回复「创意大赛」
进入赛事交流群
⬇️
最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。