如何在rgl中增加spheres3d的平滑度
Posted
技术标签:
【中文标题】如何在rgl中增加spheres3d的平滑度【英文标题】:How to increase smoothness of spheres3d in rgl 【发布时间】:2016-09-29 19:02:11 【问题描述】:当我使用rgl::spheres3d()
时,渲染的球体具有笨拙的多面边缘。
spheres = data.frame(x = c(1,2,3), y = c(1,3,1),
color = c("#992222" , "#222299", "#229922"))
open3d()
spheres3d(spheres$x, spheres$y, radius = 1, color = spheres$color)
设置material3d(smooth = TRUE, line_antialias = TRUE)
并不能改善这一点。增加半径也无济于事。有什么方法可以增加它们绘制的平滑度?
【问题讨论】:
【参考方案1】:一个更简单的方法是使用subdivision3d()
。在这里,depth=4
并不是那么流畅,但你可以增加它。
library(rgl)
sphere <- subdivision3d(cube3d(),depth=4)
sphere$vb[4,] <- apply(sphere$vb[1:3,], 2, function(x) sqrt(sum(x^2)))
open3d()
shade3d(sphere, col="red")
【讨论】:
添加法线使它看起来更好。您可以通过为球体添加sphere$normals <- sphere$vb
或更一般地添加shape <- addNormals(shape)
来做到这一点。
我喜欢这种方法的简单性。它可以通过使用dodecahedron3d()
而不是cube3d()
来改进。不幸的是,它不适用于高分辨率球体。如果我将深度增加到 6 以上,我往往会出现内存分配错误。也就是说,深度 6 的 dodecahedron3d 看起来还算过得去,虽然处理起来有点慢
在我上一条评论的基础上,添加 @user2554330 的建议,即在深度 6 处将法线添加到 icosahedron3d()
中,使一些看起来非常漂亮的球体【参考方案2】:
这是我使用persp3d.function()
的方法
sphere.f <- function(x0 = 0, y0 = 0, z0 = 0, r = 1, n = 101, ...)
f <- function(s, t) cbind(r * cos(s) * cos(t) + x0,
r * sin(s) * cos(t) + y0,
r * sin(t) + z0)
persp3d(f, slim = c(0, pi), tlim = c(0, 2*pi), n = n, add = T, ...)
sphere.f(col = rainbow)
【讨论】:
这使得一些看起来非常光滑的球体看起来比我的基于 qmesh3d 的函数快得多。阻止我接受这个答案的一件事是球体在两极周围有缺陷,表面有间隙,并且在赤道有一个轻微的脊,两个半球并不完全匹配。您对如何解决这些问题有任何想法吗? @dww;不幸的是,这似乎是不可能的。有一些方法可以用两个变量来表示球体。但是以任何方式都会出现低密度或高密度区域和/或重复点。【参考方案3】:虽然rgl::spheres3d()
不能这样做,但另一种方法是编写自己的函数来绘制球体。这是一个将球体渲染为纬度和经度相等的四边形网格的函数。
drawSphere = function(xc=0, yc=0, zc=0, r=1, lats=50L, longs=50L, ...)
#xc,yc,zc give centre of sphere, r is radius, lats/longs for resolution
vertices = vector(mode = "numeric", length = 12L * lats * longs)
vi = 1L
for(i in 1:lats)
lat0 = pi * (-0.5 + (i - 1) / lats)
z0 = sin(lat0)*r
zr0 = cos(lat0)*r
lat1 = pi * (-0.5 + i / lats)
z1 = sin(lat1)*r
zr1 = cos(lat1)*r
for(j in 1:longs)
lng1 = 2 * pi * (j - 1) / longs
lng2 = 2 * pi * (j) / longs
x1 = cos(lng1)
y1 = sin(lng1)
x2 = cos(lng2)
y2 = sin(lng2)
vertices[vi] = x1 * zr0 + xc; vi = vi + 1L
vertices[vi] = y1 * zr0 + yc; vi = vi + 1L
vertices[vi] = z0 + zc; vi = vi + 1L
vertices[vi] = x1 * zr1 + xc; vi = vi + 1L
vertices[vi] = y1 * zr1 + yc; vi = vi + 1L
vertices[vi] = z1 + zc; vi = vi + 1L
vertices[vi] = x2 * zr1 + xc; vi = vi + 1L
vertices[vi] = y2 * zr1 + yc; vi = vi + 1L
vertices[vi] = z1 + zc; vi = vi + 1L
vertices[vi] = x2 * zr0 + xc; vi = vi + 1L
vertices[vi] = y2 * zr0 + yc; vi = vi + 1L
vertices[vi] = z0 + zc; vi = vi + 1L
indices = 1:(length(vertices)/3)
shade3d(qmesh3d(vertices, indices, homogeneous=F), ...)
应该可以对此进行改进,例如使用icospheres(即将球体绘制为拉伸的二十面体)。但是,如果您将 lats 和 long 设置得足够高,这个版本已经可以绘制出相当不错的球体了。
函数实例:
spheres = data.frame(x = c(1,2,3), y = c(1,3,1), z=c(0,0,0), color = c("#992222" , "#222299", "#229922"))
open3d()
material3d(ambient = "black", specular = "grey60", emission = "black", shininess = 30.0)
rgl.clear(type = "lights")
rgl.light(theta = -30, phi = 60, viewpoint.rel = TRUE, ambient = "#FFFFFF", diffuse = "#FFFFFF", specular = "#FFFFFF", x = NULL, y = NULL, z = NULL)
rgl.light(theta = -0, phi = 0, viewpoint.rel = TRUE, diffuse = "gray20", specular = "gray25", ambient = "gray80", x = NULL, y = NULL, z = NULL)
sapply(1:NROW(spheres), function(i)
drawSphere(spheres$x[i], spheres$y[i], spheres$z[i], r=1, lats = 400, longs = 400, color=spheres$color[i]))
【讨论】:
是的,绘制你自己的球体是要走的路。如果您只想要一种颜色,一个可能的改进是您可以只绘制一个球体,并将其重新用作 3d 精灵。这样可以节省内存,这在 R 中可能无关紧要,但如果您使用rglwidget()
导出场景,文件大小会显着不同。【参考方案4】:
扩展cuttlefish44's excellent answer,我发现了一个效果更好的参数化 - 即它在两极没有缺陷(图像中浅蓝色球体上的黑色伪影)。
library(rgl)
sphere.f <- function(x0 = 0, y0 = 0, z0 = 0, r = 1, n = 101, ...)
f <- function(s, t) cbind(r * cos(s) * cos(t) + x0,
r * sin(s) * cos(t) + y0,
r * sin(t) + z0)
persp3d(f, slim = c(0, pi), tlim = c(0, 2*pi), n = n, add = T, ...)
sphere1.f <- function(x0 = 0, y0 = 0, z0 = 0, r = 1, n = 101, ...)
f <- function(s,t)
cbind( r * cos(t)*cos(s) + x0,
r * sin(s) + y0,
r * sin(t)*cos(s) + z0)
persp3d(f, slim = c(-pi/2,pi/2), tlim = c(0, 2*pi), n = n, add = T, ...)
sphere.f( -1.5,0, col = "lightblue")
sphere1.f( 1.5,0, col = "pink")
图片:
【讨论】:
这太棒了——干得好。在 n=101 时,赤道周围的山脊缺陷仍然存在,但在速度和质量之间取得了很好的折衷。在 n=201 的情况下,已经很难看到了。在 n=301 时,几乎看不出来,但速度仍然可以接受。 感谢您给我正确的答案投票。很想在 rgl 中获得青铜徽章,但按照这个速度,大约需要 10 年。【参考方案5】:这并不容易;我认为如果你想这样做,你将不得不这样做
下载rgl source from CRAN 解压并修改src/sphereSet.cpp
的第24行,目前为
sphereMesh.setGlobe(16,16);
用一些更大的值调用函数(这个函数在src/SphereMesh.cpp
的第25行定义;参数是in_segments
和in_sections
...)
sudo apt-get build-dep r-cran-rgl
来获取它们,我认为...)
这个我没试过。祝你好运...或者,您可以要求包维护者通过materials3d
或以其他方式将其设为可设置参数...
【讨论】:
我会尝试下载源代码,编辑上面提到的行和DESCRIPTION
文件(将自己设置为维护者),然后通过 ftp 上传到win-builder.r-project.org
【参考方案6】:
另一种可能性是使用Rvcg
包的vcgSphere
函数。
library(Rvcg)
sphr <- vcgSphere(subdivision = 4) # unit sphere centered at (0,0,0)
library(rgl)
shade3d(sphr, color="red")
# sphere with given radius and center
radius <- 0.5
center <- c(2,1,1)
sphr2 <- translate3d(
scale3d(sphr, radius, radius, radius),
center[1], center[2], center[3])
shade3d(sphr2, color="green")
【讨论】:
以上是关于如何在rgl中增加spheres3d的平滑度的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 RGL 中的 tmesh3d 在 RGL 中使用 shade3d 或 wire3d 制作 3D 网格