Navidad/Christmas @ #rstats
Intro to/a rayrender
Tutorial en español
Esta Navidad las Rladies de Querétaro hicieron un concurso para realizar un árbol de Navidad con R y decidí participar. Esta vez no quería hacer el típico arbolito con ggplot2
así que decidí probar otra tecnología: rayrender
.
rayrender
es una librería para crear imágenes 3D mediante trazado de rayos (raytracing). No es la mejor opción open source para la tarea pero ¡hey está en R!
La forma en la que rayrender
funciona es consturyendo una escena y a partir de ésta agregar objetos a la escena. Podemos empezar con una escena vacía de estudio fotográfico:
library(rayrender)
<- generate_studio(depth = 0.2,
scene_test material = diffuse(checkercolor = "red"))
Para poder ver previsualizar la escena se utiliza render_scene
:
render_scene(scene_test,
samples = 100, #Muestras (+ muestras menos ruido)
preview = T, #Si quieres previsualizar la escena antes de calcular
parallel = T #Si realizar el cómputo en paralelo
)
Sobre la escena podemos agregar objetos con add_object
. En particular podemos agregar un cilindro para el tronco especificando sus coordenadas así como el material:
<- scene_test |>
scene_test add_object(
cylinder(
x = 0,
y = 0,
z = 0,
radius = 0.25,
length = 10,
material = diffuse(color = "#725c42")
) )
Hay diferentes materiales, por ejemplo metal
, cabello hair
, luz light
, cristal dielectric
, etc (la lista completa en el apartado materials) por lo que si quisiéramos hacer nuestro árbol de cristal bastaría cambiar el material:
<- scene_test |>
scene_metal add_object(
cylinder(
x = 0,
y = 0,
z = 0,
radius = 0.25,
length = 10,
material = metal(color = "#725c42")
) )
Podemos cambiar la perspectiva de la cámara ajustando manualmente y dando p
para obtener las coordenadas y luego imputarlas en el render:
render_scene(scene_metal,
samples = 100,
preview = T,
parallel = T,
lookfrom = c(50.10, 7.25, 60.13), #Dónde está la cámara
lookat = c(1, 5, 0), #Dónde está viendo la cámara
aperture = 0.5, #Apertura
fov = 17,
focal_distance = 77.66, #Distancia focal
iso = 400, #Sensibilidad "del rollo fotográfico" a la luz
clamp_value = 10
)
Sobre nuestro árbol podemos agregar conos verdes. Agregamos varios conos en un loop
para darle mayor figura. Lo pondremos sobre un nuevo fondo (blanco) y con el tronco que hicimos previamente usando diffuse
:
<- generate_studio(depth = 0.2) |>
scene add_object(
cylinder(
x = 0,
y = 0,
z = 0,
radius = 0.25,
length = 10,
material = diffuse(color = "#725c42")
)
)
#Conos
for (i in seq(1, 10, length.out = 20)){
<- scene |>
scene add_object(
cone(
start = c(0, 6 - i/2, 0),
end = c(0, 6 - (i/2 - 1), 0),
radius = i/3,
material = diffuse(color = "darkgreen")
)
) }
Finalmente agregamos esferas luminosas de dos colores distintos: amarillas y rojas sobre las superficies de los conos aleatoriamente:
set.seed(27522)
#Esferas luminosas
for (i in seq(1, 10, length.out = 20)){
#Esferas rojas
for (k in runif(2*i, 0, 2*pi)){
<- scene |>
scene add_object(
sphere(
radius = 0.1,
y = 6 - i/2, #Altura de la esfera
x = cos(k)*i/3, #Coordenadas polares para superficie de cono
z = sin(k)*i/3,
material = light(color = "red", intensity = 5)
)
)
}#Esferas amarillas
for (k in runif(2*i, 0, 2*pi)){
<- scene |>
scene add_object(
sphere(
radius = 0.1,
y = 6 - i/2, #Altura de la esfera
x = cos(k)*i/3, #Coordenadas polares para superficie de cono
z = sin(k)*i/3,
material = light(color = "#f8d568", intensity = 5)
)
)
} }
La estrella se agrega hasta arriba con un polígono extruded_polygon
que permite diseñar figuras.
#Adaptado de la estrella de https://www.rayrender.net/index.html
<- seq(0, 2*pi, length.out = 11)
angulos <- rev(c(rep(c(1,0.5), 5), 1)) * cos(angulos)
x <- rev(c(rep(c(1,0.5), 5), 1)) * sin(angulos)
z <- data.frame(x = x, z = z)
poligono <- rbind(poligono, 0.8*poligono)
estrella
#Agregamos la estrella luminosa a la escena
<- scene |>
scene add_object(
extruded_polygon(
estrella,top = -0.5,
bottom = -1,
y = 7, #Altura
z = 0.75, #Centrar
angle = c(90, 0, 90),
material = light(color = "white")
) )
Una vez que está listo nuestro árbol nos preparamos para renderizarlo con suficientes muestras para eliminar todo el ruido:
png("arbolito.png")
render_scene(scene,
width = 500, #Ancho en pixeles
height = 500, #Alto en pixeles
samples = 1000, #Suficientes muestras!
preview = F,
parallel = T,
lookfrom = c(50.10, 7.25, 60.13),
lookat = c(1, 5, 0),
aperture = 0.5,
fov = 17,
focal_distance = 77.66,
iso = 400,
clamp_value = 10
)dev.off()
Finalmente al arbolito le agregamos texto de Feliz Navidad
library(png)
library(showtext)
library(cowplot)
library(ggplot2)
#Descarga de la fuente Passions Conflict
font_add_google("Passions Conflict", "pconflict")
showtext_auto()
#Leemos la imagen
<- readPNG("arbolito.png")
arbol
<- ggdraw() +
drawplot annotation_raster(arbol, xmin = 0, ymin = 0, xmax = 1, ymax = 1) +
geom_text(aes(x = 0.5, y = 0.1, label = "Feliz Navidad"), color = "white",
family = "pconflict", size = 15) +
geom_text(aes(x = 0.5, y = 0.95,
label = "@RodZepeda | rodrigozepeda.github.io/Statisticats/posts/Navidad"),
color = "gray75",
size = 3)
ggsave("arbolito_navidad_2022_es.png", drawplot, dpi = 100, width = 500, height = 500, units = "px")
Tutorial in English
This Christmas Rladies Querétaro created a contest. One was to build a Christmas tree in R
. I decided to participate. This time I didn’t want to do the typical ggplot2
tree so I decided to test another technology: rayrender
.
rayrender
is a library to create 3D images using raytracing. This is not the best open source option for this task, but hey, it’s in R!
The way rayrender
works is by building a scene and adding objects to it. We can start with an empty study scene:
library(rayrender)
<- generate_studio(depth = 0.2,
scene_test material = diffuse(checkercolor = "red"))
To preview the scene use render_scene
:
render_scene(scene_test,
samples = 100, #More samples less noise
preview = T, #To preview the scene (before calculating)
parallel = T #Compute in parallel for extra speed
)
We can add different objects onto the scene with add_object
. In particular, we can add a cylinder for the tree trunk as well as its coordinates:
<- scene_test |>
scene_test add_object(
cylinder(
x = 0,
y = 0,
z = 0,
radius = 0.25,
length = 10,
material = diffuse(color = "#725c42")
) )
There are different materials (e.g. metal
, hair
, light
, (crystal) dielectric
, etc see materials). If we wish to make our tree metal we can just change the material:
<- scene_test |>
scene_metal add_object(
cylinder(
x = 0,
y = 0,
z = 0,
radius = 0.25,
length = 10,
material = metal(color = "#725c42")
) )
We can shift the camera’s perspective by manually adjusting and obtain the coordinates with p
. These coordinates can be inputed into the render:
render_scene(scene_metal,
samples = 100,
preview = T,
parallel = T,
lookfrom = c(50.10, 7.25, 60.13), #Where camera is
lookat = c(1, 5, 0), #What is camera watching
aperture = 0.5,
fov = 17,
focal_distance = 77.66,
iso = 400, #Sensitivity to light
clamp_value = 10
)
We can add green cones to our tree. We do that with a for
loop. We’ll put that tree over a white background and with the previous trunk we had using diffuse
:
<- generate_studio(depth = 0.2) |>
scene add_object(
cylinder(
x = 0,
y = 0,
z = 0,
radius = 0.25,
length = 10,
material = diffuse(color = "#725c42")
)
)
#Cones
for (i in seq(1, 10, length.out = 20)){
<- scene |>
scene add_object(
cone(
start = c(0, 6 - i/2, 0),
end = c(0, 6 - (i/2 - 1), 0),
radius = i/3,
material = diffuse(color = "darkgreen")
)
) }
Finally, we add light spheres of two colors: yellow and red. These go randomly over the cone’s surface.
set.seed(27522)
for (i in seq(1, 10, length.out = 20)){
#Red spheres
for (k in runif(2*i, 0, 2*pi)){
<- scene |>
scene add_object(
sphere(
radius = 0.1,
y = 6 - i/2, #Sphere height
x = cos(k)*i/3, #Polar coordinates for the cone's surface
z = sin(k)*i/3,
material = light(color = "red", intensity = 5)
)
)
}#yellow spheres
for (k in runif(2*i, 0, 2*pi)){
<- scene |>
scene add_object(
sphere(
radius = 0.1,
y = 6 - i/2,
x = cos(k)*i/3,
z = sin(k)*i/3,
material = light(color = "#f8d568", intensity = 5)
)
)
} }
We add an extruded_polygon
for the star:
#Adapted from the star at https://www.rayrender.net/index.html
<- seq(0, 2*pi, length.out = 11)
angulos <- rev(c(rep(c(1,0.5), 5), 1)) * cos(angulos)
x <- rev(c(rep(c(1,0.5), 5), 1)) * sin(angulos)
z <- data.frame(x = x, z = z)
poligono <- rbind(poligono, 0.8*poligono)
estrella
#Add the luminous star
<- scene |>
scene add_object(
extruded_polygon(
estrella,top = -0.5,
bottom = -1,
y = 7, #Altura
z = 0.75, #Centrar
angle = c(90, 0, 90),
material = light(color = "white")
) )
Once our tree is ready we render it with enough samples to eliminate the noise:
png("arbolito.png")
render_scene(scene,
width = 500, #Pixel width
height = 500, #Pixel height
samples = 1000, #Enough samples
preview = F,
parallel = T,
lookfrom = c(50.10, 7.25, 60.13),
lookat = c(1, 5, 0),
aperture = 0.5,
fov = 17,
focal_distance = 77.66,
iso = 400,
clamp_value = 10
)dev.off()
Finally we add Merry Christmas
text to the tree
library(png)
library(showtext)
library(cowplot)
library(ggplot2)
#Download Passions Conflict from google fonts
font_add_google("Passions Conflict", "pconflict")
showtext_auto()
#Leemos la imagen
<- readPNG("arbolito.png")
arbol
<- ggdraw() +
drawplot annotation_raster(arbol, xmin = 0, ymin = 0, xmax = 1, ymax = 1) +
geom_text(aes(x = 0.5, y = 0.1, label = "Merry Christmas"), color = "white",
family = "pconflict", size = 15) +
geom_text(aes(x = 0.5, y = 0.95,
label = "@RodZepeda | rodrigozepeda.github.io/Statisticats/posts/Navidad"),
color = "gray75",
size = 3)
ggsave("arbolito_navidad_2022_en.png", drawplot, dpi = 100, width = 500, height = 500, units = "px")