餐饮加盟网-免费发布餐饮招商信息!本站不提供任何加盟资料,如需加盟请去其官网了解详情

来自宇宙的能量!银河系甜甜圈制作教学

来源:餐饮加盟
作者:小吃加盟·发布时间 2025-10-13
核心提示:nstagram 上一张星系甜甜圈的照片,带点深邃和魔幻色彩的糖衣,就算平时不吃甜食的人,都会忍不住多看几眼。Instagram 用户 So B

<>

nstagram 上一张星系甜甜圈的照片,带点深邃和魔幻色彩的糖衣,就算平时不吃甜食的人,都会忍不住多看几眼。Instagram 用户 So Beautifully Raw 就依样画葫芦,做出了一盘同样令人只顾看而舍不得吃的甜甜圈,不同的是,So Beautifully Raw 做的是素食版的甜甜圈,送给生病中的男友吃,希望男友吃了可以马上“甜”到病除。

【甜甜圈食谱】

材料:

中筋面粉1杯

原蔗糖半杯

酵母粉1又2分之一茶匙

盐巴4分之1茶匙

肉荳蔻4分之1茶匙

豆浆半杯

苹果醋1茶匙

带籽的香草精1茶匙

亚麻籽1汤匙+水3汤匙

素食用人造奶油4分之1杯(Nuttelex或Earthbalance皆可)

作法

1. 将烤箱预热到180度,并在甜甜圈模型上涂上油

2. 将上述的干料放入碗中混合、搅拌

3. 将豆浆、苹果醋混合,静置几分钟,直到变厚

4. 拿出一个小的平底锅,将豆浆、香草、亚麻籽、奶油以低温加热,直到奶油融化(记得千万别煮到滚)

5. 将湿料和干料混合,直到变成软面团型态

6. 使用茶匙,将面团放入均匀的甜甜圈模型中,必要时可以用手指压平面团,达到最佳状态

7. 放入烤箱烤12分钟,可以插入筷子来确认是不是烤熟了,如果筷子抽出来的时候是“干爽”的状态,那就是烤熟了!

8. 从模型中取出放凉,然后就是等着上糖衣啦!

【星系糖衣】

材料:

融化的椰子油2~3汤匙

豆浆2~3汤匙

糖粉1又3分之2~杯

素食用的天然食用色素(红色和蓝色)

作法:

1. 将椰子油和豆浆搅拌,然后慢慢加入糖粉搅拌,直到达到你要的浓稠度,必要时可以加点水调整稠度

2. 所谓的“星系”色泽,和制作大理石花纹的技巧类似,先在小碗中放入一些白色糖衣,然后滴入几滴色素,用汤匙轻轻搅拌,让色素化开,并适时调整颜色和饱和度

3. 将一半的甜甜圈泡入糖衣后稍微的旋转一下,就能在甜甜圈上裹住一整片银河

4. 最后可以在甜甜圈上,再洒上一些食用色素粉,就一整大功告成啦!

(编辑:小仔)

<>< class="tt_format_content js_underline_content autoTypeSetting24psection " id="js_content">

:在三维渲染技术中,符号距离函数很难理解,而本文作者仅用 46 行 Python 代码就展示了这项技术。

接:https://vgel.me/posts/donut/

声明:本文为 CSDN 翻译,未经允许禁止转载。

作者 | Theia Vogel
译者 | 弯月 责编 | 郑丽媛
出品 | CSDN(ID:CSDNnews)

符号距离函数(Signed Distance Function,简称 SDF)是一种很酷的三维渲染技术——但不幸的是,这种技术很难理解。

该技术通常都通过 GLSL 编写的 shader 示例来展示,但大多数程序员并不熟悉 GLSL。从本质上来说,SDF 的编写思路非常简单。在本文中,我将编写一个程序来展示这项技术:一段只有 46 行 Python 代码的程序,使用 RayMarching 算法做出了一个甜甜圈动图。

当然,你也可以用其他自己喜欢的语言来编写这段代码,即便没有图形 API 的帮助,我们仅凭 ASCII 码也可以实现这样的动图。所以,一起来试试看吧!最终,我们将获得一个用 ASCII 码制作的、不停旋转的、美味的甜甜圈。只要掌握了这种渲染技术,你就可以制作各种动图。


