Compare commits
2 Commits
e410168923
...
b198b61bb7
| Author | SHA1 | Date | |
|---|---|---|---|
| b198b61bb7 | |||
| 5386b8bee9 |
270
flat.js
Normal file
270
flat.js
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
import { getPath } from "./common.js";
|
||||||
|
|
||||||
|
function test(position)
|
||||||
|
{
|
||||||
|
const x = position[0] * 4 * Math.PI;
|
||||||
|
const y = position[1] * 4 * Math.PI;
|
||||||
|
return Math.cos(y - 3 + Math.sin(x)) * 0.5 + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
function length(vector)
|
||||||
|
{
|
||||||
|
return Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sub(a, b)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
a[0] - b[0],
|
||||||
|
a[1] - b[1],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function smoothing(radius, distance)
|
||||||
|
{
|
||||||
|
const volume = Math.PI * Math.pow(radius, 8) / 4;
|
||||||
|
const value = Math.max(0, radius * radius - distance * distance);
|
||||||
|
return value * value * value / volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlatRenderer {
|
||||||
|
createParticles(device)
|
||||||
|
{
|
||||||
|
this.maxDim = 32;
|
||||||
|
this.maxParticles = this.maxDim * this.maxDim;
|
||||||
|
this.particleStride = 4 * 2; // in elements
|
||||||
|
this.particles = new Float32Array(this.maxParticles * this.particleStride);
|
||||||
|
this.particleBuffers = [];
|
||||||
|
this.particleBindGroups = [];
|
||||||
|
|
||||||
|
this.particleConfiguration = new Float32Array(3 * 4);
|
||||||
|
this.particleConfigurationBuffers = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
const buffer = device.createBuffer({
|
||||||
|
label: `particle buffer ${i}`,
|
||||||
|
size: this.particles.byteLength,
|
||||||
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
||||||
|
});
|
||||||
|
this.particleBuffers.push(buffer);
|
||||||
|
|
||||||
|
const configBuffer = device.createBuffer({
|
||||||
|
label: `particle configuration buffer ${i}`,
|
||||||
|
size: this.particleConfiguration.byteLength,
|
||||||
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||||
|
});
|
||||||
|
this.particleConfigurationBuffers.push(configBuffer);
|
||||||
|
|
||||||
|
const bindGroup = device.createBindGroup({
|
||||||
|
label: `particle buffer bind group ${i}`,
|
||||||
|
layout: this.bindGroupLayout.particle,
|
||||||
|
entries: [{
|
||||||
|
binding: 0,
|
||||||
|
resource: { buffer: configBuffer },
|
||||||
|
}, {
|
||||||
|
binding: 1,
|
||||||
|
resource: { buffer: buffer },
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
this.particleBindGroups.push(bindGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let y = 0; y < this.maxDim; y++) {
|
||||||
|
for (let x = 0; x < this.maxDim; x++) {
|
||||||
|
const index = y * this.maxDim + x;
|
||||||
|
//this.particles[index * this.particleStride + 0] = (x / this.maxDim) + 0.5 / this.maxDim;
|
||||||
|
//this.particles[index * this.particleStride + 1] = (y / this.maxDim) + 0.5 / this.maxDim;
|
||||||
|
// position
|
||||||
|
const position = [Math.random(), Math.random()];
|
||||||
|
this.particles[index * this.particleStride + 0] = position[0];
|
||||||
|
this.particles[index * this.particleStride + 1] = position[1];
|
||||||
|
this.particles[index * this.particleStride + 2] = 0.0;
|
||||||
|
this.particles[index * this.particleStride + 3] = 1.0;
|
||||||
|
// value.x
|
||||||
|
this.particles[index * this.particleStride + 4] = test(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(device, canvasFormat, viewUniformBuffer, shaderModule)
|
||||||
|
{
|
||||||
|
const label = "flat";
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// buffer
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const array = new Uint16Array(6);
|
||||||
|
array[0] = 0;
|
||||||
|
array[1] = 1;
|
||||||
|
array[2] = 2;
|
||||||
|
array[3] = 0;
|
||||||
|
array[4] = 3;
|
||||||
|
array[5] = 1;
|
||||||
|
|
||||||
|
this.buffer = device.createBuffer({
|
||||||
|
label: `${label} renderer buffer`,
|
||||||
|
size: array.byteLength,
|
||||||
|
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
||||||
|
});
|
||||||
|
|
||||||
|
device.queue.writeBuffer(this.buffer, 0, array, 0, array.length);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// pipeline
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
this.bindGroupLayout = {
|
||||||
|
view: device.createBindGroupLayout({
|
||||||
|
label: `${label} bind group layout view`,
|
||||||
|
entries: [{
|
||||||
|
binding: 0,
|
||||||
|
visibility: GPUShaderStage.VERTEX,
|
||||||
|
buffer: { type: "uniform" }
|
||||||
|
}]
|
||||||
|
}),
|
||||||
|
particle: device.createBindGroupLayout({
|
||||||
|
label: `${label} bind group layout particle`,
|
||||||
|
entries: [{
|
||||||
|
binding: 0,
|
||||||
|
visibility: GPUShaderStage.FRAGMENT,
|
||||||
|
buffer: { type: "uniform" }
|
||||||
|
}, {
|
||||||
|
binding: 1,
|
||||||
|
visibility: GPUShaderStage.FRAGMENT,
|
||||||
|
buffer: { type: "read-only-storage" }
|
||||||
|
}]
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const pipelineLayout = device.createPipelineLayout({
|
||||||
|
label: `${label} pipeline layout`,
|
||||||
|
bindGroupLayouts: [
|
||||||
|
this.bindGroupLayout.view,
|
||||||
|
this.bindGroupLayout.particle
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renderPipeline = device.createRenderPipeline({
|
||||||
|
label: `${label} pipeline`,
|
||||||
|
layout: pipelineLayout,
|
||||||
|
vertex: {
|
||||||
|
module: shaderModule,
|
||||||
|
entryPoint: "vertexMain",
|
||||||
|
},
|
||||||
|
fragment: {
|
||||||
|
module: shaderModule,
|
||||||
|
entryPoint: "fragmentMain",
|
||||||
|
targets: [{
|
||||||
|
format: canvasFormat,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
primitive: {
|
||||||
|
topology: "triangle-list",
|
||||||
|
},
|
||||||
|
depthStencil: {
|
||||||
|
depthWriteEnabled: true,
|
||||||
|
depthCompare: "less",
|
||||||
|
format: "depth24plus",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// particles
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
this.createParticles(device);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// bind group
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
this.bindGroups = [
|
||||||
|
device.createBindGroup({
|
||||||
|
label: `${label} bind group`,
|
||||||
|
layout: this.bindGroupLayout.view,
|
||||||
|
entries: [{
|
||||||
|
binding: 0,
|
||||||
|
resource: { buffer: viewUniformBuffer },
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
positionDensity(position, circleRadius, particleCount)
|
||||||
|
{
|
||||||
|
var density = 0.0;
|
||||||
|
for (var i = 0; i < particleCount; i++) {
|
||||||
|
const particlePosition = [this.particles[i * this.particleStride + 0],
|
||||||
|
this.particles[i * this.particleStride + 1]];
|
||||||
|
const distance = length(sub(position, particlePosition));
|
||||||
|
const influence = smoothing(circleRadius, distance);
|
||||||
|
density += influence;
|
||||||
|
}
|
||||||
|
return density;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateParticles(device, frameNumber, configuration)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < configuration.particleCount; i++) {
|
||||||
|
const position = [this.particles[i * this.particleStride + 0],
|
||||||
|
this.particles[i * this.particleStride + 1]];
|
||||||
|
const density = this.positionDensity(position,
|
||||||
|
configuration.circleRadius,
|
||||||
|
configuration.particleCount);
|
||||||
|
// value.y
|
||||||
|
this.particles[i * this.particleStride + 5] = density;
|
||||||
|
}
|
||||||
|
|
||||||
|
device.queue.writeBuffer(this.particleBuffers[frameNumber], 0,
|
||||||
|
this.particles, 0, this.particles.length);
|
||||||
|
|
||||||
|
const PARTICLE = 0;
|
||||||
|
const CIRCLE = 1;
|
||||||
|
const MOUSE = 2;
|
||||||
|
|
||||||
|
const config = this.particleConfiguration;
|
||||||
|
config[PARTICLE * 4 + 0] = configuration.particleCount;
|
||||||
|
config[PARTICLE * 4 + 1] = configuration.particleSize;
|
||||||
|
config[PARTICLE * 4 + 2] = configuration.particleMass1;
|
||||||
|
config[PARTICLE * 4 + 3] = configuration.particleMass2;
|
||||||
|
|
||||||
|
config[CIRCLE * 4 + 0] = configuration.circleRadius;
|
||||||
|
config[CIRCLE * 4 + 1] = configuration.circleThickness;
|
||||||
|
config[CIRCLE * 4 + 2] = configuration.testIntensity;
|
||||||
|
config[CIRCLE * 4 + 3] = configuration.mass12Mix;
|
||||||
|
|
||||||
|
config[MOUSE * 4 + 0] = configuration.mousePosition[0];
|
||||||
|
config[MOUSE * 4 + 1] = configuration.mousePosition[1];
|
||||||
|
config[MOUSE * 4 + 2] = configuration.mousePosition[2];
|
||||||
|
config[MOUSE * 4 + 3] = configuration.mousePosition[3];
|
||||||
|
|
||||||
|
device.queue.writeBuffer(this.particleConfigurationBuffers[frameNumber], 0,
|
||||||
|
this.particleConfiguration, 0, this.particleConfiguration.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
render(device, renderPass, frameNumber, configuration)
|
||||||
|
{
|
||||||
|
this.updateParticles(device, frameNumber, configuration);
|
||||||
|
|
||||||
|
renderPass.setPipeline(this.renderPipeline);
|
||||||
|
renderPass.setIndexBuffer(this.buffer, "uint16");
|
||||||
|
renderPass.setBindGroup(0, this.bindGroups[0]);
|
||||||
|
renderPass.setBindGroup(1, this.particleBindGroups[frameNumber]);
|
||||||
|
renderPass.drawIndexed(6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadFlat(device, canvasFormat, viewUniformBuffer)
|
||||||
|
{
|
||||||
|
const flatWgsl = await getPath("flat.wgsl");
|
||||||
|
const shaderModule = device.createShaderModule({
|
||||||
|
label: "flag shader",
|
||||||
|
code: flatWgsl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderer = new FlatRenderer(device, canvasFormat, viewUniformBuffer, shaderModule);
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { loadFlat };
|
||||||
124
flat.wgsl
Normal file
124
flat.wgsl
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
struct Configuration {
|
||||||
|
viewProj: mat4x4f,
|
||||||
|
lightViewProj: mat4x4f,
|
||||||
|
lightPosition: vec4f,
|
||||||
|
eyePosition: vec4f,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Particle {
|
||||||
|
position: vec4f,
|
||||||
|
value: vec4f,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParticleConfiguration {
|
||||||
|
particle: vec4f,
|
||||||
|
circle: vec4f,
|
||||||
|
mouse: vec4f,
|
||||||
|
};
|
||||||
|
|
||||||
|
@group(0) @binding(0) var<uniform> config: Configuration;
|
||||||
|
@group(1) @binding(0) var<uniform> particleConfiguration: ParticleConfiguration;
|
||||||
|
@group(1) @binding(1) var<storage> particle: array<Particle>;
|
||||||
|
|
||||||
|
struct VertexInput {
|
||||||
|
@builtin(vertex_index) vertex_index: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position: vec4f,
|
||||||
|
@location(0) texture: vec2f,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vertices = array(
|
||||||
|
vec2f(1.0, 0.0),
|
||||||
|
vec2f(0.0, 1.0),
|
||||||
|
vec2f(0.0, 0.0),
|
||||||
|
vec2f(1.0, 1.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vertexMain(input: VertexInput) -> VertexOutput
|
||||||
|
{
|
||||||
|
let texture = vertices[input.vertex_index];
|
||||||
|
|
||||||
|
var output: VertexOutput;
|
||||||
|
output.position = vec4f(texture * 2.0 - 1.0, 0.0, 1.0);
|
||||||
|
output.texture = texture;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pi: f32 = 3.14159274101257324219;
|
||||||
|
|
||||||
|
fn smoothing(radius: f32, distance: f32) -> f32
|
||||||
|
{
|
||||||
|
let volume = pi * pow(radius, 8) / 4;
|
||||||
|
let value = max(0, radius * radius - distance * distance);
|
||||||
|
return value * value * value / volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test(position: vec2f) -> f32
|
||||||
|
{
|
||||||
|
let x = position.x * 4 * pi;
|
||||||
|
let y = position.y * 4 * pi;
|
||||||
|
return cos(y - 3 + sin(x)) * 0.5 + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn positionDensity(position: vec2f, circleRadius: f32, particleCount: u32) -> f32
|
||||||
|
{
|
||||||
|
var density: f32 = 0.0;
|
||||||
|
for (var i: u32 = 0; i < u32(particleCount); i++) {
|
||||||
|
let distance = length(position - particle[i].position.xy);
|
||||||
|
let influence = smoothing(circleRadius, distance);
|
||||||
|
density += influence;
|
||||||
|
}
|
||||||
|
return density;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
|
||||||
|
{
|
||||||
|
var color: vec4f = vec4f(0.0, 0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
let particleCount = u32(particleConfiguration.particle.x);
|
||||||
|
let particleSize = particleConfiguration.particle.y;
|
||||||
|
let particleMass1 = particleConfiguration.particle.z;
|
||||||
|
let particleMass2 = particleConfiguration.particle.w;
|
||||||
|
|
||||||
|
let circleRadius = particleConfiguration.circle.x;
|
||||||
|
let circleThickness = particleConfiguration.circle.y;
|
||||||
|
let testIntensity = particleConfiguration.circle.z;
|
||||||
|
let mass12Mix = particleConfiguration.circle.w;
|
||||||
|
|
||||||
|
let circlePosition = particleConfiguration.mouse;
|
||||||
|
|
||||||
|
var property: f32 = 0.0;
|
||||||
|
|
||||||
|
for (var i: u32 = 0; i < particleCount; i++) {
|
||||||
|
let distance = length(input.texture.xy - particle[i].position.xy);
|
||||||
|
if (distance < particleSize) {
|
||||||
|
color.x = particle[i].value.x;
|
||||||
|
}
|
||||||
|
//let circleDistance = length(input.texture.xy - circlePosition.xy);
|
||||||
|
let influence = smoothing(circleRadius, distance);
|
||||||
|
//let particleDensity = positionDensity(particle[i].position.xy, circleRadius, particleCount);
|
||||||
|
let particleProperty = particle[i].value.x;
|
||||||
|
let particleDensity = particle[i].value.y;
|
||||||
|
|
||||||
|
let p1 = particleProperty * influence;
|
||||||
|
let p2 = p1 / particleDensity;
|
||||||
|
|
||||||
|
property += mix(p1, p2, mass12Mix);
|
||||||
|
}
|
||||||
|
color.z = property * mix(particleMass1, particleMass2, mass12Mix);
|
||||||
|
|
||||||
|
if (circlePosition.w != 0) {
|
||||||
|
let thickness = abs(length(input.texture.xy - circlePosition.xy) - circleRadius);
|
||||||
|
if (thickness < circleThickness) {
|
||||||
|
//color.y = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let testColor = test(input.texture.xy);
|
||||||
|
return vec4(color.xz, testColor * testIntensity, 1.0);
|
||||||
|
//return vec4(color.x, 0, 0, 1.0);
|
||||||
|
}
|
||||||
29
index.html
29
index.html
@ -11,6 +11,9 @@
|
|||||||
div, span {
|
div, span {
|
||||||
font: 0.8rem sans-serif;
|
font: 0.8rem sans-serif;
|
||||||
}
|
}
|
||||||
|
span {
|
||||||
|
padding-bottom: 0.1rem;
|
||||||
|
}
|
||||||
select, option {
|
select, option {
|
||||||
font: 0.8rem sans-serif;
|
font: 0.8rem sans-serif;
|
||||||
}
|
}
|
||||||
@ -23,6 +26,12 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
height: 0.9rem;
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
.control-root {
|
.control-root {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -32,13 +41,13 @@
|
|||||||
background: #eee;
|
background: #eee;
|
||||||
}
|
}
|
||||||
.control-main {
|
.control-main {
|
||||||
width: 250px;
|
width: 310px;
|
||||||
float: right;
|
float: right;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
background: #444;
|
background: #444;
|
||||||
}
|
}
|
||||||
.label {
|
.label {
|
||||||
width: calc(40% - 0.3rem);
|
width: calc(30% - 0.3rem);
|
||||||
float: left;
|
float: left;
|
||||||
clear: left;
|
clear: left;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -46,7 +55,7 @@
|
|||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
.value {
|
.value {
|
||||||
width: calc(60% - 0.2rem);
|
width: calc(70% - 0.2rem);
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: 0.1rem;
|
margin-top: 0.1rem;
|
||||||
margin-right: 0.2rem;
|
margin-right: 0.2rem;
|
||||||
@ -57,9 +66,9 @@
|
|||||||
.row {
|
.row {
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
}
|
}
|
||||||
code {
|
.code, code {
|
||||||
font: 0.95rem monospace;
|
font: 0.85rem monospace;
|
||||||
padding-top: 0.1rem;
|
padding-top: 0.15rem;
|
||||||
}
|
}
|
||||||
.float3 {
|
.float3 {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
@ -71,6 +80,9 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.range-span {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src="index.js" type="module"></script>
|
<script src="index.js" type="module"></script>
|
||||||
<!--<script src="index2.js" type="module"></script>-->
|
<!--<script src="index2.js" type="module"></script>-->
|
||||||
@ -78,7 +90,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<canvas></canvas>
|
<canvas></canvas>
|
||||||
<div class="control-root">
|
<div class="control-root">
|
||||||
<div class="control-main">
|
<div class="control-main" id="control-main">
|
||||||
<!--
|
<!--
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="label">model</span>
|
<span class="label">model</span>
|
||||||
@ -92,6 +104,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
|
<!--
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="label">eye</span>
|
<span class="label">eye</span>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
@ -107,6 +120,7 @@
|
|||||||
<code class="float2" id="pitch">0.0</code>
|
<code class="float2" id="pitch">0.0</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
<!--
|
<!--
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="label">test2</span>
|
<span class="label">test2</span>
|
||||||
@ -116,6 +130,5 @@
|
|||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
147
index.js
147
index.js
@ -1,6 +1,7 @@
|
|||||||
import { getPath } from "./common.js";
|
import { getPath } from "./common.js";
|
||||||
import { loadGltf } from "./index2.js";
|
import { loadGltf } from "./index2.js";
|
||||||
import { loadLight } from "./light.js";
|
import { loadLight } from "./light.js";
|
||||||
|
import { loadFlat } from "./flat.js";
|
||||||
|
|
||||||
if (!navigator.gpu) {
|
if (!navigator.gpu) {
|
||||||
throw new Error("WebGPU not supported on this browser.");
|
throw new Error("WebGPU not supported on this browser.");
|
||||||
@ -64,9 +65,9 @@ const eyeValueZ = document.getElementById("eye-value-z");
|
|||||||
const yawValue = document.getElementById("yaw");
|
const yawValue = document.getElementById("yaw");
|
||||||
const pitchValue = document.getElementById("pitch");
|
const pitchValue = document.getElementById("pitch");
|
||||||
|
|
||||||
function recreateDepth()
|
function recreateDepth(force)
|
||||||
{
|
{
|
||||||
if (canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height) {
|
if (canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height || force === true) {
|
||||||
canvas.width = canvas.clientWidth;
|
canvas.width = canvas.clientWidth;
|
||||||
canvas.height = canvas.clientHeight;
|
canvas.height = canvas.clientHeight;
|
||||||
|
|
||||||
@ -95,8 +96,9 @@ const cameraStateAddress = module.instance.exports.mem_alloc(cameraStateSize);
|
|||||||
|
|
||||||
module.instance.exports.camera_init(cameraStateAddress);
|
module.instance.exports.camera_init(cameraStateAddress);
|
||||||
|
|
||||||
const gltfRenderer = await loadGltf(device, canvasFormat, viewUniformBuffer, memory, module);
|
//const gltfRenderer = await loadGltf(device, canvasFormat, viewUniformBuffer, memory, module);
|
||||||
const lightRenderer = await loadLight(device, canvasFormat, viewUniformBuffer);
|
//const lightRenderer = await loadLight(device, canvasFormat, viewUniformBuffer);
|
||||||
|
const flatRenderer = await loadFlat(device, canvasFormat, viewUniformBuffer);
|
||||||
|
|
||||||
|
|
||||||
const KEY = {
|
const KEY = {
|
||||||
@ -188,21 +190,140 @@ function updateView()
|
|||||||
viewUniformBufferSize);
|
viewUniformBufferSize);
|
||||||
|
|
||||||
const cameraStateF32 = new Float32Array(memory.buffer, cameraStateAddress, cameraStateSize);
|
const cameraStateF32 = new Float32Array(memory.buffer, cameraStateAddress, cameraStateSize);
|
||||||
|
/*
|
||||||
eyeValueX.innerHTML = cameraStateF32[0].toFixed(1);
|
eyeValueX.innerHTML = cameraStateF32[0].toFixed(1);
|
||||||
eyeValueY.innerHTML = cameraStateF32[1].toFixed(1);
|
eyeValueY.innerHTML = cameraStateF32[1].toFixed(1);
|
||||||
eyeValueZ.innerHTML = cameraStateF32[2].toFixed(1);
|
eyeValueZ.innerHTML = cameraStateF32[2].toFixed(1);
|
||||||
yawValue.innerHTML = cameraStateF32[9].toFixed(2);
|
yawValue.innerHTML = cameraStateF32[9].toFixed(2);
|
||||||
pitchValue.innerHTML = cameraStateF32[10].toFixed(2);
|
pitchValue.innerHTML = cameraStateF32[10].toFixed(2);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const controlMain = document.getElementById("control-main");
|
||||||
|
|
||||||
|
function remap(low1, high1, low2, high2, value)
|
||||||
|
{
|
||||||
|
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sliderDefaults = {
|
||||||
|
"circle radius": 385,
|
||||||
|
"circle thickness": 284,
|
||||||
|
"mass12 mix": 1,
|
||||||
|
"particle count": 360,
|
||||||
|
"particle mass1": 126,
|
||||||
|
"particle mass2": 417,
|
||||||
|
"particle size": 51,
|
||||||
|
"test intensity": 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Slider {
|
||||||
|
constructor(label, low, high, max = 1024)
|
||||||
|
{
|
||||||
|
const labelE = document.createElement("span");
|
||||||
|
labelE.className = "label";
|
||||||
|
labelE.innerHTML = label;
|
||||||
|
|
||||||
|
const valueE = document.createElement("div");
|
||||||
|
valueE.className = "value";
|
||||||
|
|
||||||
|
const inputE = document.createElement("input");
|
||||||
|
inputE.className = "range";
|
||||||
|
inputE.min = 0;
|
||||||
|
inputE.max = max;
|
||||||
|
inputE.type = "range";
|
||||||
|
const spanE = document.createElement("span");
|
||||||
|
spanE.className = "range-span code";
|
||||||
|
|
||||||
|
valueE.appendChild(inputE);
|
||||||
|
valueE.appendChild(spanE);
|
||||||
|
|
||||||
|
controlMain.appendChild(labelE);
|
||||||
|
controlMain.appendChild(valueE);
|
||||||
|
|
||||||
|
this.label = label;
|
||||||
|
this.input = inputE;
|
||||||
|
this.span = spanE;
|
||||||
|
this.low = low;
|
||||||
|
this.high = high;
|
||||||
|
this.lastValue = undefined;
|
||||||
|
|
||||||
|
const storageValue = localStorage.getItem(this.label);
|
||||||
|
if (storageValue !== null) {
|
||||||
|
this.input.value = storageValue;
|
||||||
|
} else if (this.label in sliderDefaults) {
|
||||||
|
this.input.value = sliderDefaults[this.label];
|
||||||
|
}
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
|
{
|
||||||
|
const rawValue = parseInt(this.input.value);
|
||||||
|
const value = remap(this.input.min, this.input.max, this.low, this.high, this.input.value);
|
||||||
|
if (rawValue !== this.lastValue) {
|
||||||
|
this.lastValue = rawValue;
|
||||||
|
localStorage.setItem(this.label, rawValue);
|
||||||
|
}
|
||||||
|
if (this.high > 10) {
|
||||||
|
this.span.innerHTML = value.toFixed(0);
|
||||||
|
} else {
|
||||||
|
this.span.innerHTML = value.toFixed(5);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sliders = {
|
||||||
|
particleSize: new Slider("particle size", 0.0, 0.05),
|
||||||
|
particleMass1: new Slider("particle mass1", 0.0, 0.01),
|
||||||
|
particleMass2: new Slider("particle mass2", 0.0, 1.0),
|
||||||
|
particleCount: new Slider("particle count", 0.0, 1024.0),
|
||||||
|
circleRadius: new Slider("circle radius", 0.0, 0.5),
|
||||||
|
circleThickness: new Slider("circle thickness", 0.0, 0.01),
|
||||||
|
testIntensity: new Slider("test intensity", 0.0, 2.0),
|
||||||
|
mass12Mix: new Slider("mass12 mix", 0.0, 1.0, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mousePosition = new Float32Array([0, 0, 0, 0]);
|
||||||
|
|
||||||
|
function updateSliders()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
mousePosition: mousePosition,
|
||||||
|
particleSize: sliders.particleSize.update(),
|
||||||
|
particleMass1: sliders.particleMass1.update(),
|
||||||
|
particleMass2: sliders.particleMass2.update(),
|
||||||
|
particleCount: sliders.particleCount.update(),
|
||||||
|
circleRadius: sliders.circleRadius.update(),
|
||||||
|
circleThickness: sliders.circleThickness.update(),
|
||||||
|
testIntensity: sliders.testIntensity.update(),
|
||||||
|
mass12Mix: sliders.mass12Mix.update(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClick(e)
|
||||||
|
{
|
||||||
|
mousePosition[0] = e.clientX / canvas.width;
|
||||||
|
mousePosition[1] = 1.0 - (e.clientY / canvas.height);
|
||||||
|
mousePosition[2] = 0;
|
||||||
|
mousePosition[3] = 1;
|
||||||
|
}
|
||||||
|
canvas.addEventListener("click", onClick);
|
||||||
|
|
||||||
|
var canRender = false;
|
||||||
|
var frameNumber = 0;
|
||||||
function render2()
|
function render2()
|
||||||
{
|
{
|
||||||
recreateDepth();
|
if (canRender === true) {
|
||||||
|
const configuration = updateSliders();
|
||||||
|
|
||||||
|
canRender = false;
|
||||||
|
recreateDepth(false);
|
||||||
const colorView = context.getCurrentTexture().createView();
|
const colorView = context.getCurrentTexture().createView();
|
||||||
|
|
||||||
updateView();
|
updateView();
|
||||||
|
|
||||||
{
|
|
||||||
const encoder = device.createCommandEncoder();
|
const encoder = device.createCommandEncoder();
|
||||||
const renderPass = encoder.beginRenderPass({
|
const renderPass = encoder.beginRenderPass({
|
||||||
colorAttachments: [{
|
colorAttachments: [{
|
||||||
@ -219,15 +340,25 @@ function render2()
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
gltfRenderer.render(renderPass);
|
//gltfRenderer.render(renderPass);
|
||||||
lightRenderer.render(renderPass);
|
//lightRenderer.render(renderPass);
|
||||||
|
flatRenderer.render(device, renderPass, frameNumber, configuration);
|
||||||
|
|
||||||
renderPass.end();
|
renderPass.end();
|
||||||
|
|
||||||
const commandBuffer = encoder.finish();
|
const commandBuffer = encoder.finish();
|
||||||
device.queue.submit([commandBuffer]);
|
device.queue.submit([commandBuffer]);
|
||||||
|
|
||||||
|
frameNumber = (frameNumber + 1) % 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(render2);
|
requestAnimationFrame(render2);
|
||||||
}
|
}
|
||||||
|
function setCanRender()
|
||||||
|
{
|
||||||
|
canRender = true;
|
||||||
|
}
|
||||||
|
setInterval(setCanRender, 66.67); // 15 fps
|
||||||
|
|
||||||
|
recreateDepth(true);
|
||||||
requestAnimationFrame(render2);
|
requestAnimationFrame(render2);
|
||||||
|
|||||||
@ -148,5 +148,6 @@ void camera_view_projection(CameraState * camera_state,
|
|||||||
XMStoreFloat4x4(&view_state->viewProj, view_projection);
|
XMStoreFloat4x4(&view_state->viewProj, view_projection);
|
||||||
XMStoreFloat4x4(&view_state->lightViewProj, light_world * view_projection);
|
XMStoreFloat4x4(&view_state->lightViewProj, light_world * view_projection);
|
||||||
XMStoreFloat4(&view_state->lightPosition, light_position);
|
XMStoreFloat4(&view_state->lightPosition, light_position);
|
||||||
XMStoreFloat4(&view_state->eyePosition, XMLoadFloat3(&camera_state->eye));
|
XMVECTOR eye = XMLoadFloat3(&camera_state->eye);
|
||||||
|
XMStoreFloat4(&view_state->eyePosition, eye);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user