-
Notifications
You must be signed in to change notification settings - Fork 95
/
Copy pathrender_graphic.R
191 lines (156 loc) · 6.17 KB
/
render_graphic.R
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
library(sf)
library(tidyverse)
library(elevatr)
library(rayshader)
library(glue)
library(colorspace)
library(NatParksPalettes)
library(scico)
###################################
# Set up polygon for clipping DEM #
###################################
# Set map name that will be used in file names, and
# to get get boundaries from master NPS list
map <- "glacier"
data <- st_read("data/nps_boundary/nps_boundary.shp") |>
#filter(str_detect(PARKNAME, str_to_title(str_replace(map, "_", " "))))
filter(PARKNAME == "Glacier") |>
st_transform(crs = 32001)
# Plot to review
data |>
ggplot() +
geom_sf()
################
# Download DEM #
################
# Get DEM using `elevatr` package. Larger Z value (max is 14)
# results in greater resolution. Higher resolution takes more compute, though --
# I can't always max `z` up to 14 on my machine.
z <- 12
zelev <- get_elev_raster(data, z = z, clip = "location")
mat <- raster_to_matrix(zelev)
# When initially building your object to render, you'll want to work with
# slimmed down data so you can iterate faster. I prefer to just start with
# a `z` value of 10 above, but an alternative is to create a smaller matrix
# with rayshader::resize_matrix().
# small <- resize_matrix(mat, .25)
# Set up color palette. The `pal` argument will be used in file names,
# so it's important. `colors` will also be passed along.
pal <- "glacier_lajolla"
c1 <- natparks.pals("Glacier", 5)
c2 <- scico(palette = "lajolla", n = 5)
swatchplot(c1)
colors <- c("white",
c1[5:3],
# lighten(c1[10], .5),
# lighten(c1[10], .75),
c2[4:1],
"white")
swatchplot(colors)
# Calculate the aspect ratio of the plot so you can translate the dimensions
w <- nrow(mat)
h <- ncol(mat)
# Scale so longer side is 1
wr <- w / max(c(w,h))
hr <- h / max(c(w,h))
###################
# Build 3D Object #
###################
# Keep this line so as you're iterating you don't forget to close the
# previous window
try(rgl::rgl.close())
# Create the initial 3D object
mat %>%
# This adds the coloring, we're passing in our `colors` object
height_shade(texture = grDevices::colorRampPalette(colors, bias = .5)(256)) %>%
plot_3d(heightmap = mat,
# This is my preference, I don't love the `solid` in most cases
solid = FALSE,
# You might need to hone this in depending on the data resolution;
# lower values exaggerate the height
z = 10,
# Set the location of the shadow, i.e. where the floor is.
# This is on the same scale as your data, so call `zelev` to see the
# min/max, and set it however far below min as you like.
shadowdepth = -5000,
# Set the window size relatively small with the dimensions of our data.
# Don't make this too big because it will just take longer to build,
# and we're going to resize with `render_highquality()` below.
windowsize = c(800*wr,800*hr),
# This is the azimuth, like the angle of the sun.
# 90 degrees is directly above, 0 degrees is a profile view.
phi = 90,
zoom = 1,
# `theta` is the rotations of the map. Keeping it at 0 will preserve
# the standard (i.e. north is up) orientation of a plot
theta = 0,
background = "white")
# Use this to adjust the view after building the window object
render_camera(phi = 80, zoom = 1, theta = 0)
###############################
# Create High Quality Graphic #
###############################
# You should only move on if you have the object set up
# as you want it, including colors, resolution, viewing position, etc.
# Ensure dir exists for these graphics
if (!dir.exists(glue("images/{map}"))) {
dir.create(glue("images/{map}"))
}
# Set up outfile where graphic will be saved.
# Note that I am not tracking the `images` directory, and this
# is because these files are big enough to make tracking them on
# GitHub difficult.
outfile <- str_to_lower(glue("images/{map}/{map}_{pal}_z{z}.png"))
# Now that everything is assigned, save these objects so we
# can use then in our markup script
saveRDS(list(
map = map,
pal = pal,
z = z,
colors = colors,
outfile = outfile
), glue("R/portraits/{map}/header.rds"))
# Wrap this in brackets so it runs as chunk
{
# Test write a PNG to ensure the file path is good.
# You don't want `render_highquality()` to fail after it's
# taken hours to render.
if (!file.exists(outfile)) {
png::writePNG(matrix(1), outfile)
}
# I like to track when I start the render
start_time <- Sys.time()
cat(glue("Start Time: {start_time}"), "\n")
render_highquality(
# We test-wrote to this file above, so we know it's good
outfile,
# See rayrender::render_scene for more info, but best
# sample method ('sobol') works best with values over 256
samples = 300,
# Turn light off because we're using environment_light
light = FALSE,
# All it takes is accidentally interacting with a render that takes
# hours in total to decide you NEVER want it interactive
interactive = FALSE,
preview = FALSE,
# HDR lighting used to light the scene
environment_light = "assets/env/phalzer_forest_01_4k.hdr",
# environment_light = "assets/env/small_rural_road_4k.hdr",
# Adjust this value to brighten or darken lighting
intensity_env = 1.25,
# Rotate the light -- positive values move it counter-clockwise
rotate_env = -45,
# This effectively sets the resolution of the final graphic,
# because you increase the number of pixels here.
width = round(6000 * wr), height = round(6000 * hr),
# default ground material is diffuse white, here using
# microfacet instead to create a brushed metal appearance
ground_material = rayrender::microfacet(roughness = rev(c(.1, .4)),
# I honestly don't know what the scale is for
# eta and kappy, I just play around until
# I get what I want.
eta=c(3,3,6), kappa=c(90, 90, 100))
)
end_time <- Sys.time()
cat(glue("Total time: {end_time - start_time}"))
}