准备工作


首先,我们用 Python 来渲染每一帧 ASCII。此外,我们还将添加一个循环来实现动画效果:

  • import timedef sample(x: int, y: int) -> str:# draw an alternating checkboard patternif (x + y + int(time.time())) % 2:return '#'else:return ' 'while True:# loop over each position and sample a characterframe_chars=[]for y in range(20):for x in range(80):frame_chars.append(sample(x, y))frame_chars.append('\n')# print out a control sequence to clear the terminal, then the frame# (I haven't tested this on windows, but I believe it should work there,# please get in touch if it doesn't)print('3[2J' + ''.join(frame_chars))# cap at 30fpstime.sleep(1/30)

    以上代码为我们呈现了一个 80x20 的棋盘格,每秒交替一次:

    这只是基础,下面我们来增加一些视觉效果——下一个任务很简单:决定屏幕上的每个字符显示什么。


    画圆圈


    首先,从最简单的工作着手。我们根据 x 坐标和 y 坐标绘制一个圆,虽然这还不是 3D 动画。我们可以通过几种不同的方式来画圆圈,此处我们采用的方法是:针对屏幕上的每个字符,决定应该显示什么,也就是说我们需要逐个字符处理。对于每个字符的坐标 (x, y),我们的基本算法如下:

    1. 计算 (x, y) 到屏幕中心的距离:√((x-0)^2 + (y-0)^2),即 √(x^2+y^2)。

    2. 减去圆半径。如果该点在圆的内部或边缘,则得到的值 ≤ 0,否则值 > 0。

    3. 验证得到的值与 0 的关系,如果该点在圆的内部或边缘,则返回 #,否则返回空格。

    此外,我们还需要将 x 和 y 分别映射到 -1..1 和 (-.5)..(.5) 上,这样结果就不会受分辨率的影响了,还可以保证正确的纵横比(2 * 20/ 80=0.5,因为 y 仅包含 20 个字符,而 x 包含 80 个字符,终端字符的高度大约是宽度的两倍)——这样可以防止我们画的圆圈看起来像一个压扁的豆子。

    • import math, timedef circle(x: float, y: float) -> float:# since the range of x is -1..1, the circle's radius will be 40%,# meaning the circle's diameter is 40% of the screenradius=0.4# calculate the distance from the center of the screen and subtract the# radius, so d will be < 0 inside the circle, 0 on the edge, and > 0 outsidereturn math.sqrt(x**2 + y**2) - radiusdef sample(x: float, y: float) -> str:# return a '#' if we're inside the circle, and ' ' otherwiseif circle(x, y) <=0:return '#'else:return ' 'while True:frame_chars=[]for y in range(20):for x in range(80):# remap to -1..1 range (for x)...remapped_x=x / 80 * 2 - 1# ...and corrected for aspect ratio range (for y)remapped_y=(y / 20 * 2 - 1) * (2 * 20/80)frame_chars.append(sample(remapped_x, remapped_y))frame_chars.append('\n')print('3[2J' + ''.join(frame_chars))time.sleep(1/30)

      我们成功地画出了一个圆!这个例子中并没有使用 time.time(),所以这个圆不会动,不过我们稍后会添加动画。


      二维的甜甜圈


      一个圆只是二维甜甜圈的一半,而另一半是中间的那个洞洞,也就是另一个圆。下面,我们在这个圆的中心加上一个洞,让它变成甜甜圈。实现方法有好几种,不过最好的方式是用一个半径和半径之外的厚度来定义:

      • import math, timedef donut_2d(x: float, y: float) -> float:# same radius as before, though the donut will appear larger as# half the thickness is outside this radiusradius=0.4# how thick the donut will bethickness=0.3# take the abs of the circle calculation from before, subtracting# `thickness / 2`. `abs(...)` will be 0 on the edge of the circle, and# increase as you move away. therefore, `abs(...) - thickness / 2` will# be ≤ 0 only `thickness / 2` units away from the circle's edge on either# side, giving a donut with a total width of `thickness`return abs(math.sqrt(x**2 + y**2) - radius) - thickness / 2def sample(x: float, y: float) -> str:if donut_2d(x, y) <=0:return '#'else:return ' 'while True:frame_chars=[]for y in range(20):for x in range(80):remapped_x=x / 80 * 2 - 1remapped_y=(y / 20 * 2 - 1) * (2 * 20/80)frame_chars.append(sample(remapped_x, remapped_y))frame_chars.append('\n')print('3[2J' + ''.join(frame_chars))time.sleep(1/30)

        这种表示方法(半径 + 厚度)能呈现很好的艺术效果,因为半径和厚度是相对独立的参数,二者可以单独变更,几乎不需要互相参照。

        这样代码也很好写,我们只需要稍微调整计算距离的方式:之前我们计算的是到圆心的距离,现在我们计算到圆边线的距离。边距 - 厚度/2,如果结果 ≤ 0,说明点到圆边线的距离 ≤ 厚度/2,这样就得到一个给定厚度的圆环,其圆心位于给定半径的圆的边线上。

        另外一点好处是,代码的改动非常小:只需更新符号距离函数,而渲染循环的其余部分都不需要修改。也就是说,无论我们使用哪个 SDF,渲染循环都可以保持不变,我们只需要针对每个像素采样其距离即可。


        三维模型


        下面,我们来画三维模型。首先,我们来做一个简单的练习:渲染一个球体,它的 SDF 与圆几乎相同,只不过我们需要再加一个坐标轴 Z。

        • def sphere(x: float, y: float, z: float) -> float:radius=0.4return math.sqrt(x**2 + y**2 + z**2) - radius

          此处,X 是水平轴,Y 是纵向轴,而 Z 表示深度。

          我们还需要重用同一个 frame_chars 循环。唯一需要修改的函数是 sample,我们需要处理第三个维度。从根本上来说, sample 函数需要接受一个二维点(x, y),并以这个二维点为基础,在三维空间中采样。换句话说,我们需要“计算”出正确的 Z,以确保渲染正确的字符。我们可以偷懒采用 z=0,这样渲染出来的就是三维世界中的一个二维切片,即对象在平面 z=0 上的截面。

          但为了渲染出更立体的三维视图,我们需要模拟现实世界。想象一只眼睛,它(近似)是一个二维平面,如何看到远处的物体呢?

          太阳光有可能直接照射到眼睛所在的平面上,也有可能经过一个或多个物体反射后到达眼睛。我们可以按照同样的方式来渲染三维视图:每次调用 sample(x, y) 时,从一个模拟的光源射出无数光线,其中至少有一条光线经过物体反射后,穿过点 (x, y , camera_z)。但这种方法的速度会有点慢,某条光线照射到特定点的几率微乎其微,因此大部分工作都是无用功。要是这样写代码的话,用最强大的虚拟机也运行不完,所以我们来走一条捷径。

          对于函数 sample(x, y),我们只关心穿过 (x, y, camera_z) 的光线,所以根本没必要在意其他光线。我们可以反向模拟光线:从 (x, y, camera_z) 出发,每走一步首先计算 SDF,获取光线从当前点到场景中任意一点(方向任意)的距离。

          如果距离小于某个阈值,则意味着光线击中了场景中的点。反之,我们可以安全地将光线向前“移动”相应的距离,因为我们知道场景中有的点至少位于这段距离之外(实际情况可能更复杂,设想一下光线可能与场景足够接近,但永远不会进入场景:当光线接近场景时SDF 的计算结果会非常小,所以光线只能更加缓慢地前进,但最终在足够多的次数之后,光线会越过场景中的点,然后加快前进速度)。我们将前进的最大步数限制为 30,如果届时光线没有击中任何场景中的点,则返回背景字符。综上所述,下面就是这个三维函数的定义:

          • def sample(x: float, y: float) -> str:# start `z` far back from the scene, which is centered at 0, 0, 0,# so nothing clipsz=-10# we'll step at most 30 steps before assuming we missed the scenefor _step in range(30):# get the distance, just like in 2Dd=sphere(x, y, z)# test against 0.01, not 0: we're a little more forgiving with the distance# in 3D for faster convergenceif d <=0.01:# we hit the sphere!return '#'else:# didn't hit anything yet, move the ray forward# we can safely move forward by `d` without hitting anything since we know# that's the distance to the scenez +=d# we didn't hit anything after 30 steps, return the backgroundreturn ' '

            渲染球体的所有代码如下:

            • import math, timedef sphere(x: float, y: float, z: float) -> float:radius=0.4return math.sqrt(x**2 + y**2 + z**2) - radiusdef sample(x: float, y: float) -> str:radius=0.4z=-10for _step in range(30):d=sphere(x, y, z)if d <=0.01:return '#'else:z +=dreturn ' '# this is unchangedwhile True:frame_chars=[]for y in range(20):for x in range(80):remapped_x=x / 80 * 2 - 1remapped_y=(y / 20 * 2 - 1) * (2 * 20/80)frame_chars.append(sample(remapped_x, remapped_y))frame_chars.append('\n')print('3[2J' + ''.join(frame_chars))time.sleep(1/30)

              好了,我们绘制出了一个三维的球体。下面,我们来画三维的甜甜圈。


              三维的甜甜圈


              为了绘制三维的甜甜圈,接下来我们需要将绘制球体的 SDF 换成绘制更复杂的甜甜圈,其余代码保持不变:

              • import math, timedef donut(x: float, y: float, z: float) -> float:radius=0.4thickness=0.3# first, we get the distance from the center and subtract the radius,# just like the 2d donut.# this value is the distance from the edge of the xy circle along a line# drawn between [x, y, 0] and [0, 0, 0] (the center of the donut).xy_d=math.sqrt(x**2 + y**2) - radius# now we need to consider z, which, since we're evaluating the donut at# [0, 0, 0], is the distance orthogonal (on the z axis) to that# [x, y, 0]..[0, 0, 0] line.# we can use these two values in the usual euclidean distance function to get# the 3D version of our 2D donut "distance from edge" value.d=math.sqrt(xy_d**2 + z**2)# then, we subtract `thickness / 2` as before to get the signed distance,# just like in 2D.return d - thickness / 2# unchanged from before, except for s/sphere/donut/g:def sample(x: float, y: float) -> str:z=-10for _step in range(30):d=donut(x, y, z)if d <=0.01:return '#'else:z +=dreturn ' 'while True:frame_chars=[]for y in range(20):for x in range(80):remapped_x=x / 80 * 2 - 1remapped_y=(y / 20 * 2 - 1) * (2 * 20/80)frame_chars.append(sample(remapped_x, remapped_y))frame_chars.append('\n')print('3[2J' + ''.join(frame_chars))time.sleep(1/30)

                这个甜甜圈还不够完美,下面我们来添加一些动画,证明它是三维的。


                旋转的三维甜甜圈


                为了让甜甜圈旋转起来,我们需要在计算 SDF 之前对 sample 计算的点进行变换:

                • def sample(x: float, y: float) -> str:...for _step in range(30):# calculate the angle based on time, to animate the donut spinningθ=time.time() * 2# rotate the input coordinates, which is equivalent to rotating the sdft_x=x * math.cos(θ) - z * math.sin(θ)t_z=x * math.sin(θ) + z * math.cos(θ)d=donut(t_x, y, t_z)...

                  在这段代码中,y 值保持不变,所以甜甜圈是围绕 y 轴旋转的。我们在每次采样时计算 θ 值,然后计算旋转矩阵:

                  • import math, timedef donut(x: float, y: float, z: float) -> float:radius=0.4thickness=0.3return math.sqrt((math.sqrt(x**2 + y**2) - radius)**2 + z**2) - thickness / 2def sample(x: float, y: float) -> str:z=-10for _step in range(30):θ=time.time() * 2t_x=x * math.cos(θ) - z * math.sin(θ)t_z=x * math.sin(θ) + z * math.cos(θ)d=donut(t_x, y, t_z)if d <=0.01:return '#'else:z +=dreturn ' 'while True:frame_chars=[]for y in range(20):for x in range(80):remapped_x=x / 80 * 2 - 1remapped_y=(y / 20 * 2 - 1) * (2 * 20/80)frame_chars.append(sample(remapped_x, remapped_y))frame_chars.append('\n')print('3[2J' + ''.join(frame_chars))time.sleep(1/30)

                    这样,三维的甜甜圈就画好了。下面,我们使用法向量估算器,添加一些简单的光照和纹理。


                    添加光照和糖霜


                    为了增加光照和糖霜纹理,我们需要计算法向量。法向量的定义是从对象表面上每个点垂直地发散出来的向量,就像仙人掌上的刺,或者某人在接触到静电气球后头发爆炸的样子。

                    大多数表面都有计算法向量的公式,但是当一个场景中融合了多个 SDF 时,计算就会非常困难。另外,谁愿意针对每个 SDF 编写一个法向量函数?所以,我们还是需要一点小技巧:在目标点周围的每个轴上对 SDF 进行采样,并以此来估算法向量:

                    • Sdf=typing.Callable[[float, float, float], float]def normal(sdf: Sdf, x: float, y: float, z: float) -> tuple[float, float, float]:# an arbitrary small amount to offset around the pointε=0.001# calculate each axis independentlyn_x=sdf(x + ε, y, z) - sdf(x - ε, y, z)n_y=sdf(x, y + ε, z) - sdf(x, y - ε, z)n_z=sdf(x, y, z + ε) - sdf(x, y, z - ε)# normalize the result to length=1norm=math.sqrt(n_x**2 + n_y**2 + n_z**2)return (n_x / norm, n_y / norm, n_z / norm)

                      为了理解该函数的原理,我们可以假设一种特殊情况:法向量的一个分量为 0,比如 x=0。这意味着,这个点上的 SDF 在 x 轴上是平的,也就是说 sdf(x + ε, y, z)==sdf(x - ε, y, z)。

                      随着这些值的发散,法向量的 x 分量会向正方向或负方向移动。这只是一个估算值,但对于渲染来说已经足够了,甚至一些高级演示也会使用这种方法。但这种方法的缺点是速度非常慢,因为每次调用都需要对 SDF 进行六次采样。随着场景 SDF 变得越来越复杂,性能就会出现问题。

                      不过,对我们来说这就足够了。如果光线命中,我们就在 sample 中计算法向量,并使用它来计算一些光照和纹理:

                      • if d <=0.01:_, nt_y, nt_z=normal(donut, t_x, y, t_z)is_lit=nt_y < -0.15is_frosted=nt_z < -0.5if is_frosted:return '@' if is_lit else '#'else:return '=' if is_lit else '.'

                        我们只关心法向量的 y 和 z 分量,并不在意 x 分量。我们使用 y 来计算光照,假设表面朝上(法向量的 y 接近 -1),则应该被照亮。我们使用 z 来计算糖霜材质,针对不同的值设定阈值,就可以调整甜甜圈的糖霜厚度。为了理解这些值的含义,你可以试试看修改如下代码:

                        • import math, time, typingdef donut(x: float, y: float, z: float) -> float:radius=0.4thickness=0.3return math.sqrt((math.sqrt(x**2 + y**2) - radius)**2 + z**2) - thickness / 2Sdf=typing.Callable[[float, float, float], float]def normal(sdf: Sdf, x: float, y: float, z: float) -> tuple[float, float, float]:ε=0.001n_x=sdf(x + ε, y, z) - sdf(x - ε, y, z)n_y=sdf(x, y + ε, z) - sdf(x, y - ε, z)n_z=sdf(x, y, z + ε) - sdf(x, y, z - ε)norm=math.sqrt(n_x**2 + n_y**2 + n_z**2)return (n_x / norm, n_y / norm, n_z / norm)def sample(x: float, y: float) -> str:z=-10for _step in range(30):θ=time.time() * 2t_x=x * math.cos(θ) - z * math.sin(θ)t_z=x * math.sin(θ) + z * math.cos(θ)d=donut(t_x, y, t_z)if d <=0.01:_, nt_y, nt_z=normal(donut, t_x, y, t_z)is_lit=nt_y < -0.15is_frosted=nt_z < -0.5if is_frosted:return '@' if is_lit else '#'else:return '=' if is_lit else '.'else:z +=dreturn ' 'while True:frame_chars=[]for y in range(20):for x in range(80):remapped_x=x / 80 * 2 - 1remapped_y=(y / 20 * 2 - 1) * (2 * 20/80)frame_chars.append(sample(remapped_x, remapped_y))frame_chars.append('\n')print('3[2J' + ''.join(frame_chars))time.sleep(1/30)

                          到这里,我们的三维甜甜圈就画好了,不仅做了光照和纹理处理,还可以不停地旋转,而且只用到了 46 行代码

类首张黑洞照片就仿佛一个“模糊的橙色甜甜圈”,但在机器学习的帮助下,这一来自M87星系中心的黑洞正式“改头换面”。新图像进一步展示了一个更大、更暗的中心区域,周围环绕着明亮的吸积气体。

美国研究团队使用了在2017年联网观测的“事件视界望远镜”(EHT)合作组织获得的数据,首次实现了阵列的全分辨率。相关论文已发表在《天体物理学杂志快报》上。

最初由EHT合作组织于2019年拍摄的M87超大质量黑洞(左)和PRIMO算法生成的新图像(右)(图片来源:EHT小组/普林斯顿高级研究所)

2017年,集结了地球上8台无线电波望远镜的EHT成功拍摄到了M87超大质量黑洞的影子。然而,数据中出现了缺口,就像拼图游戏中缺失的碎片。普林斯顿大学EHT小组成员表示,利用新机器学习技术PRIMO,他们能够实现当前阵列的最大分辨率。PRIMO能够根据大量训练材料生成规则。例如,如果向计算机提供一系列不同的香蕉图像,经过充分的训练,它能确定一张未知的图像是不是香蕉。

研究小组表示,利用PRIMO,计算机分析了3万多张黑洞吸积气体的高保真模拟图像。这些图集涵盖了黑洞如何吸积物质的广泛模型,以便寻找图像结构中的共同模式。各种结构模式根据它们在模拟中出现的频率进行分类,进而混合,以高度准确地显示黑洞图像,同时,还提供对图像缺失结构的高保真估计。

该团队表示,新绘制的图像与EHT数据和理论预期是一致的。生成图像需要假设缺失信息的适当形式,而PRIMO也做到了这一点。

转自 |科技日报

来源: 羊城晚报

如果您对此项目感兴趣,请在此留言,坐等企业找您(成功的创业者90%都是通过留言,留言只需5秒钟)
  • 知名招商项目汇聚平台

    汇聚海量知名、高诚信度品牌招商项目,随时为您提供招商信息

  • 事实和口碑胜于一切

    千万创业者通过这里找项目、迈出成功创业第一步;

  • 诚信的商机发布平台

    请你在加盟留言时,选择有实力、 加盟店多、成功案例多、合法资质、 证照齐全、诚信经营的品牌.

郑重承诺:本公司郑重承诺尊重你的隐私,并承诺为你保密!
随时 上班时间 下班时间
您可以根据下列意向选择快捷留言
  1. 加盟费多少
  2. 我们这里有加盟店吗?
  3. 我想了解一些加盟资料
  4. 我对这个项目感兴趣,尽快联系我


创业专题



热门创业项目

精品推荐

餐饮项目分类

联系我们

微信扫一扫
第一时间推送投资小回报快利润高的项目

合作伙伴

我们也在这里

关注微信关注微信

您身边的财富顾问...

扫一下
客户端客户端

iPhone/Android/iPad

去下载
关注微博关注微博

官方微博随时分享...

加关注
手机看hbdrt.cn手机看hbdrt.cn

随时随地找商机...

去看看

温馨提示

  • 1在找餐饮项目的过程中多对比同类项目。
  • 2了解项目时多打电话,进行实地考察。
  • 3投资有风险,请谨慎加盟。
  • 4本网站对投资者的风险概不承担。