forked from regl-project/regl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
geomorph.js
157 lines (133 loc) Β· 4.32 KB
/
geomorph.js
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
/*
tags: advanced
<p>No description</p>
*/
const regl = require('../regl')()
const mat4 = require('gl-mat4')
const bunny = require('bunny')
// We'll generate 4 refined levels of detail for the bunny mesh
const NUM_LODS = 4
// First we extract the edges from the bunny mesh
const lodCells = bunny.cells.reduce((edges, cell) => {
edges.push(
[cell[0], cell[1]],
[cell[1], cell[2]],
[cell[2], cell[0]])
return edges
}, [])
// We initialize the finest level of detail to be just the mesh
const lodPositions = [bunny.positions]
const lodOffsets = [lodCells.length]
// For each level of detail, we cluster the vertices and then move all
// of the non-degenerate cells to the front of the buffer
for (let lod = 1; lod <= NUM_LODS; ++lod) {
const points = lodPositions[lod - 1]
// Here we use an exponentially growing bin size, though you could really
// use whatever you like here as long as it is monotonically increasing
const binSize = 0.2 * Math.pow(2.2, lod)
// For the first phase of clustering, we map each vertex into a bin
const grid = {}
points.forEach((p, i) => {
const binId = p.map((x) => Math.floor(x / binSize)).join()
if (binId in grid) {
grid[binId].push(i)
} else {
grid[binId] = [i]
}
})
// Next we iterate over the bins and snap each vertex to the centroid of
// all vertices in its bin
const snapped = Array(points.length)
Object.keys(grid).forEach((binId) => {
const bin = grid[binId]
const centroid = [0, 0, 0]
bin.forEach(function (idx) {
const p = points[idx]
for (let i = 0; i < 3; ++i) {
centroid[i] += p[i] / bin.length
}
})
bin.forEach(function (idx) {
snapped[idx] = centroid
})
})
lodPositions.push(snapped)
// Finally we partition the cell array in place so that all non-degenerate
// cells are moved to the front of the array
const cellCount = lodOffsets[lod - 1]
let ptr = 0
for (let idx = 0; idx < cellCount; ++idx) {
const cell = lodCells[idx]
if (snapped[cell[0]] !== snapped[cell[1]]) {
lodCells[idx] = lodCells[ptr]
lodCells[ptr++] = cell
}
}
// And we save this offset of the last non degenerate cell so that when we
// draw at this level of detail we don't waste time drawing degenerate cells
lodOffsets.push(ptr)
}
// Now that the LODs are computed we upload them to the GPU
const lodBuffers = lodPositions.map(regl.buffer)
// Ok! It's time to define our command:
const drawBunnyWithLOD = regl({
vert: `
precision mediump float;
// p0 and p1 are the two LOD arrays for this command
attribute vec3 p0, p1;
uniform float lod;
uniform mat4 view, projection;
varying vec3 fragColor;
void main () {
vec3 position = mix(p0, p1, lod);
fragColor = 0.5 + (0.2 * position);
gl_Position = projection * view * vec4(position, 1);
}`,
frag: `
precision mediump float;
varying vec3 fragColor;
void main() {
gl_FragColor = vec4(fragColor, 1);
}`,
// We take the two LOD attributes directly above and below the current
// fractional LOD
attributes: {
p0: (_, { lod }) => lodBuffers[Math.floor(lod)],
p1: (_, { lod }) => lodBuffers[Math.ceil(lod)]
},
// For the elements we use the LOD-orderd array of edges that we computed
// earlier. regl automatically infers the primitive type from this data.
elements: lodCells,
uniforms: {
// This is a standard perspective camera
projection: ({ viewportWidth, viewportHeight }) => mat4.perspective([],
Math.PI / 4,
viewportWidth / viewportHeight,
0.01,
1000),
// We slowly rotate the camera around the center of the bunny
view: ({ tick }) => {
const t = 0.004 * tick
return mat4.lookAt([],
[20 * Math.cos(t), 10, 20 * Math.sin(t)],
[0, 2.5, 0],
[0, 1, 0])
},
// We set the lod uniform to be the fractional LOD
lod: (_, { lod }) => lod - Math.floor(lod)
},
// Finally we only draw as many primitives as are present in the finest LOD
count: (_, { lod }) => 2 * lodOffsets[Math.floor(lod)]
})
regl.frame(({ tick }) => {
regl.clear({
depth: 1,
color: [0, 0, 0, 1]
})
// To use the LOD draw command, we just pass it an object with the LOD as
// a single property:
drawBunnyWithLOD({
lod: Math.min(NUM_LODS, Math.max(0,
0.5 * NUM_LODS * (1 + Math.cos(0.003 * tick))))
})
})