-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNoise.html
374 lines (269 loc) · 15.2 KB
/
Noise.html
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
---
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Noise </title>
<!-- CDNs -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.2/p5.min.js" integrity="sha512-1YMgn4j8cIL91s14ByDGmHtBU6+F8bWOMcF47S0cRO3QNm8SKPNexy4s3OCim9fABUtO++nJMtcpWbINWjMSzQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/themes/prism.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/prism.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/SimulationLabs/Stylesheets/Default.css" rel="stylesheet">
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
processEscapes: true
}
});
</script>
<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type="text/javascript"></script>
<!-- Simulations -->
<script src="/SimulationLabs/Scripts/Noise/RandomHeightMap.js" defer></script>
<script src="/SimulationLabs/Scripts/Noise/NoiseHeightMap.js" defer></script>
<script src="/SimulationLabs/Scripts/Noise/SmoothNoiseHeightMap.js" defer></script>
<script src="/SimulationLabs/Scripts/Noise/DesertIslands.js" defer></script>
<script src="/SimulationLabs/Scripts/Noise/DetailedDesertIslands.js" defer></script>
</head>
<body>
{% include HeaderBar.html %}
<!-- Page content -->
<div class="section">
<h2> What you'll create </h2>
<p> The aim of this workshop is understand some applications of Perlin Noise, and use it to create some colourful terrain!
Once you can generate this terrain as a 2D image, you could apply this in 3D contexts as well to create Minecraft-like worlds. </p>
<div id="DetailedDesertIslands"></div>
<em> Click to generate a new map </em>
<p> We will be coding in JavaScript but don't worry about knowing the ins and outs of the language; you should be able to pick it up as we go! </p>
<h2> Using p5.js </h2>
We will be using JavaScript and a library called <strong> p5.js</strong>, made by the lovely <a href="https://processingfoundation.org/"> Processing Foundation</a>.
<ol>
<li> <p>To get started, create an account for the <a href="https://editor.p5js.org/signup">web editor</a> </p> </li>
<li> <p> Open a new sketch (File->New) </p> </li>
</ol>
<h3> What is a sketch? </h3>
<p>A sketch is a small program that will work with p5.js to produce a simulation.
Just like a game, your simulation will have frames (an image of your simulation) and p5.js will aim to show 60 frames a second.
You'll be able to code what happens on certain events e.g. just before a frame is shown, just before the simulation starts, when the mouse is clicked.
<br><br>
In this lab, since we're just drawing an image, we'll only concern ourselves with drawing before the simulation starts and when the mouse is clicked.
</p>
<p> Your new sketch should have 2 functions: </p>
<ul>
<li> <strong> setup() </strong> which is a function executed once at the start </li>
<li> <strong> draw() </strong> which is a function executed every frame </li>
</ul>
We're going to <strong>ignore</strong> the <strong>draw()</strong> function, since we'll just be using the setup function. In other words, in this lab, just delete the <strong>draw()</strong> function.
We create the canvas (arguments being the width and length) in <strong> setup() </strong>; this is something you only do once pretty much regardless of the simulation.
<pre>
<code class="language-js">
function setup() {
createCanvas(400, 400);
let redV = 128; // if you're new to js, use "let" to declare variables
let greenV = 12;
let blueV = 128;
background(redV, greenV, blueV); // my favourite colour; purple!
}
</code>
</pre>
<em> RGB values have R,G,B values ranging from 0-255 </em>
<br>
<p><strong>Mini-Task:</strong> Try running the above simulation! Maybe change the variables to display your own favourite colour. </p>
<h2> Creating Random Noise </h2>
<div id="RandomHeightMap"></div>
<em>Click to generate new random noise</em>
<br>
<p>Let's dive straight into generating random noise! We'll need to know a couple things: </p>
<ol>
<li>Drawing a pixel to the screen, with a specific colour</li>
<li>Generating random values</li>
<li>Turning a random value into a colour</li>
</ol>
<p>Let's tackle (1) first!</p>
<h3>Pixels</h3>
<pre>
<code class="language-js">
function setup() {
createCanvas(400, 400);
// the eyes
point(25, 25);
point(28, 25);
// the smile
point(25, 30);
point(26, 30);
point(27, 30);
point(28, 30);
point(24, 29);
point(23, 28);
point(29, 29);
point(30, 28);
}
</code>
</pre>
<p>We can use the <strong>point()</strong> function to draw a pixel at specified <strong>X,Y</strong> coordinates. What about the colour? </p>
<pre>
<code class="language-js">
function setup() {
createCanvas(400, 400);
// eye colour
stroke(0, 0, 255);
// the eyes
...
// smile colour
stroke(255, 0, 0);
// the smile
...
}
</code>
</pre>
<p>We can use the <strong>stroke()</strong> function to determine the colour of the pixels drawn from that point on; we can supply it with 3 values to specify an RGB colour.
If we're only using grayscale colours, then we can provide just a single value (to represent the brightness or "value" of the colour) e.g. 255 for pure white, 0 for pure black.
</p>
<p>Using this knowledge, can you guess what the following code does?</p>
<pre>
<code class="language-js">
function setup() {
createCanvas(400, 400);
for(let y = 0; y < 400; y++){
for(let i = 0; i <= 255; i++){
stroke(i);
point(i,y);
}
}
}
</code>
</pre>
<p><strong>Mini-Task:</strong> Run the above simulation, and see if you guessed what it does correctly! </p>
<h3>Random Values</h3>
<p>A key thing to remember about p5.js is that ultimately, it is still JavaScript. Therefore, we can use JavaScript functions and libraries with p5.js. </p>
<p>To handle randomness, we'll use js' Math library; in particular, the function <strong>Math.random()</strong>! Let's use the JavaScript console to test it out. </p>
<pre>
<code class="language-js">
for(let i = 0; i < 20; i++){
console.log(Math.random()); // values between 0-1
}
</code>
</pre>
<p> Okay cool, there's only a slight problem now; we get random values in the range 0-1, but the colour values are in the range 0-255. All we need to do is multiply up :) </p>
<h4>Use the docs! </h4>
<p>If you ever have questions about p5 functions, then please refer to the excellent <a href="https://p5js.org/reference/">documentation</a>! </p>
<h3>Task: Random Noise</h3>
<p>It's now your job to replicate the random noise sketch! Use the tools above and the template below to create some random noise. </p>
<pre>
<code class="language-js">
const width = 400; // these are "constant" variables, as they don't change
const height = 400; // made to be variables since they're used in 2 places (iterating and createCanvas)
function drawRandomNoise(){
for(let x = 0; x < width; x++){
for(let y = 0; y < height; y++){
// FILL ME
}
}
}
function setup() {
createCanvas(width, height);
drawRandomNoise();
}
</code>
</pre>
<em>Generally, taking this out into its own function for this is a good idea; especially since this function's sole purpose is drawing random noise </em>
<h2>Perlin Noise</h2>
<p>Okay so we've programmed <em>random noise</em> which is "incoherent" noise - you can have sharp constrasts between neighbouring pixels.
If we want nice smooth gradients, then we can use perlin noise! </p>
<div id="NoiseHeightMap"></div>
<p>We can definitely see that the noise is more ordered than random noise, however we want it to be less sharp; we want it to be smoother. </p>
<h4>Smoothing it out</h4>
<p>So how do we achieve a smoother noise? We can figure this out by looking at 1D noise, and think about sampling 10 points from perlin noise. </p>
<image src="/SimulationLabs/Images/Noise/InfrequentSamplingNoise.PNG"></image>
<p>From the above, we can see that the values sampled can be quite different. You can think of this as sampling the 10 points at $x=1, 2, 3, \dots$ </p>
<p>What if we sampled points more frequently instead?</p>
<image src="/SimulationLabs/Images/Noise/FrequentSamplingNoise.PNG"></image>
<p>From the above, we can see that the values sampled are <em>a lot closer</em>; you can think of this as sampling the 10 points at $x=0.2, 0.4, 0.6 \dots$</p>
<p> So the general takeaway is that we can generate smoother noise, by sampling more frequently. How do we do that? We can just divide x,y before feeding it into the noise function!</p>
<div id="SmoothNoiseHeightMap"></div>
<h2>Task: Smooth Noise </h2>
<p>Use the tools above to generate some smooth perlin noise! Have a variable called the smoothing factor, and change its value to see how the noise varies. </p>
<h2>Creating Islands</h2>
<p>We now have the tools to create islands! By designating pixel with noise values past a certain threshold as "land" and the rest as "ocean", we can generate islands.
Since the <strong>noise()</strong> function returns a value between 0 and 1, we should have a threshold value between 0 and 1. </p>
<pre>
<code class="language-js">
const threshold = 0.55;
function getColour(noiseVal){
if(noiseVal > threshold){
return color(239, 221, 111); // sandy yellow (island)
}else{
return color(0, 157, 196); // ocean blue
}
}
</code>
</pre>
<p> Note that the above function returns a "colour" object; if you want to work with large p5.js programs, then you should really be using classes and objects.
Now we can use the returned colour object like so:
</p>
<pre>
<code class="language-js">
let colour = getColour(noise(x/sf, y/sf)); // colour object
stroke(colour); // stroke accepts the colour object directly (instead of number values representing a colour)
</code>
</pre>
<h2> Task: Desert Islands </h2>
<p>Create desert island generation by modifying your smooth noise task, with the above tools. It should look something like below: </p>
<div id="DesertIslands"></div>
<h3>More Detailed Islands </h3>
<p>Sometimes, you want more detailed islands; you want the overall shape of the islands to be the same so you have the same smoothness factor,
but you want there to be some more granular detail. You might want your beaches to have some roughness to its terrain.
After all, terrain isn't perfectly smooth in nature!
</p>
<p>You can add this granular detail by adding layers of perlin noise together; each of these layers being called "octaves".
Typically, you'll have a large amplitude perlin noise wave which describes the general overall shape,
and then you'll have a smaller amplitude perlin noise waves to describe the finer details.
These smaller waves usually have higher frequency, which will give the islands a distinct roughness.
</p>
<p> You can increase the number of octaves and how much the smaller waves contribute to the noise by using p5's <strong>noiseDetail()</strong> function.
Read up on the details <a href="https://p5js.org/reference/p5/noiseDetail/">here</a>.</p>
<p>You can also add more "detail" to the islands by creating multiple threshold levels, like in the example at the top of this webpage! An example of a more complicated threshold colour function: </p>
<pre>
<code class="language-js">
const snowcapThreshold = 0.75;
const mountainThreshold = 0.6;
const grassThreshold = 0.4;
const shallowWaterThreshold = 0.35;
function getColour(noiseVal){
if(noiseVal > snowcapThreshold){
return color(219, 219, 219); // snowcap white
}else if(noiseVal > mountainThreshold){
return color(112, 112, 112); // mountain gray
}else if (noiseVal > grassThreshold){
return color(75, 139, 59); // grass green
}else if (noiseVal > shallowWaterThreshold){
return color(0, 157, 196); // light blue
}else{
return color(0, 147, 186); // slightly darker blue
}
}
</code>
</pre>
<h2> Final Task: Create your own islands! </h2>
<p>You have everything you need to create your own amazing terrain generation!
You can take my generator(s) (displayed at the top of the page) as inspiration, but I'm sure you'll be able to create a better looking generator than mine.
</p>
<p>You can view the code for my two generators here:</p>
<ul>
<li><a href="https://editor.p5js.org/RexMortem/sketches/sk3iSZ_ly">Desert Island Generator</a></li>
<li><a href="https://editor.p5js.org/RexMortem/sketches/-9_U5dF6S">Mountainous Terrain Generator</a></li>
</ul>
<p><strong>Tip: </strong> Getting good terrain with perlin noise is often a case of just tweaking your parameters (octaves, amplitude/frequency, smooth factor etc) until they're <em>just</em> right. </p>
<h2> Going Further </h2>
<p> If you want to make your generator even cooler, then you can explore more procedural generation techniques. Natural next steps include: </p>
<ul>
<li>Adding rivers (perhaps using perlin worms) </li>
<li>Adding trees e.g. with poisson-disc sampling </li>
<li>Structure generation</li>
</ul>
</section>
</body>
</html>