@footgun/cobalt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +18 -0
- package/bundle.js +284 -0
- package/cobalt2.jpeg +0 -0
- package/esbuild.js +20 -0
- package/examples/01-primitives/Game.js +8 -0
- package/examples/01-primitives/component-animation.js +8 -0
- package/examples/01-primitives/component-transform.js +13 -0
- package/examples/01-primitives/constants.js +6 -0
- package/examples/01-primitives/deps.js +2 -0
- package/examples/01-primitives/entity-sprite.js +47 -0
- package/examples/01-primitives/index.html +191 -0
- package/examples/01-primitives/system-renderer.js +37 -0
- package/examples/02-sprites/Game.js +8 -0
- package/examples/02-sprites/assets/spritesheet.json +6276 -0
- package/examples/02-sprites/assets/spritesheet.png +0 -0
- package/examples/02-sprites/assets/spritesheet_emissive.png +0 -0
- package/examples/02-sprites/component-animation.js +8 -0
- package/examples/02-sprites/component-transform.js +13 -0
- package/examples/02-sprites/constants.js +6 -0
- package/examples/02-sprites/deps.js +2 -0
- package/examples/02-sprites/entity-sprite.js +47 -0
- package/examples/02-sprites/index.html +310 -0
- package/examples/02-sprites/system-renderer.js +38 -0
- package/examples/03-tiles/Game.js +8 -0
- package/examples/03-tiles/assets/spelunky-tiles.png +0 -0
- package/examples/03-tiles/assets/spelunky0.png +0 -0
- package/examples/03-tiles/assets/spelunky1.png +0 -0
- package/examples/03-tiles/component-animation.js +8 -0
- package/examples/03-tiles/component-transform.js +13 -0
- package/examples/03-tiles/constants.js +6 -0
- package/examples/03-tiles/deps.js +2 -0
- package/examples/03-tiles/entity-sprite.js +47 -0
- package/examples/03-tiles/index.html +309 -0
- package/examples/03-tiles/system-renderer.js +38 -0
- package/examples/04-overlay/assets/spritesheet.json +22 -0
- package/examples/04-overlay/assets/spritesheet.png +0 -0
- package/examples/04-overlay/assets/spritesheet_emissive.png +0 -0
- package/examples/04-overlay/constants.js +6 -0
- package/examples/04-overlay/deps.js +1 -0
- package/examples/04-overlay/index.html +133 -0
- package/examples/05-bloom/Game.js +8 -0
- package/examples/05-bloom/assets/spritesheet.json +6276 -0
- package/examples/05-bloom/assets/spritesheet.png +0 -0
- package/examples/05-bloom/assets/spritesheet_emissive.png +0 -0
- package/examples/05-bloom/component-animation.js +8 -0
- package/examples/05-bloom/component-transform.js +13 -0
- package/examples/05-bloom/constants.js +6 -0
- package/examples/05-bloom/deps.js +2 -0
- package/examples/05-bloom/entity-sprite.js +47 -0
- package/examples/05-bloom/index.html +357 -0
- package/examples/05-bloom/system-renderer.js +38 -0
- package/examples/06-displacement/Game.js +8 -0
- package/examples/06-displacement/assets/displacement_map_repeat.jpg +0 -0
- package/examples/06-displacement/assets/spelunky-tiles.png +0 -0
- package/examples/06-displacement/assets/spelunky0.png +0 -0
- package/examples/06-displacement/assets/spelunky1.png +0 -0
- package/examples/06-displacement/component-animation.js +8 -0
- package/examples/06-displacement/component-transform.js +13 -0
- package/examples/06-displacement/constants.js +6 -0
- package/examples/06-displacement/deps.js +2 -0
- package/examples/06-displacement/entity-sprite.js +47 -0
- package/examples/06-displacement/index.html +350 -0
- package/examples/06-displacement/system-renderer.js +38 -0
- package/examples/07-sdl/assets/spritesheet.json +22 -0
- package/examples/07-sdl/assets/spritesheet.png +0 -0
- package/examples/07-sdl/assets/spritesheet_emissive.png +0 -0
- package/examples/07-sdl/main.js +109 -0
- package/examples/07-sdl/package.json +19 -0
- package/examples/08-light/Game.js +8 -0
- package/examples/08-light/assets/spelunky-tiles.png +0 -0
- package/examples/08-light/assets/spelunky0.png +0 -0
- package/examples/08-light/assets/spelunky1.png +0 -0
- package/examples/08-light/constants.js +6 -0
- package/examples/08-light/deps.js +2 -0
- package/examples/08-light/index.html +477 -0
- package/package.json +34 -0
- package/src/bloom/bloom.js +467 -0
- package/src/bloom/bloom.wgsl +176 -0
- package/src/cobalt.js +231 -0
- package/src/create-texture-from-buffer.js +39 -0
- package/src/create-texture-from-url.js +35 -0
- package/src/create-texture.js +46 -0
- package/src/deps.js +3 -0
- package/src/displacement/composition.wgsl +58 -0
- package/src/displacement/displacement-composition.ts +161 -0
- package/src/displacement/displacement-parameters-buffer.ts +44 -0
- package/src/displacement/displacement-texture.ts +221 -0
- package/src/displacement/displacement.js +160 -0
- package/src/displacement/displacement.wgsl +31 -0
- package/src/displacement/triangles-buffer.ts +95 -0
- package/src/fb-blit/fb-blit.js +161 -0
- package/src/fb-blit/fb-blit.wgsl +40 -0
- package/src/fb-texture/fb-texture.js +56 -0
- package/src/light/README.md +61 -0
- package/src/light/light.js +148 -0
- package/src/light/lights-buffer.ts +98 -0
- package/src/light/lights-renderer.ts +278 -0
- package/src/light/public-api.js +20 -0
- package/src/light/readme/01_illumination.webp +0 -0
- package/src/light/readme/02_lights_texture.webp +0 -0
- package/src/light/readme/03_lights_texture_decomposed.webp +0 -0
- package/src/light/readme/04_lights_texture_mask.webp +0 -0
- package/src/light/readme/05_lights_obstacle_decomposition.webp +0 -0
- package/src/light/readme/06_lights_hard_cast_shadows.webp +0 -0
- package/src/light/texture/lights-texture-initializer.ts +191 -0
- package/src/light/texture/lights-texture-mask.ts +286 -0
- package/src/light/texture/lights-texture.ts +121 -0
- package/src/light/types.ts +23 -0
- package/src/light/viewport.ts +63 -0
- package/src/overlay/constants.js +1 -0
- package/src/overlay/overlay.js +341 -0
- package/src/overlay/overlay.wgsl +88 -0
- package/src/primitives/constants.js +1 -0
- package/src/primitives/primitives.js +252 -0
- package/src/primitives/primitives.wgsl +54 -0
- package/src/primitives/public-api.js +325 -0
- package/src/scene-composite/scene-composite.js +168 -0
- package/src/scene-composite/scene-composite.wgsl +94 -0
- package/src/sprite/constants.js +1 -0
- package/src/sprite/create-sprite-quads.js +60 -0
- package/src/sprite/public-api.js +215 -0
- package/src/sprite/read-spritesheet.js +103 -0
- package/src/sprite/sorted-binary-insert.js +45 -0
- package/src/sprite/sprite.js +268 -0
- package/src/sprite/sprite.wgsl +103 -0
- package/src/sprite/spritesheet.js +212 -0
- package/src/tile/atlas.js +193 -0
- package/src/tile/tile.js +171 -0
- package/src/tile/tile.wgsl +105 -0
- package/src/uuid.js +3 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
struct TransformData {
|
|
2
|
+
view: mat4x4<f32>,
|
|
3
|
+
projection: mat4x4<f32>
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
@binding(0) @group(0) var<uniform> transformUBO: TransformData;
|
|
7
|
+
|
|
8
|
+
struct Fragment {
|
|
9
|
+
@builtin(position) Position : vec4<f32>,
|
|
10
|
+
@location(0) Color : vec4<f32>,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
@vertex
|
|
14
|
+
fn vs_main(@location(0) vertexPosition: vec2<f32>,
|
|
15
|
+
@location(1) vertexColor: vec4<f32>) -> Fragment {
|
|
16
|
+
|
|
17
|
+
var sx: f32 = 1.0; //sprites.models[i_id].scale.x;
|
|
18
|
+
var sy: f32 = 1.0; // sprites.models[i_id].scale.y;
|
|
19
|
+
var sz: f32 = 1.0;
|
|
20
|
+
|
|
21
|
+
var rot: f32 = 0.0; //sprites.models[i_id].rotation;
|
|
22
|
+
|
|
23
|
+
var tx: f32 = 1.0; //sprites.models[i_id].translate.x;
|
|
24
|
+
var ty: f32 = 1.0; //sprites.models[i_id].translate.y;
|
|
25
|
+
var tz: f32 = 0;
|
|
26
|
+
|
|
27
|
+
var s = sin(rot);
|
|
28
|
+
var c = cos(rot);
|
|
29
|
+
|
|
30
|
+
// https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html
|
|
31
|
+
|
|
32
|
+
var scaleM: mat4x4<f32> = mat4x4<f32>(sx, 0.0, 0.0, 0.0,
|
|
33
|
+
0.0, sy, 0.0, 0.0,
|
|
34
|
+
0.0, 0.0, sz, 0.0,
|
|
35
|
+
0, 0, 0, 1.0);
|
|
36
|
+
|
|
37
|
+
// rotation and translation
|
|
38
|
+
var modelM: mat4x4<f32> = mat4x4<f32>(c, s, 0.0, 0.0,
|
|
39
|
+
-s, c, 0.0, 0.0,
|
|
40
|
+
0.0, 0.0, 1.0, 0.0,
|
|
41
|
+
tx, ty, tz, 1.0) * scaleM;
|
|
42
|
+
|
|
43
|
+
var output : Fragment;
|
|
44
|
+
|
|
45
|
+
output.Position = transformUBO.projection * transformUBO.view * modelM * vec4<f32>(vertexPosition, 0.0, 1.0);
|
|
46
|
+
output.Color = vertexColor;
|
|
47
|
+
|
|
48
|
+
return output;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@fragment
|
|
52
|
+
fn fs_main(@location(0) Color: vec4<f32>) -> @location(0) vec4<f32> {
|
|
53
|
+
return Color;
|
|
54
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { vec2 } from '../deps.js'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function line (cobalt, node, start, end, color, lineWidth=1) {
|
|
5
|
+
|
|
6
|
+
const delta = vec2.sub(end, start)
|
|
7
|
+
|
|
8
|
+
const unitBasis = vec2.normalize(delta)
|
|
9
|
+
const perp = perpendicularComponent(unitBasis)
|
|
10
|
+
|
|
11
|
+
const halfLineWidth = lineWidth / 2
|
|
12
|
+
|
|
13
|
+
let i = node.data.vertexCount * 6 // 2 floats position + 4 floats color per vertex
|
|
14
|
+
|
|
15
|
+
// triangle 1
|
|
16
|
+
// pt 1
|
|
17
|
+
node.data.vertices[i + 0] = start[0] + perp[0] * halfLineWidth
|
|
18
|
+
node.data.vertices[i + 1] = start[1] + perp[1] * halfLineWidth
|
|
19
|
+
|
|
20
|
+
// pt1 color
|
|
21
|
+
node.data.vertices[i + 2] = color[0]
|
|
22
|
+
node.data.vertices[i + 3] = color[1]
|
|
23
|
+
node.data.vertices[i + 4] = color[2]
|
|
24
|
+
node.data.vertices[i + 5] = color[3]
|
|
25
|
+
|
|
26
|
+
// pt 2
|
|
27
|
+
node.data.vertices[i + 6] = start[0] - perp[0] * halfLineWidth
|
|
28
|
+
node.data.vertices[i + 7] = start[1] - perp[1] * halfLineWidth
|
|
29
|
+
|
|
30
|
+
// pt2 color
|
|
31
|
+
node.data.vertices[i + 8] = color[0]
|
|
32
|
+
node.data.vertices[i + 9] = color[1]
|
|
33
|
+
node.data.vertices[i + 10] = color[2]
|
|
34
|
+
node.data.vertices[i + 11] = color[3]
|
|
35
|
+
|
|
36
|
+
// pt 3
|
|
37
|
+
node.data.vertices[i + 12] = end[0] + perp[0] * halfLineWidth
|
|
38
|
+
node.data.vertices[i + 13] = end[1] + perp[1] * halfLineWidth
|
|
39
|
+
|
|
40
|
+
// pt3 color
|
|
41
|
+
node.data.vertices[i + 14] = color[0]
|
|
42
|
+
node.data.vertices[i + 15] = color[1]
|
|
43
|
+
node.data.vertices[i + 16] = color[2]
|
|
44
|
+
node.data.vertices[i + 17] = color[3]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
// triangle 2
|
|
48
|
+
// pt 2
|
|
49
|
+
node.data.vertices[i + 18] = start[0] - perp[0] * halfLineWidth
|
|
50
|
+
node.data.vertices[i + 19] = start[1] - perp[1] * halfLineWidth
|
|
51
|
+
|
|
52
|
+
// pt2 color
|
|
53
|
+
node.data.vertices[i + 20] = color[0]
|
|
54
|
+
node.data.vertices[i + 21] = color[1]
|
|
55
|
+
node.data.vertices[i + 22] = color[2]
|
|
56
|
+
node.data.vertices[i + 23] = color[3]
|
|
57
|
+
|
|
58
|
+
// pt 3
|
|
59
|
+
node.data.vertices[i + 24] = end[0] + perp[0] * halfLineWidth
|
|
60
|
+
node.data.vertices[i + 25] = end[1] + perp[1] * halfLineWidth
|
|
61
|
+
|
|
62
|
+
// pt3 color
|
|
63
|
+
node.data.vertices[i + 26] = color[0]
|
|
64
|
+
node.data.vertices[i + 27] = color[1]
|
|
65
|
+
node.data.vertices[i + 28] = color[2]
|
|
66
|
+
node.data.vertices[i + 29] = color[3]
|
|
67
|
+
|
|
68
|
+
// pt 4
|
|
69
|
+
node.data.vertices[i + 30] = end[0] - perp[0] * halfLineWidth
|
|
70
|
+
node.data.vertices[i + 31] = end[1] - perp[1] * halfLineWidth
|
|
71
|
+
|
|
72
|
+
// pt4 color
|
|
73
|
+
node.data.vertices[i + 32] = color[0]
|
|
74
|
+
node.data.vertices[i + 33] = color[1]
|
|
75
|
+
node.data.vertices[i + 34] = color[2]
|
|
76
|
+
node.data.vertices[i + 35] = color[3]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
node.data.vertexCount += 6
|
|
80
|
+
|
|
81
|
+
node.data.dirty = true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// return component of vector perpendicular to a unit basis vector
|
|
86
|
+
// (IMPORTANT NOTE: assumes "basis" has unit magnitude (length==1))
|
|
87
|
+
function perpendicularComponent (inp) {
|
|
88
|
+
return [ -inp[1], inp[0] ]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
export default {
|
|
93
|
+
line,
|
|
94
|
+
|
|
95
|
+
ellipse: function (cobalt, node, center, halfWidth, halfHeight, numSegments, color, lineWidth=1) {
|
|
96
|
+
|
|
97
|
+
const [ x, y ] = center
|
|
98
|
+
|
|
99
|
+
// angle between each segment
|
|
100
|
+
const deltaAngle = 2 * Math.PI / numSegments
|
|
101
|
+
|
|
102
|
+
// Generate points for the ellipsoid
|
|
103
|
+
for (let i = 0; i < numSegments; i++) {
|
|
104
|
+
// Angle for this and the next segment
|
|
105
|
+
const angle = i * deltaAngle
|
|
106
|
+
const nextAngle = (i + 1) * deltaAngle
|
|
107
|
+
|
|
108
|
+
// Calculate x and y for the current and next points on the ellipse
|
|
109
|
+
const currX = x + halfWidth * Math.cos(angle)
|
|
110
|
+
const currY = y + halfHeight * Math.sin(angle)
|
|
111
|
+
const nextX = x + halfWidth * Math.cos(nextAngle)
|
|
112
|
+
const nextY = y + halfHeight * Math.sin(nextAngle)
|
|
113
|
+
|
|
114
|
+
line(cobalt, node, [ currX, currY ], [nextX, nextY ], color, lineWidth)
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
filledEllipse: function (cobalt, node, center, halfWidth, halfHeight, numSegments, color) {
|
|
119
|
+
|
|
120
|
+
const [ x, y ] = center
|
|
121
|
+
|
|
122
|
+
// angle between each segment
|
|
123
|
+
const deltaAngle = 2 * Math.PI / numSegments
|
|
124
|
+
|
|
125
|
+
// Generate points for the ellipsoid
|
|
126
|
+
for (let i = 0; i < numSegments; i++) {
|
|
127
|
+
// Angle for this and the next segment
|
|
128
|
+
const angle = i * deltaAngle
|
|
129
|
+
const nextAngle = (i + 1) * deltaAngle
|
|
130
|
+
|
|
131
|
+
// Calculate x and y for the current and next points on the ellipse
|
|
132
|
+
const currX = x + halfWidth * Math.cos(angle)
|
|
133
|
+
const currY = y + halfHeight * Math.sin(angle)
|
|
134
|
+
const nextX = x + halfWidth * Math.cos(nextAngle)
|
|
135
|
+
const nextY = y + halfHeight * Math.sin(nextAngle)
|
|
136
|
+
|
|
137
|
+
// Add vertices for the triangles (first point is always the center)
|
|
138
|
+
// First triangle vertex (center of ellipse)
|
|
139
|
+
|
|
140
|
+
const stride = 18 // 2 floats position + 4 floats color per vertex * 3 vertices
|
|
141
|
+
const vi = (node.data.vertexCount * 6) + (i * stride)
|
|
142
|
+
|
|
143
|
+
// position
|
|
144
|
+
node.data.vertices[vi + 0] = x
|
|
145
|
+
node.data.vertices[vi + 1] = y
|
|
146
|
+
|
|
147
|
+
// color
|
|
148
|
+
node.data.vertices[vi + 2] = color[0]
|
|
149
|
+
node.data.vertices[vi + 3] = color[1]
|
|
150
|
+
node.data.vertices[vi + 4] = color[2]
|
|
151
|
+
node.data.vertices[vi + 5] = color[3]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
// Second triangle vertex (current point on ellipse)
|
|
155
|
+
|
|
156
|
+
// position
|
|
157
|
+
node.data.vertices[vi + 6] = currX
|
|
158
|
+
node.data.vertices[vi + 7] = currY
|
|
159
|
+
|
|
160
|
+
// color
|
|
161
|
+
node.data.vertices[vi + 8] = color[0]
|
|
162
|
+
node.data.vertices[vi + 9] = color[1]
|
|
163
|
+
node.data.vertices[vi + 10] = color[2]
|
|
164
|
+
node.data.vertices[vi + 11] = color[3]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
// Third triangle vertex (next point on ellipse)
|
|
168
|
+
// position
|
|
169
|
+
node.data.vertices[vi + 12] = nextX
|
|
170
|
+
node.data.vertices[vi + 13] = nextY
|
|
171
|
+
|
|
172
|
+
// color
|
|
173
|
+
node.data.vertices[vi + 14] = color[0]
|
|
174
|
+
node.data.vertices[vi + 15] = color[1]
|
|
175
|
+
node.data.vertices[vi + 16] = color[2]
|
|
176
|
+
node.data.vertices[vi + 17] = color[3]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
node.data.vertexCount += (3 * numSegments)
|
|
180
|
+
|
|
181
|
+
node.data.dirty = true
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// @param Number angle rotation (radians)
|
|
185
|
+
box: function (cobalt, node, center, width, height, color, angle=0, lineWidth=1) {
|
|
186
|
+
const [ x, y ] = center
|
|
187
|
+
|
|
188
|
+
const halfWidth = width / 2
|
|
189
|
+
const halfHeight = height / 2
|
|
190
|
+
|
|
191
|
+
const topLeft = [ x - halfWidth, y - halfHeight ]
|
|
192
|
+
const topRight = [ x + halfWidth, y - halfHeight ]
|
|
193
|
+
const bottomLeft = [ x - halfWidth, y + halfHeight ]
|
|
194
|
+
const bottomRight = [ x + halfWidth, y + halfHeight ]
|
|
195
|
+
|
|
196
|
+
if (angle !== 0) {
|
|
197
|
+
// rotate the point by <angle> rads around origin
|
|
198
|
+
// point origin rads out
|
|
199
|
+
_rotate(topLeft, center, angle, topLeft)
|
|
200
|
+
_rotate(topRight, center, angle, topRight)
|
|
201
|
+
_rotate(bottomLeft, center, angle, bottomLeft)
|
|
202
|
+
_rotate(bottomRight, center, angle, bottomRight)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
line(cobalt, node, topLeft, topRight, color, lineWidth)
|
|
206
|
+
line(cobalt, node, bottomLeft, bottomRight, color, lineWidth)
|
|
207
|
+
line(cobalt, node, topLeft, bottomLeft, color, lineWidth)
|
|
208
|
+
line(cobalt, node, topRight, bottomRight, color, lineWidth)
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
// @param Number angle rotation (radians)
|
|
214
|
+
filledBox: function (cobalt, node, center, width, height, color, angle=0) {
|
|
215
|
+
const [ x, y ] = center
|
|
216
|
+
|
|
217
|
+
const halfWidth = width / 2
|
|
218
|
+
const halfHeight = height / 2
|
|
219
|
+
|
|
220
|
+
const topLeft = [ x - halfWidth, y - halfHeight ];
|
|
221
|
+
const topRight = [ x + halfWidth, y - halfHeight ];
|
|
222
|
+
const bottomLeft = [ x - halfWidth, y + halfHeight ];
|
|
223
|
+
const bottomRight = [ x + halfWidth, y + halfHeight ];
|
|
224
|
+
|
|
225
|
+
if (angle !== 0) {
|
|
226
|
+
// rotate the point by <angle> rads around origin
|
|
227
|
+
// point origin rads out
|
|
228
|
+
_rotate(topLeft, center, angle, topLeft)
|
|
229
|
+
_rotate(topRight, center, angle, topRight)
|
|
230
|
+
_rotate(bottomLeft, center, angle, bottomLeft)
|
|
231
|
+
_rotate(bottomRight, center, angle, bottomRight)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let i = node.data.vertexCount * 6 // 2 floats position + 4 floats color per vertex
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
// triangle 1
|
|
238
|
+
// pt 1
|
|
239
|
+
node.data.vertices[i + 0] = topLeft[0]
|
|
240
|
+
node.data.vertices[i + 1] = topLeft[1]
|
|
241
|
+
|
|
242
|
+
// pt1 color
|
|
243
|
+
node.data.vertices[i + 2] = color[0]
|
|
244
|
+
node.data.vertices[i + 3] = color[1]
|
|
245
|
+
node.data.vertices[i + 4] = color[2]
|
|
246
|
+
node.data.vertices[i + 5] = color[3]
|
|
247
|
+
|
|
248
|
+
// pt 2
|
|
249
|
+
node.data.vertices[i + 6] = bottomLeft[0]
|
|
250
|
+
node.data.vertices[i + 7] = bottomLeft[1]
|
|
251
|
+
|
|
252
|
+
// pt2 color
|
|
253
|
+
node.data.vertices[i + 8] = color[0]
|
|
254
|
+
node.data.vertices[i + 9] = color[1]
|
|
255
|
+
node.data.vertices[i + 10] = color[2]
|
|
256
|
+
node.data.vertices[i + 11] = color[3]
|
|
257
|
+
|
|
258
|
+
// pt 3
|
|
259
|
+
node.data.vertices[i + 12] = topRight[0]
|
|
260
|
+
node.data.vertices[i + 13] = topRight[1]
|
|
261
|
+
|
|
262
|
+
// pt3 color
|
|
263
|
+
node.data.vertices[i + 14] = color[0]
|
|
264
|
+
node.data.vertices[i + 15] = color[1]
|
|
265
|
+
node.data.vertices[i + 16] = color[2]
|
|
266
|
+
node.data.vertices[i + 17] = color[3]
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
// triangle 2
|
|
270
|
+
// pt 2
|
|
271
|
+
node.data.vertices[i + 18] = bottomLeft[0]
|
|
272
|
+
node.data.vertices[i + 19] = bottomLeft[1]
|
|
273
|
+
|
|
274
|
+
// pt2 color
|
|
275
|
+
node.data.vertices[i + 20] = color[0]
|
|
276
|
+
node.data.vertices[i + 21] = color[1]
|
|
277
|
+
node.data.vertices[i + 22] = color[2]
|
|
278
|
+
node.data.vertices[i + 23] = color[3]
|
|
279
|
+
|
|
280
|
+
// pt 3
|
|
281
|
+
node.data.vertices[i + 24] = bottomRight[0]
|
|
282
|
+
node.data.vertices[i + 25] = bottomRight[1]
|
|
283
|
+
|
|
284
|
+
// pt3 color
|
|
285
|
+
node.data.vertices[i + 26] = color[0]
|
|
286
|
+
node.data.vertices[i + 27] = color[1]
|
|
287
|
+
node.data.vertices[i + 28] = color[2]
|
|
288
|
+
node.data.vertices[i + 29] = color[3]
|
|
289
|
+
|
|
290
|
+
// pt 4
|
|
291
|
+
node.data.vertices[i + 30] = topRight[0]
|
|
292
|
+
node.data.vertices[i + 31] = topRight[1]
|
|
293
|
+
|
|
294
|
+
// pt4 color
|
|
295
|
+
node.data.vertices[i + 32] = color[0]
|
|
296
|
+
node.data.vertices[i + 33] = color[1]
|
|
297
|
+
node.data.vertices[i + 34] = color[2]
|
|
298
|
+
node.data.vertices[i + 35] = color[3]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
node.data.vertexCount += 6 // 2 triangles in a box, baby
|
|
302
|
+
|
|
303
|
+
node.data.dirty = true
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
clear: function (cobalt, node) {
|
|
307
|
+
node.data.vertexCount = 0
|
|
308
|
+
node.data.dirty = true
|
|
309
|
+
},
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
function _rotate (a, b, rad, out) {
|
|
314
|
+
//Translate point to the origin
|
|
315
|
+
let p0 = a[0] - b[0],
|
|
316
|
+
p1 = a[1] - b[1],
|
|
317
|
+
sinC = Math.sin(rad),
|
|
318
|
+
cosC = Math.cos(rad);
|
|
319
|
+
|
|
320
|
+
//perform rotation and translate to correct position
|
|
321
|
+
out[0] = p0 * cosC - p1 * sinC + b[0];
|
|
322
|
+
out[1] = p0 * sinC + p1 * cosC + b[1];
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as Cobalt from '../cobalt.js'
|
|
2
|
+
import sceneCompositeWGSL from './scene-composite.wgsl'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
type: 'cobalt:bloom',
|
|
7
|
+
refs: [
|
|
8
|
+
{ name: 'hdr', type: 'textureView', format: 'rgba16', access: 'read' },
|
|
9
|
+
{ name: 'bloom', type: 'textureView', format: 'rgba16', access: 'read' },
|
|
10
|
+
{ name: 'combined', type: 'textureView', format: 'rgba8unorm', access: 'write' },
|
|
11
|
+
],
|
|
12
|
+
// @params Object cobalt renderer world object
|
|
13
|
+
// @params Object options optional data passed when initing this node
|
|
14
|
+
onInit: async function (cobalt, options={}) {
|
|
15
|
+
return init(cobalt, options)
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
onRun: function (cobalt, node, webGpuCommandEncoder) {
|
|
19
|
+
// do whatever you need for this node. webgpu renderpasses, etc.
|
|
20
|
+
draw(cobalt, node, webGpuCommandEncoder)
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
onDestroy: function (cobalt, node) {
|
|
24
|
+
// any cleanup for your node should go here (releasing textures, etc.)
|
|
25
|
+
//destroy(node)
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
onResize: function (cobalt, node) {
|
|
29
|
+
// do whatever you need when the dimensions of the renderer change (resize textures, etc.)
|
|
30
|
+
resize(cobalt, node)
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
onViewportPosition: function (cobalt, node) { },
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
function init (cobalt, node) {
|
|
38
|
+
const { options, refs } = node
|
|
39
|
+
|
|
40
|
+
const { device } = cobalt
|
|
41
|
+
const format = Cobalt.getPreferredFormat(cobalt) // bgra8unorm
|
|
42
|
+
|
|
43
|
+
const bloom_intensity = options.bloom_intensity ?? 40.0
|
|
44
|
+
const bloom_combine_constant = options.bloom_combine_constant ?? 0.68
|
|
45
|
+
const dat = new Float32Array([ bloom_intensity, bloom_combine_constant ])
|
|
46
|
+
const params_buf = device.createBuffer({
|
|
47
|
+
label: 'scene composite params buffer',
|
|
48
|
+
size: dat.byteLength, // vec4<f32> and f32 and u32 with 4 bytes per float32 and 4 bytes per u32
|
|
49
|
+
mappedAtCreation: true,
|
|
50
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
new Float32Array(params_buf.getMappedRange()).set(dat)
|
|
54
|
+
|
|
55
|
+
params_buf.unmap()
|
|
56
|
+
|
|
57
|
+
const pipeline = device.createRenderPipeline({
|
|
58
|
+
layout: 'auto',
|
|
59
|
+
vertex: {
|
|
60
|
+
module: device.createShaderModule({
|
|
61
|
+
code: sceneCompositeWGSL,
|
|
62
|
+
}),
|
|
63
|
+
entryPoint: 'vert_main',
|
|
64
|
+
},
|
|
65
|
+
fragment: {
|
|
66
|
+
module: device.createShaderModule({
|
|
67
|
+
code: sceneCompositeWGSL,
|
|
68
|
+
}),
|
|
69
|
+
entryPoint: 'frag_main',
|
|
70
|
+
targets: [
|
|
71
|
+
{
|
|
72
|
+
format,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
primitive: {
|
|
77
|
+
topology: 'triangle-list',
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const bindGroup = device.createBindGroup({
|
|
82
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
83
|
+
entries: [
|
|
84
|
+
{
|
|
85
|
+
binding: 0,
|
|
86
|
+
resource: refs.hdr.data.sampler,
|
|
87
|
+
},
|
|
88
|
+
// color
|
|
89
|
+
{
|
|
90
|
+
binding: 1,
|
|
91
|
+
resource: refs.hdr.data.view,
|
|
92
|
+
},
|
|
93
|
+
// emissive
|
|
94
|
+
{
|
|
95
|
+
binding: 2,
|
|
96
|
+
resource: refs.bloom.data.mip_view[0],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
binding: 3,
|
|
100
|
+
resource: {
|
|
101
|
+
buffer: params_buf,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
bindGroup,
|
|
109
|
+
pipeline,
|
|
110
|
+
params_buf,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
// combine bloom and color textures and draw to a fullscreen quad
|
|
116
|
+
function draw (cobalt, node, commandEncoder) {
|
|
117
|
+
|
|
118
|
+
const passEncoder = commandEncoder.beginRenderPass({
|
|
119
|
+
colorAttachments: [
|
|
120
|
+
{
|
|
121
|
+
view: node.refs.combined.data.view, //getCurrentTextureView(cobalt)
|
|
122
|
+
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
|
|
123
|
+
loadOp: 'clear',
|
|
124
|
+
storeOp: 'store',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const { pipeline, bindGroup } = node.data
|
|
130
|
+
|
|
131
|
+
passEncoder.setPipeline(pipeline)
|
|
132
|
+
passEncoder.setBindGroup(0, bindGroup)
|
|
133
|
+
passEncoder.draw(3)
|
|
134
|
+
passEncoder.end()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
function resize (cobalt, node) {
|
|
139
|
+
const { pipeline, params_buf } = node.data
|
|
140
|
+
const { device } = cobalt
|
|
141
|
+
|
|
142
|
+
node.data.bindGroup = device.createBindGroup({
|
|
143
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
144
|
+
entries: [
|
|
145
|
+
{
|
|
146
|
+
binding: 0,
|
|
147
|
+
resource: node.refs.hdr.data.sampler,
|
|
148
|
+
},
|
|
149
|
+
// color
|
|
150
|
+
{
|
|
151
|
+
binding: 1,
|
|
152
|
+
resource: node.refs.hdr.data.view,
|
|
153
|
+
},
|
|
154
|
+
// emissive
|
|
155
|
+
{
|
|
156
|
+
binding: 2,
|
|
157
|
+
resource: node.refs.bloom.data.mip_view[0], //bloom_mat.bind_groups_textures[2].mip_view[0],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
binding: 3,
|
|
161
|
+
resource: {
|
|
162
|
+
buffer: params_buf,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
|
|
2
|
+
struct BloomComposite {
|
|
3
|
+
bloom_intensity: f32,
|
|
4
|
+
bloom_combine_constant: f32,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@group(0) @binding(0) var mySampler : sampler;
|
|
8
|
+
@group(0) @binding(1) var colorTexture : texture_2d<f32>;
|
|
9
|
+
@group(0) @binding(2) var emissiveTexture : texture_2d<f32>;
|
|
10
|
+
@group(0) @binding(3) var<uniform> composite_parameter: BloomComposite;
|
|
11
|
+
|
|
12
|
+
struct VertexOutput {
|
|
13
|
+
@builtin(position) Position : vec4<f32>,
|
|
14
|
+
@location(0) fragUV : vec2<f32>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// fullscreen triangle position and uvs
|
|
18
|
+
const positions = array<vec2<f32>, 3>(
|
|
19
|
+
vec2<f32>(-1.0, -3.0),
|
|
20
|
+
vec2<f32>(3.0, 1.0),
|
|
21
|
+
vec2<f32>(-1.0, 1.0)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const uvs = array<vec2<f32>, 3>(
|
|
25
|
+
vec2<f32>(0.0, 2.0),
|
|
26
|
+
vec2<f32>(2.0, 0.0),
|
|
27
|
+
vec2<f32>(0.0, 0.0)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@vertex
|
|
32
|
+
fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
|
|
33
|
+
|
|
34
|
+
var output : VertexOutput;
|
|
35
|
+
output.Position = vec4<f32>(positions[VertexIndex], 0.0, 1.0);
|
|
36
|
+
output.fragUV = vec2<f32>(uvs[VertexIndex]);
|
|
37
|
+
|
|
38
|
+
return output;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// can be optimized into lut (compute can gen it)
|
|
43
|
+
fn GTTonemap_point(x: f32) -> f32{
|
|
44
|
+
let m: f32 = 0.22; // linear section start
|
|
45
|
+
let a: f32 = 1.0; // contrast
|
|
46
|
+
let c: f32 = 1.33; // black brightness
|
|
47
|
+
let P: f32 = 1.0; // maximum brightness
|
|
48
|
+
let l: f32 = 0.4; // linear section length
|
|
49
|
+
let l0: f32 = ((P-m)*l) / a; // 0.312
|
|
50
|
+
let S0: f32 = m + l0; // 0.532
|
|
51
|
+
let S1: f32 = m + a * l0; // 0.532
|
|
52
|
+
let C2: f32 = (a*P) / (P - S1); // 2.13675213675
|
|
53
|
+
let L: f32 = m + a * (x - m);
|
|
54
|
+
let T: f32 = m * pow(x/m, c);
|
|
55
|
+
let S: f32 = P - (P - S1) * exp(-C2*(x - S0)/P);
|
|
56
|
+
let w0: f32 = 1.0 - smoothstep(0.0, m, x);
|
|
57
|
+
var w2: f32 = 1.0;
|
|
58
|
+
if (x < m+l) {
|
|
59
|
+
w2 = 0.0;
|
|
60
|
+
}
|
|
61
|
+
let w1: f32 = 1.0 - w0 - w2;
|
|
62
|
+
return f32(T * w0 + L * w1 + S * w2);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// this costs about 0.2-0.3ms more than aces, as-is
|
|
66
|
+
fn GTTonemap(x: vec3<f32>) -> vec3<f32>{
|
|
67
|
+
return vec3<f32>(GTTonemap_point(x.r), GTTonemap_point(x.g), GTTonemap_point(x.b));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
fn aces(x: vec3<f32>) -> vec3<f32> {
|
|
72
|
+
let a: f32 = 2.51;
|
|
73
|
+
let b: f32 = 0.03;
|
|
74
|
+
let c: f32 = 2.43;
|
|
75
|
+
let d: f32 = 0.59;
|
|
76
|
+
let e: f32 = 0.14;
|
|
77
|
+
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@fragment
|
|
82
|
+
fn frag_main(@location(0) fragUV : vec2<f32>) -> @location(0) vec4<f32> {
|
|
83
|
+
|
|
84
|
+
let hdr_color = textureSample(colorTexture, mySampler, fragUV);
|
|
85
|
+
let bloom_color = textureSample(emissiveTexture, mySampler, fragUV);
|
|
86
|
+
|
|
87
|
+
let combined_color = ((bloom_color * composite_parameter.bloom_intensity) * composite_parameter.bloom_combine_constant);
|
|
88
|
+
|
|
89
|
+
let mapped_color = GTTonemap(combined_color.rgb);
|
|
90
|
+
//let mapped_color = aces(combined_color.rgb);
|
|
91
|
+
let gamma_corrected_color = pow(mapped_color, vec3<f32>(1.0 / 2.2));
|
|
92
|
+
|
|
93
|
+
return vec4<f32>(gamma_corrected_color + hdr_color.rgb, 1.0);
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const FLOAT32S_PER_SPRITE = 12 // vec2(translate) + vec2(scale) + vec4(tint) + opacity + rotation + emissiveIntensity + sortValue
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export default function createSpriteQuads (device, spritesheet) {
|
|
2
|
+
|
|
3
|
+
// u,v coordinates specify top left as 0,0 bottom right as 1,1
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
const vertices = new Float32Array([
|
|
7
|
+
// position tex
|
|
8
|
+
// x y z u v
|
|
9
|
+
-0.5, -0.5, 0.0, 0.0, 0.0,
|
|
10
|
+
-0.5, 0.5, 0.0, 0.0, 1.0,
|
|
11
|
+
0.5, 0.5, 0.0, 1.0, 1.0,
|
|
12
|
+
|
|
13
|
+
// triangle 2 (2nd half of quad)
|
|
14
|
+
-0.5, -0.5, 0.0, 0.0, 0.0,
|
|
15
|
+
0.5, 0.5, 0.0, 1.0, 1.0,
|
|
16
|
+
0.5, -0.5, 0.0, 1.0, 0.0,
|
|
17
|
+
])
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const vertices = spritesheet.vertices
|
|
21
|
+
|
|
22
|
+
const usage = GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
23
|
+
|
|
24
|
+
const descriptor = {
|
|
25
|
+
size: vertices.byteLength,
|
|
26
|
+
usage,
|
|
27
|
+
// make this memory space accessible from the CPU (host visible)
|
|
28
|
+
mappedAtCreation: true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const buffer = device.createBuffer(descriptor)
|
|
32
|
+
|
|
33
|
+
new Float32Array(buffer.getMappedRange()).set(vertices)
|
|
34
|
+
|
|
35
|
+
buffer.unmap()
|
|
36
|
+
|
|
37
|
+
const bufferLayout = {
|
|
38
|
+
arrayStride: 20,
|
|
39
|
+
stepMode: 'vertex',
|
|
40
|
+
attributes: [
|
|
41
|
+
// position
|
|
42
|
+
{
|
|
43
|
+
shaderLocation: 0,
|
|
44
|
+
format: 'float32x3',
|
|
45
|
+
offset: 0
|
|
46
|
+
},
|
|
47
|
+
// uv
|
|
48
|
+
{
|
|
49
|
+
shaderLocation: 1,
|
|
50
|
+
format: 'float32x2',
|
|
51
|
+
offset: 12
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
buffer,
|
|
58
|
+
bufferLayout,
|
|
59
|
+
}
|
|
60
|
+
}
|