-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathmain.ts
286 lines (242 loc) · 9.45 KB
/
main.ts
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
import { PrefixSumKernel } from 'webgpu-radix-sort';
import { mat4 } from 'wgpu-matrix'
import { Camera } from './camera'
import { mlsmpmParticleStructSize, MLSMPMSimulator } from './mls-mpm/mls-mpm'
import { SPHSimulator, sphParticleStructSize } from './sph/sph';
import { renderUniformsViews, renderUniformsValues, numParticlesMax } from './common'
import { FluidRenderer } from './render/fluidRender'
/// <reference types="@webgpu/types" />
async function init() {
const canvas: HTMLCanvasElement = document.querySelector('canvas')!
if (!navigator.gpu) {
alert("WebGPU is not supported on your browser.");
throw new Error()
}
const adapter = await navigator.gpu.requestAdapter()
if (!adapter) {
alert("Adapter is not available.");
throw new Error()
}
const device = await adapter.requestDevice()
const context = canvas.getContext('webgpu') as GPUCanvasContext
if (!context) {
throw new Error()
}
// const { devicePixelRatio } = window
// let devicePixelRatio = 3.0;
let devicePixelRatio = 0.7;
canvas.width = devicePixelRatio * canvas.clientWidth
canvas.height = devicePixelRatio * canvas.clientHeight
const presentationFormat = navigator.gpu.getPreferredCanvasFormat()
context.configure({
device,
format: presentationFormat,
})
return { canvas, device, presentationFormat, context }
}
async function main() {
const { canvas, device, presentationFormat, context } = await init();
console.log("initialization done")
context.configure({
device,
format: presentationFormat,
})
let cubemapTexture: GPUTexture;
{
// The order of the array layers is [+X, -X, +Y, -Y, +Z, -Z]
const imgSrcs = [
'cubemap/posx.png',
'cubemap/negx.png',
'cubemap/posy.png',
'cubemap/negy.png',
'cubemap/posz.png',
'cubemap/negz.png',
];
const promises = imgSrcs.map(async (src) => {
const response = await fetch(src);
return createImageBitmap(await response.blob());
});
const imageBitmaps = await Promise.all(promises);
cubemapTexture = device.createTexture({
dimension: '2d',
// Create a 2d array texture.
// Assume each image has the same size.
size: [imageBitmaps[0].width, imageBitmaps[0].height, 6],
format: 'rgba8unorm',
usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
});
for (let i = 0; i < imageBitmaps.length; i++) {
const imageBitmap = imageBitmaps[i];
device.queue.copyExternalImageToTexture(
{ source: imageBitmap },
{ texture: cubemapTexture, origin: [0, 0, i] },
[imageBitmap.width, imageBitmap.height]
);
}
}
const cubemapTextureView = cubemapTexture.createView({
dimension: 'cube',
});
console.log("cubemap initialization done")
// uniform buffer を作る
renderUniformsViews.texel_size.set([1.0 / canvas.width, 1.0 / canvas.height]);
// storage buffer を作る
const maxParticleStructSize = Math.max(mlsmpmParticleStructSize, sphParticleStructSize)
const particleBuffer = device.createBuffer({
label: 'particles buffer',
size: maxParticleStructSize * numParticlesMax,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
})
const posvelBuffer = device.createBuffer({
label: 'position buffer',
size: 32 * numParticlesMax, // 32 = 2 x vec3f + padding
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
})
const renderUniformBuffer = device.createBuffer({
label: 'filter uniform buffer',
size: renderUniformsValues.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
})
console.log("buffer allocating done")
let mlsmpmNumParticleParams = [40000, 70000, 120000, 200000]
let mlsmpmInitBoxSizes = [[35, 25, 55], [40, 30, 60], [45, 40, 80], [50, 50, 80]]
let mlsmpmInitDistances = [60, 70, 90, 100]
let sphNumParticleParams = [10000, 20000, 30000, 40000]
let sphInitBoxSizes = [[0.7, 2.0, 0.7], [1.0, 2.0, 1.0], [1.2, 2.0, 1.2], [1.4, 2.0, 1.4]]
let sphInitDistances = [2.6, 3.0, 3.4, 3.8]
const canvasElement = document.getElementById("fluidCanvas") as HTMLCanvasElement;
// シミュレーション,カメラの初期化
const mlsmpmFov = 45 * Math.PI / 180
const mlsmpmRadius = 0.6
const mlsmpmDiameter = 2 * mlsmpmRadius
const mlsmpmZoomRate = 1.5
const mlsmpmSimulator = new MLSMPMSimulator(particleBuffer, posvelBuffer, mlsmpmDiameter, device)
const sphFov = 45 * Math.PI / 180
const sphRadius = 0.04
const sphDiameter = 2 * sphRadius
const sphZoomRate = 0.05
const sphSimulator = new SPHSimulator(particleBuffer, posvelBuffer, sphDiameter, device)
const mlsmpmRenderer = new FluidRenderer(device, canvas, presentationFormat, mlsmpmRadius, mlsmpmFov, posvelBuffer, renderUniformBuffer, cubemapTextureView)
const sphRenderer = new FluidRenderer(device, canvas, presentationFormat, sphRadius, sphFov, posvelBuffer, renderUniformBuffer, cubemapTextureView)
console.log("simulator initialization done")
const camera = new Camera(canvasElement);
// ボタン押下の監視
let numberButtonForm = document.getElementById('number-button') as HTMLFormElement;
let numberButtonPressed = false;
let numberButtonPressedButton = "1"
numberButtonForm.addEventListener('change', function(event) {
const target = event.target as HTMLInputElement
if (target?.name === 'options') {
numberButtonPressed = true
numberButtonPressedButton = target.value
}
});
let simulationModeForm = document.getElementById('simulation-mode') as HTMLFormElement;
let simulationModePressed = false;
let simulationModePressedButton = "mls-mpm"
simulationModeForm.addEventListener('change', function(event) {
const target = event.target as HTMLInputElement
if (target?.name === 'options') {
simulationModePressed = true
simulationModePressedButton = target.value
}
});
const smallValue = document.getElementById("small-value") as HTMLSpanElement;
const mediumValue = document.getElementById("medium-value") as HTMLSpanElement;
const largeValue = document.getElementById("large-value") as HTMLSpanElement;
const veryLargeValue = document.getElementById("very-large-value") as HTMLSpanElement;
// デバイスロストの監視
let errorLog = document.getElementById('error-reason') as HTMLSpanElement;
errorLog.textContent = "";
device.lost.then(info => {
const reason = info.reason ? `reason: ${info.reason}` : 'unknown reason';
errorLog.textContent = reason;
});
// はじめは mls-mpm
const initDistance = mlsmpmInitDistances[1]
let initBoxSize = mlsmpmInitBoxSizes[1]
let realBoxSize = [...initBoxSize];
mlsmpmSimulator.reset(mlsmpmNumParticleParams[1], mlsmpmInitBoxSizes[1])
camera.reset(canvasElement, initDistance, [initBoxSize[0] / 2, initBoxSize[1] / 4, initBoxSize[2] / 2],
mlsmpmFov, mlsmpmZoomRate)
smallValue.textContent = "40,000"
mediumValue.textContent = "70,000"
largeValue.textContent = "120,000"
veryLargeValue.textContent = "200,000"
let sphereRenderFl = false
let sphFl = false
let boxWidthRatio = 1.
console.log("simulation start")
async function frame() {
const start = performance.now();
if (simulationModePressed) {
if (simulationModePressedButton == "mlsmpm") {
sphFl = false
smallValue.textContent = "40,000"
mediumValue.textContent = "70,000"
largeValue.textContent = "120,000"
veryLargeValue.textContent = "200,000"
} else {
sphFl = true
smallValue.textContent = "10,000"
mediumValue.textContent = "20,000"
largeValue.textContent = "30,000"
veryLargeValue.textContent = "40,000"
}
simulationModePressed = false
numberButtonPressed = true
}
if (numberButtonPressed) {
const paramsIdx = parseInt(numberButtonPressedButton)
if (sphFl) {
initBoxSize = sphInitBoxSizes[paramsIdx]
sphSimulator.reset(sphNumParticleParams[paramsIdx], initBoxSize)
camera.reset(canvasElement, sphInitDistances[paramsIdx], [0, -initBoxSize[1] + 0.1, 0],
sphFov, sphZoomRate)
} else {
initBoxSize = mlsmpmInitBoxSizes[paramsIdx]
mlsmpmSimulator.reset(mlsmpmNumParticleParams[paramsIdx], initBoxSize)
camera.reset(canvasElement, mlsmpmInitDistances[paramsIdx], [initBoxSize[0] / 2, initBoxSize[1] / 4, initBoxSize[2] / 2],
mlsmpmFov, mlsmpmZoomRate)
}
realBoxSize = [...initBoxSize]
let slider = document.getElementById("slider") as HTMLInputElement
slider.value = "100"
numberButtonPressed = false
}
// ボックスサイズの変更
const slider = document.getElementById("slider") as HTMLInputElement
const particle = document.getElementById("particle") as HTMLInputElement
sphereRenderFl = particle.checked
let curBoxWidthRatio = parseInt(slider.value) / 200 + 0.5
const minClosingSpeed = sphFl ? -0.015 : -0.007
const dVal = Math.max(curBoxWidthRatio - boxWidthRatio, minClosingSpeed)
boxWidthRatio += dVal
// 行列の更新
realBoxSize[2] = initBoxSize[2] * boxWidthRatio
if (sphFl) {
sphSimulator.changeBoxSize(realBoxSize)
} else {
mlsmpmSimulator.changeBoxSize(realBoxSize)
}
device.queue.writeBuffer(renderUniformBuffer, 0, renderUniformsValues)
const commandEncoder = device.createCommandEncoder()
// 計算のためのパス
if (sphFl) {
sphSimulator.execute(commandEncoder)
sphRenderer.execute(context, commandEncoder, sphSimulator.numParticles, sphereRenderFl)
} else {
mlsmpmSimulator.execute(commandEncoder)
mlsmpmRenderer.execute(context, commandEncoder, mlsmpmSimulator.numParticles, sphereRenderFl)
}
device.queue.submit([commandEncoder.finish()])
const end = performance.now();
// console.log(`js: ${(end - start).toFixed(1)}ms`);
requestAnimationFrame(frame)
}
requestAnimationFrame(frame)
}
main()