Skip to content

Commit 6841dcc

Browse files
committed
improve gradient selection in gradient noises for better quality noise:
1 parent fd2ae89 commit 6841dcc

File tree

9 files changed

+64
-48
lines changed

9 files changed

+64
-48
lines changed

sources/noise/gradient.swift

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,58 @@
11
import func Glibc.sin
22
import func Glibc.cos
33

4-
fileprivate let SQUISH_2D :Double = 0.5 * (1 / 3.squareRoot() - 1)
5-
fileprivate let STRETCH_2D:Double = 0.5 * (3.squareRoot() - 1)
4+
fileprivate
5+
enum Const
6+
{
7+
fileprivate static
8+
let SQUISH_2D :Double = 0.5 * (1 / 3.squareRoot() - 1),
9+
STRETCH_2D:Double = 0.5 * (3.squareRoot() - 1)
10+
11+
// each gradient appears four times to mitigate hashing biases
12+
fileprivate static
13+
let GRADIENTS_2D:[Math.DoubleV2] =
14+
[
15+
(1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0 , -1),
16+
(0.7, 0.7), (-0.7, 0.7), (-0.7, -0.7), (0.7, -0.7),
17+
18+
(0.7, -0.7),
19+
(1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0 , -1),
20+
(0.7, 0.7), (-0.7, 0.7), (-0.7, -0.7),
21+
22+
(-0.7, -0.7), (0.7, -0.7),
23+
(1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0 , -1),
24+
(0.7, 0.7), (-0.7, 0.7),
25+
26+
(-0.7, 0.7), (-0.7, -0.7), (0.7, -0.7),
27+
(1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0 , -1),
28+
(0.7, 0.7)
29+
]
30+
31+
fileprivate static
32+
let GRADIENTS_3D:[Math.DoubleV3] =
33+
[
34+
(1, 1, 0), (-1, 1, 0), (1, -1, 0), (-1, -1, 0),
35+
(1, 0, 1), (-1, 0, 1), (1, 0, -1), (-1, 0, -1),
36+
(0, 1, 1), ( 0, -1, 1), (0, 1, -1), ( 0, -1, -1),
37+
(1, 1, 0), (-1, 1, 0), (0, -1, 1), ( 0, -1, -1),
38+
39+
(0, -1, -1),
40+
(1, 1, 0), (-1, 1, 0), (1, -1, 0), (-1, -1, 0),
41+
(1, 0, 1), (-1, 0, 1), (1, 0, -1), (-1, 0, -1),
42+
(0, 1, 1), ( 0, -1, 1), (0, 1, -1), ( 0, -1, -1),
43+
(1, 1, 0), (-1, 1, 0), (0, -1, 1)
44+
]
45+
}
646

747
fileprivate
848
protocol GradientNoise2D:Noise
949
{
1050
var permutation_table:PermutationTable { get }
1151

12-
static var gradient_table16:[Math.DoubleV2] { get }
52+
static var gradient_table32:[Math.DoubleV2] { get }
1353
static var radius:Double { get }
54+
55+
1456
}
1557

1658
fileprivate
@@ -21,7 +63,7 @@ extension GradientNoise2D
2163
let dr:Double = Self.radius - Math.dot(offset, offset)
2264
if dr > 0
2365
{
24-
let gradient:Math.DoubleV2 = Self.gradient_table16[self.permutation_table.hash(point) & 15],
66+
let gradient:Math.DoubleV2 = Self.gradient_table32[self.permutation_table.hash(point) & 31],
2567
drdr:Double = dr * dr
2668
return drdr * drdr * Math.dot(gradient, offset)
2769
}
@@ -36,14 +78,7 @@ public
3678
struct SimplexNoise2D:GradientNoise2D
3779
{
3880
fileprivate static
39-
let gradient_table16:[Math.DoubleV2] =
40-
[
41-
(-1, -1), (1, 0), (-1, 0), (1, 1),
42-
(-1, 1), (0, -1), (0, 1), (1, -1),
43-
44-
(-1, -1), (1, 0), (-1, 0), (1, 1),
45-
(-1, 1), (0, -1), (0, 1), (1, -1)
46-
]
81+
let gradient_table32:[Math.DoubleV2] = Const.GRADIENTS_2D
4782

4883
fileprivate static
4984
let radius:Double = 2
@@ -58,7 +93,7 @@ struct SimplexNoise2D:GradientNoise2D
5893
public
5994
init(amplitude:Double, frequency:Double, seed:Int = 0)
6095
{
61-
self.amplitude = 0.096 * amplitude
96+
self.amplitude = 0.1322 * amplitude
6297
self.frequency = frequency
6398
self.permutation_table = PermutationTable(seed: seed)
6499
}
@@ -69,7 +104,7 @@ struct SimplexNoise2D:GradientNoise2D
69104
let sample:Math.DoubleV2 = (x * self.frequency, y * self.frequency)
70105
// transform our coordinate system so that the *simplex* (x, y) forms a
71106
// rectangular grid (u, v)
72-
let squish_offset:Double = (sample.x + sample.y) * SQUISH_2D,
107+
let squish_offset:Double = (sample.x + sample.y) * Const.SQUISH_2D,
73108
sample_uv:Math.DoubleV2 = (sample.x + squish_offset, sample.y + squish_offset)
74109

75110
// get integral (u, v) coordinates of the rhombus and get position inside
@@ -99,7 +134,7 @@ struct SimplexNoise2D:GradientNoise2D
99134
// do the same in the original (x, y) coordinate space
100135

101136
// stretch back to get (x, y) coordinates of rhombus origin
102-
let stretch_offset:Double = Double(bin.a + bin.b) * STRETCH_2D,
137+
let stretch_offset:Double = Double(bin.a + bin.b) * Const.STRETCH_2D,
103138
origin:Math.DoubleV2 = (Double(bin.a) + stretch_offset, Double(bin.b) + stretch_offset)
104139

105140
// get relative position inside the rhombus relative to (xb, xb)
@@ -114,27 +149,27 @@ struct SimplexNoise2D:GradientNoise2D
114149
}
115150

