使用misc3d绘制平滑的隐式曲面

4

misc3d 包提供了一个非常好的 marching cubes 算法实现,可以绘制隐式曲面。

例如,我们来绘制一个 Dupin 周线:

a = 0.94; mu = 0.56; c = 0.34 # cyclide parameters
f <- function(x, y, z, a, c, mu){ # implicit equation f(x,y,z)=0
  b <- sqrt(a^2-c^2)
  (x^2+y^2+z^2-mu^2+b^2)^2 - 4*(a*x-c*mu)^2 - 4*b^2*y^2
}
# define the "voxel"
nx <- 50; ny <- 50; nz <- 25
x <- seq(-c-mu-a, abs(mu-c)+a, length=nx) 
y <- seq(-mu-a, mu+a, length=ny)
z <- seq(-mu-c, mu+c, length=nz) 
g <- expand.grid(x=x, y=y, z=z)
voxel <- array(with(g, f(x,y,z,a,c,mu)), c(nx,ny,nz))
# plot the surface
library(misc3d)
surf <- computeContour3d(voxel, level=0, x=x, y=y, z=z)
drawScene.rgl(makeTriangles(surf))

输入图像描述

很好,除了表面不光滑之外。 drawScene.rgl 的文档说:"对象特定的渲染特性,如平滑和材料,是通过设置对象来控制的。" 我不知道这是什么意思。如何获得平滑的表面?

我有一个解决方案,但不是一个直接有效的解决方案:该解决方案涉及从 computeContour3d 的输出构建一个 mesh3d 对象,并将表面法线包含在此 mesh3d 中。

f(x,y,z)= 0 定义的隐式表面的表面法线简单地由 f 的梯度给出。对于这个例子,推导梯度并不困难。

gradient <- function(xyz,a,c,mu){
  x <- xyz[1]; y <- xyz[2]; z <- xyz[3]
  b <- sqrt(a^2-c^2)
  c(
    2*(2*x)*(x^2+y^2+z^2-mu^2+b^2) - 8*a*(a*x-c*mu),
    2*(2*y)*(x^2+y^2+z^2-mu^2+b^2) - 8*b^2*y,
    2*(2*z)*(x^2+y^2+z^2-mu^2+b^2)
  )
}

然后,法线的计算如下所示:
normals <- apply(surf, 1, function(xyz){
  gradient(xyz,a,c,mu)
})

现在我们已经准备好创建mesh3d对象了:
mesh <- list(vb = rbind(t(surf),1),
             it = matrix(1:nrow(surf), nrow=3),
             primitivetype = "triangle", 
             normals = rbind(-normals,1))
class(mesh) <- c("mesh3d", "shape3d")

最后使用rgl绘制它:

library(rgl)
shade3d(mesh, color="red")

在此输入图片描述

很好,表面现在很光滑。

但是有没有更简单的方法来获得平滑的表面,而不需要构建一个mesh3d对象?文档中的这句话是什么意思:"通过设置对象中的设置,可以控制特定于对象的渲染功能,例如平滑和材料。"


1
尝试在您的makeTriangles语句中添加smooth=1000,即drawScene.rgl(makeTriangles(surf, smooth=1000)) - G5W
@G5W 不好意思,我刚刚在看到你的评论之前发布了一个带有“smooth”选项的答案...我会尝试使用“smooth=1000”。 - Stéphane Laurent
@G5W 我已经尝试过了。当我们使用 rgl 渲染时,似乎 smooth=TRUEsmooth=1000 是相同的。 - Stéphane Laurent
我尝试了另一种自动的方法:我使用了 https://gamedev.stackexchange.com/a/145034 中的建议,通过差分来近似网格点处的法线,然后对三角形顶点进行插值(这些顶点总是位于网格线上)。这似乎比基于三角形法线的 addNormals 方法要好一些,但仍然显示出大的伪影。因此,如果您想要一个漂亮的光滑表面,您将不得不像您所做的那样计算真正的法线。 - user2554330
2个回答

2

我不知道文档的建议是什么。但是,您可以通过网格对象更轻松地完成它(尽管结果不太好),使用 addNormals() 函数自动计算法线而不是通过公式计算。

以下是步骤:

像您之前做的那样计算表面。

创建没有法线的网格。这基本上就是您所做的,但使用 tmesh3d()

mesh <- tmesh3d(t(surf), matrix(1:nrow(surf), nrow=3), homogeneous = FALSE)

计算哪些顶点是重复的:

verts <- apply(mesh$vb, 2, function(column) paste(column, collapse = " "))
firstcopy <- match(verts, verts)

重写索引以使用第一个副本。这是必要的,因为misc3d函数提供了一组不相连的三角形;我们需要确定哪些是连接的。

it <- as.numeric(mesh$it)
it <- firstcopy[it]
dim(it) <- dim(mesh$it)
mesh$it <- it

现在,网格中有很多未使用的顶点;如果内存是个问题,您可能需要添加一步来删除它们。我将跳过这一步。

添加法线。

mesh <- addNormals(mesh)

这是之前和之后的截图。左边是没有法线,右边是有法线。 屏幕截图 虽然它不像使用计算出的法线那样平滑,但有时很难找到这些法线。

1

makeTriangles函数中有一个选项smooth:

drawScene.rgl(makeTriangles(surf, smooth=TRUE))

我认为这个结果和@user2554330的解决方案是等效的,但这更加直接。

enter image description here


编辑

使用rmarchingcubes包的结果更好:

library(rmarchingcubes)
contour_shape <- contour3d(
  griddata = voxel, level = 0,
  x = x, y = y, z = z
)
library(rgl)
tmesh <- tmesh3d(
  vertices = t(contour_shape[["vertices"]]),
  indices = t(contour_shape[["triangles"]]),
  normals = contour_shape[["normals"]],
  homogeneous = FALSE
)
open3d(windowRect = c(50, 50, 562, 562))
view3d(zoom=0.8)
shade3d(tmesh, color = "darkred")

enter image description here


是的,绝对更可取。 - user2554330

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接