-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathREADME.Rmd
429 lines (328 loc) · 15.5 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
---
output: github_document
editor_options:
chunk_output_type: console
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
dpi = 100,
fig.width = 8,
fig.height = 8,
out.width = "100%"
)
options(crayon.enabled = TRUE)
ansi_aware_handler <- function(x, options) {
paste0(
"<pre class=\"r-output\"><code>",
fansi::to_html(x = fansi::html_esc(x), warn = FALSE,
classes = FALSE,
term.cap = "all"),
"</code></pre>"
)
}
knitr::knit_hooks$set(
output = ansi_aware_handler,
message = ansi_aware_handler,
warning = ansi_aware_handler,
error = ansi_aware_handler
)
```
rayvertex
=========================================================
<!-- badges: start -->
[![R-CMD-check](https://github.com/tylermorganwall/rayvertex/workflows/R-CMD-check/badge.svg)](https://github.com/tylermorganwall/rayvertex/actions)
[![R-CMD-check](https://github.com/tylermorganwall/rayvertex/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tylermorganwall/rayvertex/actions/workflows/R-CMD-check.yaml)
<!-- badges: end -->
<img src="man/figures/cubey.gif" ></img>
Rayvertex is a 3D software renderer that allows you to generate 3D visualizations without worrying about hardware requirements or dependencies. It uses rasterization: a method that transforms meshes of triangles into an image. These meshes are specified entirely in R, and can be built either by importing external files, building scenes out of the included objects, or by constructing the mesh yourself. Rayvertex also allows the user to add any number of point lights and directional lights to a scene, and support anti-aliased lines, shadow mapping, transparent (and translucent) objects.
<img src="man/figures/featuredfull.jpg" ></img>
------
Rayvertex features the following:
* Multicore tiled rendering
* User-specified render resolution
* Multiple material shader types:
* Diffuse
* Phong
* Vertex
* Toon (cel) shading
* Flat shading
* Built in shapes:
* Sphere, cube, cylinder, segment, cone, arrow, torus, and plane
* OBJ file support
* `mesh3d` support
* Built-in mesh transformations:
* Rotation, translation, and scale
* Support for normal maps, emission maps, ambient maps, and textures
* Support for multiple point lights and directional lights with shadow mapping
* Support for multicolor 3D line rasterization (both basic and anti-aliased)
* Hashed material IDs for efficient memory use
* Order-independent transparency rendering
* Orthographic and projective cameras
* Screen-space ambient occlusion
* Translucent (tinted) shadows
* Reflection Maps
* Refractive Materials
* Bloom
* Tone mapping
* Environment Maps
Meshes and scenes are specified via list structure that contains the following:
* A list of shapes that each include:
* An integer matrix of indices
* (optional) An integer matrix of indices for normal values
* (optional) An integer matrix of indices for texture coordinates
* A vector of material IDs, indexing into the following material property list
* A logical vector, indicating whether each vertex has normals
* A logical vector, indicating whether each vertex has texture coordinates
* A list of material properties. Rayvertex looks for the following:
* Diffuse color
* Ambient color
* Specular color
* Transmittance color
* Emission color
* Shininess (specular exponent)
* Index of refraction (ior)
* Dissolve (transparency)
* Illumination model
* Diffuse Texture Image Filename
* Ambient Texture Image Filename
* Emissive Texture Image Filename
* Ambient Texture Image Filename
* Normal Texture Image Filename
* Diffuse Intensity
* Ambient Intensity
* Emissive Intensity
* Ambient Intensity
* Culling type
* Shader type
* Translucency
* Number of Toon levels
* Reflection Intensity
* A 3xN matrix of vertex positions
* A 2xN matrix of texture coordinates for each vertex
* A 3xN matrix of normals for each vertex
* A vector of material hashes. This can save memory by only adding a new material when it does not
already exist in the object.
Rayvertex includes a series of helper functions that allow you to generate, translate, and scale meshes.
You can install rayvertex from [GitHub](https://github.com/) with:
``` r
# install.packages("devtools")
devtools::install_github("tylermorganwall/rayvertex")
```
## Example
Here, we will render a basic scene. We'll use the built-in Cornell Box mesh to begin. Here is how the scene data is printed. Note the shapes column gives the number of triangles, whether the texture (UV)/normal coordinates are given, and the number of materials for that shape. This output also prints out overall scene information such as the bounding box and the number of shapes and unique materials.
```{r cornell}
library(rayvertex)
generate_cornell_mesh()
```
We can render it by passing it to `rasterize_scene()`.
```{r cornell_render}
generate_cornell_mesh() |>
rasterize_scene()
```
Let's add a purple sphere to the scene, and a picture of a dragon in a Cornell box to the back:
```{r cornell2}
dragon_file = tempfile(fileext = ".png")
png::writePNG(rayimage::dragon, dragon_file)
mat = material_list(diffuse="purple", type = "phong", ambient="purple", ambient_intensity = 0.2)
mat_dragon = material_list(ambient_texture_location = dragon_file, ambient = "white",
diffuse_intensity = 0)
generate_cornell_mesh() |>
add_shape(sphere_mesh(position=c(300,555,555)/2, radius=80, material=mat)) |>
add_shape(xy_rect_mesh(position = c(555/2,555/2,530), angle = c(180,0,0),
scale = c(400,400,1),
material = mat_dragon)) |>
rasterize_scene(fov=40)
```
We can preview the material properties by printing the materials in the scene. This prints out non-default material arguments, whether a texture exists, as well provides a rough preview of the color.
```{r cornell_print_material}
scene_save = generate_cornell_mesh() |>
add_shape(sphere_mesh(position=c(300,555,555)/2, radius=80, material=mat)) |>
add_shape(xy_rect_mesh(position = c(555/2,555/2,530), angle = c(180,0,0),
scale = c(400,400,1),
material = mat_dragon))
print(scene_save$material)
```
Now, the ceiling of the Cornell Box is blocking the directional light. Let's remove it.
```{r cornell3}
generate_cornell_mesh(ceiling=FALSE) |>
add_shape(sphere_mesh(position=c(555,555,555)/2, radius=80, material=mat)) |>
rasterize_scene()
```
Let's add a cylinder and a platform to the bottom of our sphere.
```{r cornell4}
mat2 = material_list(diffuse="grey80", ambient="grey80", ambient_intensity = 0.2)
generate_cornell_mesh(ceiling=FALSE) |>
add_shape(sphere_mesh(position=c(555,555,555)/2, radius=80, material=mat)) |>
add_shape(segment_mesh(start=c(555/2,0,555/2),end=c(555/2,196,555/2),
radius=30, material=mat2)) |>
add_shape(cube_mesh(position=c(555/2,555/2-90,555/2),
scale=c(160,20,160),material=mat2)) |>
rasterize_scene()
```
Now let's change the angle of the directional light so it's angled from the front :
```{r cornell5}
generate_cornell_mesh(ceiling=FALSE) |>
add_shape(sphere_mesh(position=c(555,555,555)/2, radius=80, material=mat)) |>
add_shape(segment_mesh(start=c(555/2,0,555/2),end=c(555/2,196,555/2),
radius=30, material=mat2)) |>
add_shape(cube_mesh(position=c(555/2,555/2-90,555/2),
scale=c(160,20,160),material=mat2)) |>
rasterize_scene(light_info = directional_light(c(0.4,0.2,-1)))
```
And let's add all the other basic mesh shapes included in the package (including the included OBJ file and the `humface` mesh3d object from the Rvcg package):
```{r cornell6}
library(Rvcg)
data(humface)
cols = hsv(seq(0,1,length.out=6))
mats = list()
for(i in 1:5) {
mats[[i]] = material_list(diffuse=cols[i], ambient=cols[i], type="phong",
ambient_intensity = 0.2)
}
generate_cornell_mesh(ceiling=FALSE) |>
add_shape(sphere_mesh(position=c(555,555,555)/2, radius=80, material=mat)) |>
add_shape(segment_mesh(start=c(555/2,0,555/2),end=c(555/2,196,555/2),
radius=30, material=mat2)) |>
add_shape(cube_mesh(position=c(555/2,555/2-90,555/2),
scale=c(160,20,160),material=mat2)) |>
add_shape(torus_mesh(position=c(100,100,100), radius = 50, ring_radius = 20,
angle=c(45,0,45),material=mats[[1]])) |>
add_shape(cone_mesh(start=c(555-100,0,100), end=c(555-100,150,100), radius = 50,
material=mats[[2]])) |>
add_shape(arrow_mesh(start=c(555-100,455,555-100), end=c(100,455,555-100),
radius_top = 50, radius_tail=10, tail_proportion = 0.8,
material=mats[[3]])) |>
add_shape(obj_mesh(r_obj(), position=c(100,200,555/2), angle=c(-10,200,0),
scale=80,material=mats[[4]])) |>
add_shape(mesh3d_mesh(humface, position = c(555-80,220,555/2),scale = 1,
material=mats[[5]],angle=c(0,180,-30))) |>
rasterize_scene(light_info = directional_light(c(0.4,0.2,-1)))
```
We can also draw shapes with toon shading:
```{r toonshading}
set.seed(1)
col = hsv(runif(1))
scene = sphere_mesh(position=runif(3),
material=material_list(diffuse=col, type="toon",toon_levels = 3,
toon_outline_width = 0.025,
ambient=col,ambient_intensity=0.2),radius=0.1)
for(i in 1:30) {
col = hsv(runif(1))
scene = add_shape(scene, sphere_mesh(position=runif(3),
material=material_list(diffuse=col, type="toon",toon_levels = 3,
toon_outline_width = 0.025,
ambient=col, ambient_intensity=0.2),
radius=0.1))
}
rasterize_scene(scene, light_info=directional_light(direction=c(0.5,0.8,1)),
background = "white",fov=10)
```
You can also include a environment map to use for reflective, semi-reflective, and refractive surfaces. The roughness of the reflection can be controlled on a per-material basis with the `reflection_sharpness` argument.
```{r reflectionshading}
tempfilehdr = tempfile(fileext = ".hdr")
download.file("https://www.tylermw.com/data/venice_sunset_2k.hdr",tempfilehdr)
scene = torus_mesh(position=c(0.4,0,0),angle=c(-30,20,-30),
material=material_list(diffuse=c(1,1,1), type="color",
reflection_intensity = 1.0, reflection_sharpness = 0.2),
ring_radius=0.05,radius=0.2) |>
add_shape(torus_mesh(position=c(0.4,0.5,0),angle=c(-30,20,-130),
material=material_list(diffuse="green", ambient="green", type="phong",
ambient_intensity = 0.2, diffuse_intensity=0.8,
reflection_intensity = 0.5, reflection_sharpness = 0.05),
ring_radius=0.05,radius=0.2)) |>
add_shape(sphere_mesh(position=c(-0.4,0,0),
material=material_list(diffuse="white", type="color",ior=1.6),radius=0.2)) |>
add_shape(obj_mesh(r_obj(),position=c(-0.4,0.35,0),scale=0.2, angle=c(0,-30,0),
material=material_list(diffuse="purple", type="color",ior=1.6))) |>
add_shape(sphere_mesh(position=c(0,0.25,0),
material=material_list(diffuse="white", type="color",reflection_intensity = 1.0),
radius=0.2))
rasterize_scene(scene, lookat=c(0,0.25,0),
light_info=directional_light(direction=c(0.5,1,1)),
lookfrom=c(0,0.5,2.5),
fov=30, environment_map = tempfilehdr)
```
You can also blur the background but keep the reflections sharp by setting the `background_sharpness` argument to draw focus to your 3D scene.
```{r sharpness}
rasterize_scene(scene, lookat=c(0,0.25,0),
light_info=directional_light(direction=c(0.5,1,1)),
lookfrom=c(0,0.5,2.5),
fov=30, environment_map = tempfilehdr, background_sharpness = 0.5)
```
Now let's draw another example scene: we'll add the R OBJ to a flat surface.
```{r line1}
base_model = cube_mesh() |>
scale_mesh(scale=c(5,0.2,5)) |>
translate_mesh(c(0,-0.1,0)) |>
set_material(diffuse="white")
r_model = obj_mesh(r_obj()) |>
scale_mesh(scale=0.5) |>
set_material(diffuse="red") |>
add_shape(base_model)
rasterize_scene(r_model, lookfrom=c(2,4,10),fov=20,
light_info = directional_light(direction=c(0.8,1,0.7)))
```
We can reduce the shadow intensity so the shadows aren't black. Alternatively, you can add an `ambient` term to the material.
```{r line2}
#Zoom in and reduce the shadow mapping intensity
rasterize_scene(r_model, lookfrom=c(2,4,10), fov=10,shadow_map = TRUE, shadow_map_intensity=0.3,
light_info = directional_light(direction=c(0.8,1,0.7)))
```
We can increase the resolution of the shadow map to increase the fidelity of the shadows. This can reduce the amount of "pixelation" around the edges.
```{r line3}
rasterize_scene(r_model, lookfrom=c(2,4,10), fov=10,
shadow_map_dims=2, light_info = directional_light(direction=c(0.8,1,0.7)))
```
We can add multiple directional lights and change their color and intensity:
```{r line4}
lights = directional_light(c(0.7,1.1,-0.9),color = "orange",intensity = 0.7) |>
add_light(directional_light(c(0.7,1,1),color = "dodgerblue",intensity = 0.7)) |>
add_light(directional_light(c(2,4,10),color = "white",intensity = 0.3))
rasterize_scene(r_model, lookfrom=c(2,4,10), fov=10,
light_info = lights)
```
We can change the transparency of the material, which allows for colored shadows.
```{r trans1}
r_model_t = obj_mesh(r_obj()) |>
scale_mesh(scale=0.5) |>
set_material(diffuse="red", dissolve=0.5, translucent = T) |>
add_shape(base_model)
r_model_t = obj_mesh(r_obj(),position = c(-2,0,0.3)) |>
scale_mesh(scale=0.5) |>
set_material(diffuse="dodgerblue", dissolve=0.5, translucent = T) |>
add_shape(r_model_t)
rasterize_scene(r_model_t, lookfrom=c(2,4,10),fov=15,lookat=c(-0.5,0,0),
light_info = directional_light(direction=c(0.8,1,0.7), intensity = 0.5) |>
add_light(directional_light(direction=c(-0.8,1,0.7),intensity = 0.5)))
```
We can also add some point lights:
```{r line5}
#Add some point lights
lights_p = lights |>
add_light(point_light(position=c(-1,1,0),color="red", intensity=2)) |>
add_light(point_light(position=c(1,1,0),color="purple", intensity=2))
rasterize_scene(r_model, lookfrom=c(2,4,10), fov=10,
light_info = lights_p)
```
We can change the camera position by adjusting the `lookfrom` argument:
```{r line6}
#change the camera position
rasterize_scene(r_model, lookfrom=c(-2,2,-10), fov=10,
light_info = lights_p)
```
Finally, we can also add 3D lines to the scene. We'll add a spiral of lines around the R.
```{r line7}
t = seq(0,8*pi,length.out=361)
line_mat = matrix(nrow=0,ncol=9)
for(i in 1:360) {
line_mat = add_lines(line_mat,
generate_line(start = c(0.5*sin(t[i]), t[i]/(8*pi), 0.5*cos(t[i])),
end = c(0.5*sin(t[i+1]), t[i+1]/(8*pi), 0.5*cos(t[i+1]))))
}
rasterize_scene(r_model, lookfrom=c(2,4,10), fov=10, line_info = line_mat,
light_info = lights)
```