116151
// contribution from (1, 0)
117-
_inspect(point_offset: (1, 0), sample_offset: (1 + STRETCH_2D, STRETCH_2D))
152+
_inspect(point_offset: (1, 0), sample_offset: (1 + Const.STRETCH_2D, Const.STRETCH_2D))
118153

119154
// contribution from (0, 1)
120-
_inspect(point_offset: (0, 1), sample_offset: (STRETCH_2D, 1 + STRETCH_2D))
155+
_inspect(point_offset: (0, 1), sample_offset: (Const.STRETCH_2D, 1 + Const.STRETCH_2D))
121156

122157
// decide which triangle we are in
123158
let uv_sum:Double = sample_uv_rel.x + sample_uv_rel.y
124159
if (uv_sum > 1) // we are to the bottom-right of the diagonal line (du = 1 - dv)
125160
{
126-
_inspect(point_offset: (1, 1), sample_offset: (1 + 2*STRETCH_2D, 1 + 2*STRETCH_2D))
161+
_inspect(point_offset: (1, 1), sample_offset: (1 + 2*Const.STRETCH_2D, 1 + 2*Const.STRETCH_2D))
127162

128163
let center_dist:Double = 2 - uv_sum
129164
if center_dist < sample_uv_rel.x || center_dist < sample_uv_rel.y
130165
{
131166
if sample_uv_rel.x > sample_uv_rel.y
132167
{
133-
_inspect(point_offset: (2, 0), sample_offset: (2 + 2*STRETCH_2D, 2*STRETCH_2D))
168+
_inspect(point_offset: (2, 0), sample_offset: (2 + 2*Const.STRETCH_2D, 2*Const.STRETCH_2D))
134169
}
135170
else
136171
{
137-
_inspect(point_offset: (0, 2), sample_offset: (2*STRETCH_2D, 2 + 2*STRETCH_2D))
172+
_inspect(point_offset: (0, 2), sample_offset: (2*Const.STRETCH_2D, 2 + 2*Const.STRETCH_2D))
138173
}
139174
}
140175
else
@@ -160,7 +195,7 @@ struct SimplexNoise2D:GradientNoise2D
160195
}
161196
else
162197
{
163-
_inspect(point_offset: (1, 1), sample_offset: (1 + 2*STRETCH_2D, 1 + 2*STRETCH_2D))
198+
_inspect(point_offset: (1, 1), sample_offset: (1 + 2*Const.STRETCH_2D, 1 + 2*Const.STRETCH_2D))
164199
}
165200
}
166201

@@ -192,7 +227,7 @@ struct SuperSimplexNoise2D:GradientNoise2D
192227
@inline(__always)
193228
func _lattice_point(at point:Math.IntV2) -> (Math.IntV2, Math.DoubleV2)
194229
{
195-
let stretch_offset:Double = Double(point.a + point.b) * SQUISH_2D
230+
let stretch_offset:Double = Double(point.a + point.b) * Const.SQUISH_2D
196231
return (point, (Double(point.a) + stretch_offset, Double(point.b) + stretch_offset))
197232
}
198233

@@ -212,20 +247,7 @@ struct SuperSimplexNoise2D:GradientNoise2D
212247
}()
213248

