Orbit Camera
This commit is contained in:
parent
4803f11739
commit
69e2be5778
3
index.ts
3
index.ts
|
|
@ -3,7 +3,6 @@ import index from './index.html';
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
routes: {
|
routes: {
|
||||||
'/': index
|
'/': index
|
||||||
},
|
}
|
||||||
development: false
|
|
||||||
});
|
});
|
||||||
console.log(server.url.href);
|
console.log(server.url.href);
|
||||||
348
main.ts
348
main.ts
|
|
@ -5,33 +5,31 @@ import { Stats } from 'stats.ts';
|
||||||
const shaderCode = `
|
const shaderCode = `
|
||||||
struct VertexOut {
|
struct VertexOut {
|
||||||
@builtin(position) position: vec4f,
|
@builtin(position) position: vec4f,
|
||||||
@location(0) color: vec4f,
|
@location(0) color: vec4f
|
||||||
@location(1) uv: vec2f
|
|
||||||
}
|
}
|
||||||
struct Star {
|
struct Star {
|
||||||
pos: vec3f,
|
pos: vec3f,
|
||||||
lum: f32
|
lum: f32
|
||||||
}
|
}
|
||||||
|
struct Uniform {
|
||||||
|
_View: mat4x4f,
|
||||||
|
_Projection: mat4x4f
|
||||||
|
}
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> _ViewProjectionMatrix: mat4x4f;
|
@group(0) @binding(0) var<uniform> uniforms: Uniform;
|
||||||
@group(0) @binding(1) var<storage, read> stars: array<Star>;
|
@group(0) @binding(1) var<storage, read> stars: array<Star>;
|
||||||
|
|
||||||
const quad: array<vec4f, 4> = array(
|
|
||||||
vec4f(0, 0, 0, 0),
|
|
||||||
vec4f(0, 1, 0, 0),
|
|
||||||
vec4f(1, 0, 0, 0),
|
|
||||||
vec4f(1, 1, 0, 0),
|
|
||||||
);
|
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertex_main(@builtin(vertex_index) vertex_index: u32, @builtin(instance_index) instance_index: u32) -> VertexOut
|
fn vertex_main(@location(0) position: vec3f, @builtin(instance_index) instance_index: u32) -> VertexOut
|
||||||
{
|
{
|
||||||
var star: Star = stars[instance_index];
|
var star: Star = stars[instance_index];
|
||||||
var output: VertexOut;
|
var output: VertexOut;
|
||||||
|
|
||||||
output.position = vec4f(star.pos, 0) + quad[vertex_index];
|
var star_pos = uniforms._View * vec4f(star.pos, 1.0);
|
||||||
|
var billboard_pos = star_pos + vec4f(position, 0.0);
|
||||||
|
|
||||||
|
output.position = uniforms._Projection * billboard_pos;
|
||||||
output.color = vec4(star.lum, star.lum, star.lum, 1);
|
output.color = vec4(star.lum, star.lum, star.lum, 1);
|
||||||
output.uv = vec2f(quad[vertex_index].x, quad[vertex_index].y);
|
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +37,9 @@ fn vertex_main(@builtin(vertex_index) vertex_index: u32, @builtin(instance_index
|
||||||
@fragment
|
@fragment
|
||||||
fn fragment_main(fragData: VertexOut) -> @location(0) vec4f
|
fn fragment_main(fragData: VertexOut) -> @location(0) vec4f
|
||||||
{
|
{
|
||||||
return fragData.color * distance(fragData.uv, vec2f(0.5, 0.5));
|
//var dist = distance(fragData.uv, vec2f(0.5, 0.5));
|
||||||
|
//return vec4f(1 - dist, 1 - dist, 1 - dist, dist);
|
||||||
|
return vec4f(1.0, 1.0, 1.0, 1.0);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -60,6 +60,11 @@ type Constellation = {
|
||||||
description: string | null;
|
description: string | null;
|
||||||
semantics: string[] | null;
|
semantics: string[] | null;
|
||||||
};
|
};
|
||||||
|
type Position = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
}
|
||||||
|
|
||||||
class Matrix4x4
|
class Matrix4x4
|
||||||
{
|
{
|
||||||
|
|
@ -136,6 +141,30 @@ class Matrix4x4
|
||||||
return mat;
|
return mat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
identity(value: number = 1): this
|
||||||
|
{
|
||||||
|
this._view[0] = value;
|
||||||
|
this._view[1] = 0;
|
||||||
|
this._view[2] = 0;
|
||||||
|
this._view[3] = 0;
|
||||||
|
|
||||||
|
this._view[4] = 0;
|
||||||
|
this._view[5] = value;
|
||||||
|
this._view[6] = 0;
|
||||||
|
this._view[7] = 0;
|
||||||
|
|
||||||
|
this._view[8] = 0;
|
||||||
|
this._view[9] = 0;
|
||||||
|
this._view[10] = value;
|
||||||
|
this._view[11] = 0;
|
||||||
|
|
||||||
|
this._view[12] = 0;
|
||||||
|
this._view[13] = 0;
|
||||||
|
this._view[14] = 0;
|
||||||
|
this._view[15] = value;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
translate(x: number, y: number, z: number): this
|
translate(x: number, y: number, z: number): this
|
||||||
{
|
{
|
||||||
return this.multiply(this, Matrix4x4.translation(x, y, z));
|
return this.multiply(this, Matrix4x4.translation(x, y, z));
|
||||||
|
|
@ -214,7 +243,7 @@ class Matrix4x4
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
inverse()
|
invert()
|
||||||
{
|
{
|
||||||
const m00 = this._view[0 * 4 + 0]!;
|
const m00 = this._view[0 * 4 + 0]!;
|
||||||
const m01 = this._view[0 * 4 + 1]!;
|
const m01 = this._view[0 * 4 + 1]!;
|
||||||
|
|
@ -292,43 +321,67 @@ class Matrix4x4
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
camera(transform: {
|
lookat(eye: Position, target: Position): this
|
||||||
position: {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
z: number;
|
|
||||||
},
|
|
||||||
yaw: number,
|
|
||||||
pitch: number
|
|
||||||
}): this
|
|
||||||
{
|
{
|
||||||
const cosPitch = Math.cos(transform.pitch);
|
const forward = { //zAxis
|
||||||
const sinPitch = Math.sin(transform.pitch);
|
x: eye.x - target.x,
|
||||||
const cosYaw = Math.cos(transform.yaw);
|
y: eye.y - target.y,
|
||||||
const sinYaw = Math.sin(transform.yaw);
|
z: eye.z - target.z
|
||||||
|
};
|
||||||
|
|
||||||
this._view[0] = cosYaw;
|
// Normalize
|
||||||
this._view[1] = 0;
|
const forwardLen = Math.sqrt(forward.x * forward.x + forward.y * forward.y + forward.z * forward.z);
|
||||||
this._view[2] = -sinYaw;
|
forward.x /= forwardLen;
|
||||||
this._view[3] = 0;
|
forward.y /= forwardLen;
|
||||||
|
forward.z /= forwardLen;
|
||||||
|
|
||||||
this._view[4] = sinYaw * sinPitch;
|
const right = { //xAxis
|
||||||
this._view[5] = cosPitch;
|
x: 1 * forward.z - 0 * forward.y,
|
||||||
this._view[6] = cosYaw * sinPitch;
|
y: 0 * forward.x - 0 * forward.z,
|
||||||
this._view[7] = 0;
|
z: 0 * forward.y - 1 * forward.x
|
||||||
|
};
|
||||||
|
|
||||||
this._view[8] = sinYaw * cosPitch;
|
// Normalize
|
||||||
this._view[9] = -sinPitch;
|
const rightLen = Math.sqrt(right.x * right.x + right.y * right.y + right.z * right.z);
|
||||||
this._view[10] = cosYaw * cosPitch;
|
right.x /= rightLen;
|
||||||
|
right.y /= rightLen;
|
||||||
|
right.z /= rightLen;
|
||||||
|
|
||||||
|
const up = { //yAxis
|
||||||
|
x: forward.y * right.z - forward.z * right.y,
|
||||||
|
y: forward.z * right.x - forward.x * right.z,
|
||||||
|
z: forward.x * right.y - forward.y * right.x
|
||||||
|
};
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
const upLen = Math.sqrt(up.x * up.x + up.y * up.y + up.z * up.z);
|
||||||
|
up.x /= upLen;
|
||||||
|
up.y /= upLen;
|
||||||
|
up.z /= upLen;
|
||||||
|
|
||||||
|
this._view[ 0] = right.x;
|
||||||
|
this._view[ 1] = right.y;
|
||||||
|
this._view[ 2] = right.z;
|
||||||
|
this._view[ 3] = 0;
|
||||||
|
|
||||||
|
this._view[ 4] = up.x;
|
||||||
|
this._view[ 5] = up.y;
|
||||||
|
this._view[ 6] = up.z;
|
||||||
|
this._view[ 7] = 0;
|
||||||
|
|
||||||
|
this._view[ 8] = forward.x;
|
||||||
|
this._view[ 9] = forward.y;
|
||||||
|
this._view[10] = forward.z;
|
||||||
this._view[11] = 0;
|
this._view[11] = 0;
|
||||||
|
|
||||||
this._view[12] = transform.position.x;
|
this._view[12] = eye.x;
|
||||||
this._view[13] = transform.position.y;
|
this._view[13] = eye.y;
|
||||||
this._view[14] = transform.position.z;
|
this._view[14] = eye.z;
|
||||||
this._view[15] = 1;
|
this._view[15] = 1;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
perspective(aspect: number, fovY: number, near: number, far: number): this
|
perspective(aspect: number, fovY: number, near: number, far: number): this
|
||||||
{
|
{
|
||||||
const f = Math.tan(Math.PI * 0.5 - 0.5 * fovY);
|
const f = Math.tan(Math.PI * 0.5 - 0.5 * fovY);
|
||||||
|
|
@ -357,23 +410,120 @@ class Matrix4x4
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(buffer: ArrayBuffer, offset?: number)
|
|
||||||
{
|
|
||||||
new Float32Array(buffer).set(this._view, offset);
|
|
||||||
}
|
|
||||||
get view()
|
get view()
|
||||||
{
|
{
|
||||||
return this._view;
|
return this._view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class Inputs
|
||||||
|
{
|
||||||
|
static inputs: Record<string, boolean> = {};
|
||||||
|
private static _movementX = 0;
|
||||||
|
private static _movementY = 0;
|
||||||
|
private static _zoom = 0;
|
||||||
|
private static _dragging = false;
|
||||||
|
static init(eventTarget: HTMLElement)
|
||||||
|
{
|
||||||
|
const dragstart = (e: MouseEvent) => {
|
||||||
|
if(e.buttons & 1)
|
||||||
|
{
|
||||||
|
Inputs._dragging = true;
|
||||||
|
window.addEventListener('mousemove', dragmove);
|
||||||
|
window.addEventListener('mouseup', dragend);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const dragmove = (e: MouseEvent) => {
|
||||||
|
Inputs._movementX += e.movementX;
|
||||||
|
Inputs._movementY += e.movementY;
|
||||||
|
};
|
||||||
|
const dragend = () => {
|
||||||
|
Inputs._dragging = false;
|
||||||
|
window.removeEventListener('mousemove', dragmove);
|
||||||
|
window.removeEventListener('mouseup', dragend);
|
||||||
|
};
|
||||||
|
eventTarget.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
|
Inputs.inputs[e.key.toLowerCase()] = true;
|
||||||
|
});
|
||||||
|
eventTarget.addEventListener('keyup', (e: KeyboardEvent) => {
|
||||||
|
Inputs.inputs[e.key.toLowerCase()] = false;
|
||||||
|
});
|
||||||
|
eventTarget.addEventListener('mousedown', dragstart);
|
||||||
|
eventTarget.addEventListener('wheel', (e: WheelEvent) => Inputs._zoom += e.deltaY);
|
||||||
|
}
|
||||||
|
static get dragging()
|
||||||
|
{
|
||||||
|
return Inputs._dragging;
|
||||||
|
}
|
||||||
|
static get movementX()
|
||||||
|
{
|
||||||
|
const movement = Inputs._movementX;
|
||||||
|
Inputs._movementX = 0;
|
||||||
|
return movement;
|
||||||
|
}
|
||||||
|
static get movementY()
|
||||||
|
{
|
||||||
|
const movement = Inputs._movementY;
|
||||||
|
Inputs._movementY = 0;
|
||||||
|
return movement;
|
||||||
|
}
|
||||||
|
static get zoom()
|
||||||
|
{
|
||||||
|
const zoom = Inputs._zoom;
|
||||||
|
Inputs._zoom = 0;
|
||||||
|
return zoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Camera
|
||||||
|
{
|
||||||
|
pitch = 0;
|
||||||
|
yaw = 0;
|
||||||
|
distance = 25
|
||||||
|
view = new Matrix4x4();
|
||||||
|
|
||||||
|
constructor(distance?: number, yaw?: number, pitch?: number)
|
||||||
|
{
|
||||||
|
this.pitch = pitch ?? 0;
|
||||||
|
this.yaw = yaw ?? 0;
|
||||||
|
this.distance = distance ?? 25;
|
||||||
|
}
|
||||||
|
update(elapsed: number, target: GPUBuffer)
|
||||||
|
{
|
||||||
|
elapsed /= 1 << 15;
|
||||||
|
this.yaw -= Inputs.movementX * elapsed;
|
||||||
|
this.pitch += Inputs.movementY * elapsed;
|
||||||
|
|
||||||
|
this.yaw = (this.yaw + Math.PI * 2) % (Math.PI * 2);
|
||||||
|
this.pitch = clamp(this.pitch, -Math.PI / 2, Math.PI / 2);
|
||||||
|
|
||||||
|
const pitchCos = Math.cos(this.pitch), pitchSin = Math.sin(this.pitch);
|
||||||
|
const yawCos = Math.cos(this.yaw), yawSin = Math.sin(this.yaw);
|
||||||
|
|
||||||
|
this.distance = clamp(this.distance + (Inputs.zoom * elapsed * 1000), 1, 10000);
|
||||||
|
|
||||||
|
const x = this.distance * yawSin * pitchCos;
|
||||||
|
const y = this.distance * yawSin * pitchSin;
|
||||||
|
const z = this.distance * yawCos;
|
||||||
|
|
||||||
|
device!.queue.writeBuffer(target, 0, this.view.lookat({ x, y, z }, { x: 0, y: 0, z: 0 }).view.buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const stars: Star[] = untypedStars;
|
const stars: Star[] = untypedStars;
|
||||||
const constellations: Constellation[] = untypedConstellations;
|
const constellations: Constellation[] = untypedConstellations;
|
||||||
let canvas: HTMLCanvasElement, device: GPUDevice | undefined, pipeline: GPURenderPipeline, context: GPUCanvasContext | null, bindGroup: GPUBindGroup, viewport: DOMRect;
|
let canvas: HTMLCanvasElement, device: GPUDevice | undefined, pipeline: GPURenderPipeline, context: GPUCanvasContext | null, bindGroup: GPUBindGroup, viewport: DOMRect, uniforms: GPUBuffer, indirect: GPUBuffer;
|
||||||
let camera = { projection: new Matrix4x4(), view: new Matrix4x4(), transform: { position: { x: 0, y: 0, z: 0 }, yaw: 0, pitch: 0 }, final: new Matrix4x4(), _viewDirty: true, _projectionDirty: true };
|
let vertex: GPUBuffer, index: GPUBuffer;
|
||||||
let stats: Stats;
|
let camera: Camera, projectionMatrix = new Matrix4x4(), lastTime: DOMHighResTimeStamp;
|
||||||
|
let stats: Stats, dom: HTMLElement;
|
||||||
|
|
||||||
|
function clamp(x: number, min: number, max: number): number
|
||||||
|
{
|
||||||
|
if(x < min)
|
||||||
|
return min;
|
||||||
|
if(x > max)
|
||||||
|
return max;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
function fillBuffer(buffer: ArrayBuffer)
|
function fillBuffer(buffer: ArrayBuffer)
|
||||||
{
|
{
|
||||||
const array = new Float32Array(buffer);
|
const array = new Float32Array(buffer);
|
||||||
|
|
@ -391,36 +541,15 @@ function resize()
|
||||||
{
|
{
|
||||||
viewport = document.body.getBoundingClientRect();
|
viewport = document.body.getBoundingClientRect();
|
||||||
canvas!.width = viewport.width; canvas!.height = viewport.height;
|
canvas!.width = viewport.width; canvas!.height = viewport.height;
|
||||||
camera.projection.perspective(viewport.width / viewport.height, 60, 0.1, 2000);
|
device!.queue.writeBuffer(uniforms, 64, projectionMatrix.perspective(viewport.width / viewport.height, 60, 0.1, 2000000).view.buffer);
|
||||||
}
|
|
||||||
function computeViewProjectionMatrix()
|
|
||||||
{
|
|
||||||
let _dirty = false;
|
|
||||||
if(camera._viewDirty)
|
|
||||||
{
|
|
||||||
_dirty = true;
|
|
||||||
camera.view.camera(camera.transform).inverse();
|
|
||||||
camera._viewDirty = false;
|
|
||||||
}
|
|
||||||
if(camera._projectionDirty)
|
|
||||||
{
|
|
||||||
_dirty = true;
|
|
||||||
camera.projection.perspective(viewport.width / viewport.height, 60, 0.1, 2000);
|
|
||||||
camera._projectionDirty = false;
|
|
||||||
}
|
|
||||||
if(_dirty)
|
|
||||||
{
|
|
||||||
camera.final.multiply(camera.projection, camera.view);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init()
|
async function init()
|
||||||
{
|
{
|
||||||
canvas = document.createElement("canvas");
|
canvas = document.createElement("canvas");
|
||||||
context = canvas.getContext('webgpu');
|
context = canvas.getContext('webgpu');
|
||||||
|
|
||||||
document.body.appendChild(canvas);
|
Inputs.init(canvas);
|
||||||
resize();
|
|
||||||
document.addEventListener('resize', resize);
|
|
||||||
|
|
||||||
stats = new Stats();
|
stats = new Stats();
|
||||||
document.body.appendChild(stats.dom);
|
document.body.appendChild(stats.dom);
|
||||||
|
|
@ -430,33 +559,30 @@ async function init()
|
||||||
|
|
||||||
if(device && context)
|
if(device && context)
|
||||||
{
|
{
|
||||||
context.configure({
|
|
||||||
device,
|
|
||||||
format: navigator.gpu.getPreferredCanvasFormat(),
|
|
||||||
alphaMode: 'premultiplied',
|
|
||||||
});
|
|
||||||
|
|
||||||
const shader = device.createShaderModule({
|
const shader = device.createShaderModule({
|
||||||
code: shaderCode,
|
code: shaderCode,
|
||||||
label: 'default-shader'
|
label: 'default-shader'
|
||||||
});
|
});
|
||||||
|
|
||||||
const buffer = device.createBuffer({ size: stars.length * 4 * 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, mappedAtCreation: true, label: 'instanced_buffer' });
|
const buffer = device.createBuffer({ size: stars.length * 4 * 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, label: 'instanced_buffer' });
|
||||||
const arrayBuffer = buffer.getMappedRange();
|
const arrayBuffer = new ArrayBuffer(buffer.size);
|
||||||
|
|
||||||
fillBuffer(arrayBuffer);
|
fillBuffer(arrayBuffer);
|
||||||
|
|
||||||
buffer.unmap();
|
|
||||||
device.queue.writeBuffer(buffer, 0, arrayBuffer);
|
device.queue.writeBuffer(buffer, 0, arrayBuffer);
|
||||||
|
|
||||||
const uniformBuffer = device.createBuffer({ size: 16 * 4 /* matrix4x4 */, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, mappedAtCreation: true, label: 'uniform_buffer' });
|
uniforms = device.createBuffer({ size: 16 * 4 * 2, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, label: 'uniforms_buffer' });
|
||||||
const uniformArrayBuffer = uniformBuffer.getMappedRange();
|
camera = new Camera();
|
||||||
|
|
||||||
computeViewProjectionMatrix();
|
indirect = device.createBuffer({ size: 5 * 4, usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST, label: 'indirect_buffer' });
|
||||||
camera.final.copy(uniformArrayBuffer);
|
const indirectArray = new Uint32Array([ 6, stars.length, 0, 0, 0 ]);
|
||||||
|
const indirectSignedArray = new Int32Array(indirectArray.buffer);
|
||||||
|
indirectSignedArray[3] = 0;
|
||||||
|
device.queue.writeBuffer(indirect, 0, indirectArray.buffer);
|
||||||
|
|
||||||
uniformBuffer.unmap();
|
vertex = device.createBuffer({ size: 3 * 4 * 4, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, label: 'vertex_buffer' });
|
||||||
device.queue.writeBuffer(uniformBuffer, 0, uniformArrayBuffer);
|
device.queue.writeBuffer(vertex, 0, new Float32Array([ -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.5, 0.5, 0.0, -0.5, 0.5, 0.0, ]));
|
||||||
|
|
||||||
|
index = device.createBuffer({ size: 6 * 2, usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, label: 'index_buffer' });
|
||||||
|
device.queue.writeBuffer(index, 0, new Uint16Array([ 0, 1, 2, 0, 2, 3 ]));
|
||||||
|
|
||||||
const bindGroupLayout = device.createBindGroupLayout({
|
const bindGroupLayout = device.createBindGroupLayout({
|
||||||
entries: [{
|
entries: [{
|
||||||
|
|
@ -478,7 +604,7 @@ async function init()
|
||||||
layout: bindGroupLayout,
|
layout: bindGroupLayout,
|
||||||
entries: [{
|
entries: [{
|
||||||
binding: 0,
|
binding: 0,
|
||||||
resource: { buffer: uniformBuffer, }
|
resource: { buffer: uniforms, }
|
||||||
}, {
|
}, {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
resource: { buffer: buffer, }
|
resource: { buffer: buffer, }
|
||||||
|
|
@ -491,7 +617,15 @@ async function init()
|
||||||
}),
|
}),
|
||||||
vertex: {
|
vertex: {
|
||||||
module: shader,
|
module: shader,
|
||||||
entryPoint: 'vertex_main'
|
entryPoint: 'vertex_main',
|
||||||
|
buffers: [{
|
||||||
|
attributes: [{
|
||||||
|
format: 'float32x3',
|
||||||
|
offset: 0,
|
||||||
|
shaderLocation: 0
|
||||||
|
}],
|
||||||
|
arrayStride: 4 * 3,
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
fragment: {
|
fragment: {
|
||||||
targets: [{ format: navigator.gpu.getPreferredCanvasFormat() }],
|
targets: [{ format: navigator.gpu.getPreferredCanvasFormat() }],
|
||||||
|
|
@ -501,9 +635,30 @@ async function init()
|
||||||
primitive: {
|
primitive: {
|
||||||
topology: 'triangle-list',
|
topology: 'triangle-list',
|
||||||
cullMode: 'front',
|
cullMode: 'front',
|
||||||
|
frontFace: 'cw'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
resize();
|
||||||
|
document.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
context.configure({
|
||||||
|
device,
|
||||||
|
format: navigator.gpu.getPreferredCanvasFormat(),
|
||||||
|
alphaMode: 'premultiplied',
|
||||||
|
});
|
||||||
|
|
||||||
|
dom = document.createElement('pre');
|
||||||
|
Object.assign(dom.style, {
|
||||||
|
"position": "absolute",
|
||||||
|
"right": "0px",
|
||||||
|
"top": "0px",
|
||||||
|
"color": "white"
|
||||||
|
});
|
||||||
|
document.body.append(dom);
|
||||||
|
|
||||||
|
lastTime = performance.now();
|
||||||
requestAnimationFrame(render);
|
requestAnimationFrame(render);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -513,6 +668,8 @@ async function init()
|
||||||
}
|
}
|
||||||
function render(time: DOMHighResTimeStamp)
|
function render(time: DOMHighResTimeStamp)
|
||||||
{
|
{
|
||||||
|
const elapsed = time - lastTime;
|
||||||
|
lastTime = time;
|
||||||
stats.begin();
|
stats.begin();
|
||||||
const commandEncoder = device!.createCommandEncoder({ label: 'command-encoder' });
|
const commandEncoder = device!.createCommandEncoder({ label: 'command-encoder' });
|
||||||
const pass = commandEncoder.beginRenderPass({ label: 'render-pass', colorAttachments: [{
|
const pass = commandEncoder.beginRenderPass({ label: 'render-pass', colorAttachments: [{
|
||||||
|
|
@ -522,10 +679,15 @@ function render(time: DOMHighResTimeStamp)
|
||||||
view: context!.getCurrentTexture().createView({ label: 'render-target', usage: GPUTextureUsage.RENDER_ATTACHMENT }),
|
view: context!.getCurrentTexture().createView({ label: 'render-target', usage: GPUTextureUsage.RENDER_ATTACHMENT }),
|
||||||
}], });
|
}], });
|
||||||
|
|
||||||
|
camera.update(elapsed, uniforms);
|
||||||
|
dom.textContent = `Pitch: ${camera.pitch}\nYaw: ${camera.yaw}\nDistance: ${camera.distance}`;
|
||||||
|
|
||||||
pass.setPipeline(pipeline!);
|
pass.setPipeline(pipeline!);
|
||||||
pass.setBindGroup(0, bindGroup);
|
pass.setBindGroup(0, bindGroup);
|
||||||
|
pass.setVertexBuffer(0, vertex);
|
||||||
|
pass.setIndexBuffer(index, 'uint16');
|
||||||
pass.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, 0, 1);
|
pass.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, 0, 1);
|
||||||
pass.draw(4, stars.length, 0, 0);
|
pass.drawIndexedIndirect(indirect, 0);
|
||||||
pass.end();
|
pass.end();
|
||||||
|
|
||||||
device!.queue.submit([ commandEncoder.finish() ]);
|
device!.queue.submit([ commandEncoder.finish() ]);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue