JSoC 2015 项目:使用 Compose3D 在浏览器中实现交互式 3D 图形

2020 年 10 月 20 日 | Rohit Varkey Thankachan

在过去的三个月中,我一直在开发 Compose3D,它是令人惊叹的 Compose 包的 3D 扩展。我在 Compose3D 上的工作始于我与 Pranav T Bhat 一起上的计算机图形学课程项目,到课程结束时,我们已经完成了一个 Compose3D 的工作原型,它支持上下文和几何体,以及一个非常基础的 WebGL 后端。

我很高兴能以第一届 Julia Summer of Code 的身份,在 Shashi GowdaSimon Danisch 的指导下继续这项工作,该项目得到了 Gordon and Betty Moore Foundation 的慷慨赞助。虽然我已经能够为 Compose3D 添加很多功能,但它还没有完全准备好发布。希望不久后它就能发布。但作为一项令人愉快的副作用,我已经能够将原始原型(以及更多!)提供的 WebGL 渲染功能抽象到一个名为 ThreeJS.jl 的单独包中,现在可以使用它在浏览器中使用 Julia 渲染 3D 图形,这为在 IJulia 笔记本和 Escher 中显示此类场景打开了可能性。

ThreeJS.jl

ThreeJS 现在负责 Compose3D 执行的所有 WebGL 渲染。它也可以用作其他图形包的独立包,以用作后端。

最初,我在 Compose3D 中渲染场景的方法是,只是将相应的 JavaScript 代码输出到 IJulia 笔记本中,然后执行它!这在 IJulia 笔记本中运行得很好,但很快显而易见,这种方法有几个缺点。

因此,Shashi 建议围绕优秀的 three.js 库实现一个 Polymer 包装器,以创建 threejs 网页组件。Polymer 团队在创建 threejs 组件方面做了一些工作,并准备好了一个基本实现,我立即将其 fork 并进行了一些调整,以添加我需要的功能。可以肯定地说,我在 JSoC 期间编写 JavaScript 的时间比编写 Julia 的时间更多!

切换到使用网页组件突然打开了 2 条主要途径。Compose3D 现在可以与 Escher 协同工作,并且还提供了交互性。ThreeJS 输出 Patchwork 元素,这使它能够使用 Patchwork 的巧妙的 diffing 功能,从而仅更新所需的 DOM 元素,并提高性能。

另一方面,网页组件在 IJulia 笔记本中引入了关于提供 ThreeJS 所需文件的问题。我仍在努力找到解决这个问题的有效方法,但目前,一个技巧可以让 ThreeJS 在 IJulia 中运行,尽管有一些限制。

绘制内容!

无论如何,现在我们已经准备好绘制浏览器中的 3D 场景!例如,下面的代码片段将绘制一个从角落照亮的红色立方体。ThreeJS 绘制的场景中的相机可以使用鼠标或触控板旋转、缩放和平移,让你可以探索场景。

import ThreeJS
ThreeJS.outerdiv() << (ThreeJS.initscene() <<
    [
        ThreeJS.mesh(0.0, 0.0, 0.0) <<
        [
            ThreeJS.box(1.0,1.0,1.0),
            ThreeJS.material(Dict(:kind=>"lambert",:color=>"red"))
        ],
        ThreeJS.pointlight(3.0, 3.0, 3.0),
        ThreeJS.camera(0.0, 0.0, 10.0)
    ])

使它们具有交互性

目前,IJulia 中的交互性已损坏(切换到 Polymer 1.0 和新的隐秘 DOM 的副作用),因此,如果你想与 3D 场景进行交互,Escher 是你的首选。所以,一个例子可以是与之前相同的场景,但添加一个滑块,并使立方体的大小由滑块控制。

import ThreeJS
function main(window)
  push!(window.assets, "widgets")
  push!(window.assets, ("ThreeJS", "threejs"))
  side = Input(1.0)
  vbox(
    slider(1.0:5.0) >>> side,
    lift(side) do val
      ThreeJS.outerdiv() << (ThreeJS.initscene() <<
      [
          ThreeJS.mesh(0.0, 0.0, 0.0) <<
          [
              ThreeJS.box(val, val, val),
              ThreeJS.material(Dict(:kind=>"lambert",:color=>"red"))
          ],
          ThreeJS.pointlight(3.0, 3.0, 3.0),
          ThreeJS.camera(0.0, 0.0, 10.0)
      ])
    end
  )
end

你也可以进行动画!

可以使用 Escher 创建小规模动画。我们不使用滑块来更新元素,而是使用 every 函数或 fpswhen 函数在特定间隔内更新它。使用对上述代码的几处修改,就可以绘制一个旋转立方体的场景。

import ThreeJS
function main(window)
  push!(window.assets, "widgets")
  push!(window.assets, ("ThreeJS", "threejs"))
  rx = 0.0
  ry = 0.0
  rz = 0.0
  delta = fpswhen(window.alive, 60) #Update at 60 FPS
  lift(delta) do _
      rx += 0.5
      ry += 0.5
      rz += 0.5
      ThreeJS.outerdiv() << (ThreeJS.initscene() <<
      [
          ThreeJS.mesh(0.0, 0.0, 0.0) <<
          [
              ThreeJS.box(2.0, 2.0, 2.0, rx = rx, ry = ry, rz = rz),
              ThreeJS.material(Dict(:kind=>"lambert",:color=>"red"))
          ],
          ThreeJS.pointlight(3.0, 3.0, 3.0),
          ThreeJS.camera(0.0, 0.0, 10.0)
      ])
    end
end

Rotating Cube

曲面图和网格图!(某种程度上)

ThreeJS 支持渲染参数曲面,这基本上是典型 surf 图绘制的曲面类型。它还支持绘制像典型 mesh 图一样的线。可以通过传递要使用的颜色数组,将颜色映射应用于这些曲面。ThreeJS 会计算并选择要应用的颜色。这些颜色在与使用 vertexcolorkind 属性的材质组合在一起时才会生效。下面显示了 ThreeJS 绘制的此类曲面的屏幕截图。

Parametric surface Mesh lines

Compose3D

Compose3D 对渲染库提供了一种抽象,让你可以像 Compose 库(它的灵感来源)一样,将基本图形组合在一起以构建场景。这使你能够用更少的代码创建非常有趣的结构!Compose3D 具有与 Compose 相似的功能,用户可以创建 3D 上下文,然后在其中使用相对和绝对度量,并将其他基本图形组合在一起。

我最喜欢的展示 Compose3D 的例子是 Sierpinski 金字塔示例。在这里,我们将父上下文拆分为我们想要的各个部分,然后在其中绘制金字塔!因此,3D 空间的下半部分被分成 4 部分,然后在上面排列一个金字塔。

using Compose3D

function sierpinski(n)
    if n == 0
        compose(Context(0w,0h,0d,1w,1h,1d),pyramid(0w,0h,0d,1w,1h)) #The basic unit
    else
        t = sierpinski(n - 1)
        compose(Context(0w,0h,0d,1w,1h,1d),
        (Context(0w,0h,0d,(1/2)w,(1/2)h,(1/2)d), t),
        (Context(0w,0h,0.5d,(1/2)w,(1/2)h,(1/2)d), t),
        (Context(0.5w,0h,0.5d,(1/2)w,(1/2)h,(1/2)d), t),
        (Context(0.5w,0h,0d,(1/2)w,(1/2)h,(1/2)d), t),
        (Context(0.25w,0.5h,0.25d,(1/2)w,(1/2)h,(1/2)d), t)) #The top one
    end
end
compose(Context(-5mm,-5mm,-5mm,10mm,10mm,10mm),sierpinski(3))

瞧!你得到了一个像下图所示的级别为 3 的 Sierpinski 金字塔。

Sierpinski

切换到 ThreeJS 使 Compose3D 能够享受到 ThreeJS 带来的所有优势。这包括交互性和动画!

例如,同一个 Sierpinski 示例可以包含一些交互式元素,例如定义递归级别的滑块,以及可能控制金字塔颜色的滑块。这可以在 Escher 中轻松完成,就像使用 ThreeJS 一样。在定义了下面的 sierpinski 函数后,只需创建一个滑块并将其连接到 sierpinski 函数即可完成设置!

function main(window)
    push!(window.assets, ("ThreeJS", "threejs")) #Push the threejs static assets
    push!(window.assets, "widgets")
    n = Input(0.0)

    vbox(
        slider(0.0:3.0) >>> n, #Set up the slider
        lift(n) do i
            #Draw the composed figure!
            draw(
                Patchable3D(100,100),
                compose(
                    Context(-5mm,-5mm,-5mm,10mm,10mm,10mm), sierpinski(i)
                )
            )
        end
    )
end

Interactive Sierpinski

作为动画的示例,我将 Ian Dunning 的 Escher 鱼群示例从 2D 移植到 3D,你可以在下面找到它的屏幕截图。

未来的方向