214249
static
215-
let gradient_table16:[Math.DoubleV2] =
216-
{
217-
var gradients:[Math.DoubleV2] = []
218-
gradients.reserveCapacity(16)
219-
220-
let :Double = 2 * Double.pi / Double(16)
221-
for i in 0 ..< 16
222-
{
223-
let θ:Double = Double(i) *
224-
gradients.append((cos(θ), sin(θ)))
225-
}
226-
227-
return gradients
228-
}()
250+
let gradient_table32:[Math.DoubleV2] = Const.GRADIENTS_2D
229251

230252
static
231253
let radius:Double = 2/3
@@ -239,7 +261,7 @@ struct SuperSimplexNoise2D:GradientNoise2D
239261
public
240262
init(amplitude:Double, frequency:Double, seed:Int = 0)
241263
{
242-
self.amplitude = 18 * amplitude
264+
self.amplitude = 18.5 * amplitude
243265
self.frequency = frequency
244266
self.permutation_table = PermutationTable(seed: seed)
245267
}
@@ -249,7 +271,7 @@ struct SuperSimplexNoise2D:GradientNoise2D
249271
{
250272
let sample:Math.DoubleV2 = (x * self.frequency, y * self.frequency)
251273
// transform our (x, y) coordinate to (u, v) space
252-
let stretch_offset:Double = (sample.x + sample.y) * STRETCH_2D,
274+
let stretch_offset:Double = (sample.x + sample.y) * Const.STRETCH_2D,
253275
sample_uv:Math.DoubleV2 = (sample.x + stretch_offset, sample.y + stretch_offset)
254276

255277
// (0, 0) ----- (1, 0)
@@ -358,7 +380,7 @@ struct SuperSimplexNoise2D:GradientNoise2D
358380
*/
359381

360382
// get the relative offset from (0, 0)
361-
let squish_offset:Double = (sample_uv_rel.x + sample_uv_rel.y) * SQUISH_2D,
383+
let squish_offset:Double = (sample_uv_rel.x + sample_uv_rel.y) * Const.SQUISH_2D,
362384
sample_rel:Math.DoubleV2 = (sample_uv_rel.x + squish_offset, sample_uv_rel.y + squish_offset)
363385

364386
var Σ:Double = 0
@@ -408,13 +430,7 @@ struct SuperSimplexNoise3D:Noise
408430
}()
409431

410432
private static
411-
let gradient_table16:[Math.DoubleV3] =
412-
[
413-
(1, 1, 0), (-1, 1, 0), (1, -1, 0), (-1, -1, 0),
414-
(1, 0, 1), (-1, 0, 1), (1, 0, -1), (-1, 0, -1),
415-
(0, 1, 1), ( 0, -1, 1), (0, 1, -1), ( 0, -1, -1),
416-
(1, 1, 0), (-1, 1, 0), (0, -1, 1), ( 0, -1, -1)
417-
]
433+
let gradient_table32:[Math.DoubleV3] = Const.GRADIENTS_3D
418434

419435
private
420436
let permutation_table:PermutationTable
@@ -437,7 +453,7 @@ struct SuperSimplexNoise3D:Noise
437453
let dr:Double = 0.75 - Math.dot(offset, offset)
438454
if dr > 0
439455
{
440-
let gradient:Math.DoubleV3 = SuperSimplexNoise3D.gradient_table16[self.permutation_table.hash(point) & 15],
456+
let gradient:Math.DoubleV3 = SuperSimplexNoise3D.gradient_table32[self.permutation_table.hash(point) & 31],
441457
drdr:Double = dr * dr
442458
return drdr * drdr * Math.dot(gradient, offset)
443459
}

tests/banner_FBM.png

2.55 KB
Loading

tests/banner_simplex2d.png

-3.57 KB
Loading

tests/banner_supersimplex2d.png

-3.79 KB
Loading

tests/banner_supersimplex3d.png

-920 Bytes
Loading

tests/noise/tests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public
220220
func banners(width:Int, ratio:Double)
221221
{
222222
let height:Int = Int(Double(width) / ratio)
223-
banner_simplex2d (width: width, height: height, seed: 5)
223+
banner_simplex2d (width: width, height: height, seed: 6)
224224
banner_supersimplex2d(width: width, height: height, seed: 8)
225225
banner_supersimplex3d(width: width, height: height, seed: 0)
226226
banner_cell2d (width: width, height: height, seed: 0)

tests/simplex2d.png

22.3 KB
Loading

tests/super_simplex2d.png

8.19 KB
Loading

tests/super_simplex3d.png

-14.2 KB
Loading

0 commit comments

Comments
 (0)