diff --git a/src/Camera.ts b/src/Camera.ts index c6fd564..8ffc9a1 100644 --- a/src/Camera.ts +++ b/src/Camera.ts @@ -35,7 +35,7 @@ export class CameraOrthographic { _farPlane: number; /** backreference */ - _node: Node | undefined; + _node: Node | null; constructor({ name = "", @@ -51,16 +51,16 @@ export class CameraOrthographic { this._nearPlane = nearPlane; this._farPlane = farPlane; - this._node = undefined; + this._node = null; } detach(): Camera { - if (this._node === undefined) { + if (this._node === null) { return this; } - this._node._camera = undefined; - this._node = undefined; + this._node._camera = null; + this._node = null; return this; } } @@ -76,7 +76,7 @@ export class CameraPerspective { _farPlane: number; /** backreference */ - _node: Node | undefined; + _node: Node | null; constructor({ name = "", @@ -92,16 +92,16 @@ export class CameraPerspective { this._nearPlane = nearPlane; this._farPlane = farPlane; - this._node = undefined; + this._node = null; } detach(): Camera { - if (this._node === undefined) { + if (this._node === null) { return this; } - this._node._camera = undefined; - this._node = undefined; + this._node._camera = null; + this._node = null; return this; } } diff --git a/src/IndexBuffer.ts b/src/IndexBuffer.ts index 106ecaa..7bd9ff7 100644 --- a/src/IndexBuffer.ts +++ b/src/IndexBuffer.ts @@ -4,20 +4,23 @@ * obtain one at http://mozilla.org/MPL/2.0/. */ +import { Renderer } from "./Renderer"; + export const INDEX_SIZE = 2; export class IndexBuffer { readonly type!: "IndexBuffer"; + _renderer: Renderer; - _device: GPUDevice; _buffer: GPUBuffer; - constructor(device: GPUDevice, indexCount: number) { + constructor(renderer: Renderer, indexCount: number) { Object.defineProperty(this, "type", { value: "IndexBuffer" }); - this._device = device; - this._buffer = device.createBuffer({ + this._renderer = renderer; + + this._buffer = renderer._device.createBuffer({ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.INDEX, size: indexCount * INDEX_SIZE, }); @@ -34,12 +37,12 @@ export class IndexBuffer { writeArray(offset: number, indices: readonly number[]): IndexBuffer { const array = new Uint16Array(indices); - this._device.queue.writeBuffer(this._buffer, offset * INDEX_SIZE | 0, array); + this._renderer._device.queue.writeBuffer(this._buffer, offset * INDEX_SIZE | 0, array); return this; } writeTypedArray(offset: number, indices: Uint16Array): IndexBuffer { - this._device.queue.writeBuffer(this._buffer, offset * INDEX_SIZE | 0, indices); + this._renderer._device.queue.writeBuffer(this._buffer, offset * INDEX_SIZE | 0, indices); return this; } } diff --git a/src/Material.ts b/src/Material.ts index 49a9615..e419300 100644 --- a/src/Material.ts +++ b/src/Material.ts @@ -5,69 +5,111 @@ */ import { Color, ColorObject } from "./Color"; +import { Renderer } from "./Renderer"; +import { Texture2D } from "./Texture2D"; + +export const UNIFORM_BUFFER_SIZE = 64; export interface MaterialProps { name?: string; baseColor?: ColorObject; + partialCoverage?: number; + occlusionTextureStrength?: number; metallic?: number; roughness?: number; + normalScale?: number; emissive?: ColorObject; - partialCoverage?: number; transmission?: ColorObject; collimation?: number; ior?: number; + baseColorPartialCoverageTexture?: Texture2D | null; + occlusionMetallicRoughnessTexture?: Texture2D | null; + normalTexture?: Texture2D | null; + emissiveTexture?: Texture2D | null; + transmissionCollimationTexture?: Texture2D | null; + + transparent?: boolean; doubleSided?: boolean; } export class Material { readonly type!: "Material"; + _renderer: Renderer; _name: string; _baseColor: Color; + _partialCoverage: number; + _occlusionTextureStrength: number; _metallic: number; _roughness: number; + _normalScale: number; _emissive: Color; - _partialCoverage: number; _transmission: Color; _collimation: number; _ior: number; + _baseColorPartialCoverageTexture: Texture2D | null; + _occlusionMetallicRoughnessTexture: Texture2D | null; + _normalTexture: Texture2D | null; + _emissiveTexture: Texture2D | null; + _transmissionCollimationTexture: Texture2D | null; + + _transparent: boolean; _doubleSided: boolean; - constructor({ + constructor(renderer: Renderer, { name = "", baseColor, + partialCoverage = 1, + occlusionTextureStrength = 1, metallic = 1, roughness = 1, + normalScale = 1, emissive, - partialCoverage = 1, transmission, collimation = 1, ior = 1.45, + baseColorPartialCoverageTexture = null, + occlusionMetallicRoughnessTexture = null, + normalTexture = null, + emissiveTexture = null, + transmissionCollimationTexture = null, + transparent = false, doubleSided = false, }: MaterialProps) { Object.defineProperty(this, "type", { value: "Material" }); + this._renderer = renderer; + this._name = name; this._baseColor = baseColor !== undefined ? Color.fromObject(baseColor) : Color.white(); + this._partialCoverage = partialCoverage; + this._occlusionTextureStrength = occlusionTextureStrength; this._metallic = metallic; this._roughness = roughness; + this._normalScale = normalScale; this._emissive = emissive !== undefined ? Color.fromObject(emissive) : Color.black(); - this._partialCoverage = partialCoverage; this._transmission = transmission !== undefined ? Color.fromObject(transmission) : Color.black(); this._collimation = collimation; this._ior = ior; + this._baseColorPartialCoverageTexture = baseColorPartialCoverageTexture; + this._occlusionMetallicRoughnessTexture = occlusionMetallicRoughnessTexture; + this._normalTexture = normalTexture; + this._emissiveTexture = emissiveTexture; + this._transmissionCollimationTexture = transmissionCollimationTexture; + + this._transparent = transparent; this._doubleSided = doubleSided; } - get isTransparent(): boolean { - return this._partialCoverage < 1 || this._transmission.r > 0 || this._transmission.g > 0 || this._transmission.b > 0; + dispose(): Material { + return this; } } diff --git a/src/Mesh.ts b/src/Mesh.ts index 96be3d8..39ecaf4 100644 --- a/src/Mesh.ts +++ b/src/Mesh.ts @@ -7,11 +7,17 @@ import { IndexBuffer } from "./IndexBuffer"; import { VertexBuffer } from "./VertexBuffer"; +export type Submesh = { + start: number, + length: number, +}; + export interface MeshProps { readonly name?: string; readonly vertexBuffer: VertexBuffer; readonly indexBuffer: IndexBuffer; + readonly submeshes: Submesh[]; } export class Mesh { @@ -22,11 +28,13 @@ export class Mesh { _vertexBuffer: VertexBuffer; _indexBuffer: IndexBuffer; + _submeshes: Submesh[]; constructor({ name = "", vertexBuffer, indexBuffer, + submeshes, }: MeshProps) { Object.defineProperty(this, "type", { value: "Mesh" }); @@ -34,6 +42,11 @@ export class Mesh { this._vertexBuffer = vertexBuffer; this._indexBuffer = indexBuffer + this._submeshes = submeshes; + } + + get submeshCount(): number { + return this._submeshes.length; } } diff --git a/src/Node.ts b/src/Node.ts index f71b3ec..cf0db3c 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -5,6 +5,7 @@ */ import { Camera } from "./Camera"; +import { Material } from "./Material"; import { Mesh } from "./Mesh"; import { Quaternion, QuaternionObject } from "./Quaternion"; import { Vector3, Vector3Object } from "./Vector3"; @@ -16,8 +17,9 @@ export interface NodeProps { readonly rotation?: QuaternionObject; readonly scale?: Vector3Object; - readonly camera?: Camera; - readonly mesh?: Mesh; + readonly camera?: Camera | null; + readonly mesh?: Mesh | null; + readonly materials?: Material[]; readonly children?: Node[]; } @@ -33,23 +35,26 @@ export class Node { _scale: Vector3; /** unique */ - _camera: Camera | undefined; + _camera: Camera | null; /** shared */ - _mesh: Mesh | undefined; + _mesh: Mesh | null; + /** shared */ + _materials: Material[]; /** unique */ _children: Node[]; /** backreference */ - _parent: Node | undefined; + _parent: Node | null; constructor({ name = "", translation, rotation, scale, - camera, - mesh, + camera = null, + mesh = null, + materials = [], children = [], }: NodeProps) { Object.defineProperty(this, "type", { value: "Node" }); @@ -62,16 +67,17 @@ export class Node { this._camera = camera; this._mesh = mesh; + this._materials = materials; this._children = children; - this._parent = undefined; + this._parent = null; - if (this._camera !== undefined) { + if (this._camera !== null) { this._camera._node = this; } - if (this._children !== undefined) { + if (this._children !== null) { for (const child of this._children) { child._parent = this; } diff --git a/src/Renderer.ts b/src/Renderer.ts new file mode 100644 index 0000000..2193e78 --- /dev/null +++ b/src/Renderer.ts @@ -0,0 +1,91 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { IndexBuffer } from "./IndexBuffer"; +import { Material, MaterialProps } from "./Material"; +import { Texture2D, Texture2DProps } from "./Texture2D"; +import { VertexBuffer } from "./VertexBuffer"; + +export class Renderer { + + _adapter: GPUAdapter; + _device: GPUDevice; + _context: GPUCanvasContext; + _format: GPUTextureFormat; + + _textureWhite: Texture2D; // 1×1 rgba8unorm of [255, 255, 255, 255] + _textureBlack: Texture2D; // 1×1 rgba8unorm of [0, 0, 0, 255] + _textureNormal: Texture2D; // 1×1 rgba8unorm of [128, 128, 128, 255] + + private constructor ( + adapter: GPUAdapter, + device: GPUDevice, + context: GPUCanvasContext, + format: GPUTextureFormat, + ) { + this._adapter = adapter; + this._device = device; + this._context = context; + this._format = format; + + this._textureWhite = new Texture2D(this, { + width: 1, + height: 1, + }); + this._textureWhite.writeTypedArray(new Uint8Array([255, 255, 255, 255])); + + this._textureBlack = new Texture2D(this, { + width: 1, + height: 1, + }); + this._textureBlack.writeTypedArray(new Uint8Array([0, 0, 0, 255])); + + this._textureNormal = new Texture2D(this, { + width: 1, + height: 1, + }); + this._textureNormal.writeTypedArray(new Uint8Array([128, 128, 128, 255])); + } + + static async init(canvas: HTMLCanvasElement) { + if (!navigator.gpu) { + throw new Error("WebGPU is not supported"); + } + + const adapter = await navigator.gpu.requestAdapter({ + powerPreference: "high-performance", + }); + if (adapter === null) { + throw new Error("GPUAdapter is not available"); + } + + const device = await adapter.requestDevice(); + + const context = canvas.getContext("webgpu"); + if (context === null) { + throw new Error("GPUCanvasContext is not available"); + } + + const format = navigator.gpu.getPreferredCanvasFormat(); + context.configure({ device, format }); + } + + createTexture(props: Texture2DProps): Texture2D { + return new Texture2D(this, props); + } + + createIndexBuffer(indexCount: number): IndexBuffer { + return new IndexBuffer(this, indexCount); + } + + createMaterial(props: MaterialProps): Material { + return new Material(this, props); + } + + createVertexBuffer(vertexCount: number): VertexBuffer { + return new VertexBuffer(this, vertexCount); + } +} diff --git a/src/Scene.ts b/src/Scene.ts index 61cdcfb..6970b30 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -16,7 +16,7 @@ export class Scene { readonly type!: "Scene"; - _name: string | undefined; + _name: string; _nodes: Node[]; diff --git a/src/Texture2D.ts b/src/Texture2D.ts index 227c60e..33a9ff0 100644 --- a/src/Texture2D.ts +++ b/src/Texture2D.ts @@ -4,41 +4,47 @@ * obtain one at http://mozilla.org/MPL/2.0/. */ +import { Renderer } from "./Renderer"; + export interface Texture2DProps { name?: string; - device: GPUDevice; + width: number; height: number; + + sRGB?: boolean; } export class Texture2D { readonly type!: "Texture2D"; + _renderer: Renderer; _name: string; - _device: GPUDevice; _texture: GPUTexture; _textureView: GPUTextureView; - constructor({ + constructor(renderer: Renderer, { name = "", - device, width, height, + sRGB = false, }: Texture2DProps) { Object.defineProperty(this, "type", { value: "Texture2D" }); + this._renderer = renderer; + this._name = name; - this._device = device; - this._texture = device.createTexture({ + this._renderer = renderer; + this._texture = renderer._device.createTexture({ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, size: { width, height }, - format: "rgba8unorm", + format: sRGB ? "rgba8unorm-srgb" : "rgba8unorm", }); this._textureView = this._texture.createView({ - format: "rgba8unorm", + format: sRGB ? "rgba8unorm-srgb" : "rgba8unorm", dimension: "2d", }); } @@ -57,7 +63,7 @@ export class Texture2D { } writeTypedArray(data: Uint8Array): Texture2D { - this._device.queue.writeTexture( + this._renderer._device.queue.writeTexture( { texture: this._texture }, data, { bytesPerRow: 4 * this._texture.width }, diff --git a/src/VertexBuffer.ts b/src/VertexBuffer.ts index 5b755dc..09797ee 100644 --- a/src/VertexBuffer.ts +++ b/src/VertexBuffer.ts @@ -4,6 +4,7 @@ * obtain one at http://mozilla.org/MPL/2.0/. */ +import { Renderer } from "./Renderer"; import { Vector3Object } from "./Vector3"; export const VERTEX_SIZE = 12; @@ -11,15 +12,16 @@ export const VERTEX_SIZE = 12; export class VertexBuffer { readonly type!: "VertexBuffer"; + _renderer: Renderer; - _device: GPUDevice; _buffer: GPUBuffer; - constructor(device: GPUDevice, vertexCount: number) { + constructor(renderer: Renderer, vertexCount: number) { Object.defineProperty(this, "type", { value: "VertexBuffer" }); - this._device = device; - this._buffer = device.createBuffer({ + this._renderer = renderer; + + this._buffer = renderer._device.createBuffer({ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX, size: vertexCount * VERTEX_SIZE, }); @@ -42,12 +44,12 @@ export class VertexBuffer { array[ptr++] = vertex.y; array[ptr++] = vertex.z; } - this._device.queue.writeBuffer(this._buffer, offset * VERTEX_SIZE | 0, array); + this._renderer._device.queue.writeBuffer(this._buffer, offset * VERTEX_SIZE | 0, array); return this; } writeTypedArray(offset: number, vertices: Float32Array): VertexBuffer { - this._device.queue.writeBuffer(this._buffer, offset * VERTEX_SIZE | 0, vertices); + this._renderer._device.queue.writeBuffer(this._buffer, offset * VERTEX_SIZE | 0, vertices); return this; } } diff --git a/src/oktaeder.ts b/src/oktaeder.ts index f78f1c6..2d3eaf1 100644 --- a/src/oktaeder.ts +++ b/src/oktaeder.ts @@ -12,6 +12,7 @@ export * from "./Matrix4x4"; export * from "./Mesh"; export * from "./Node"; export * from "./Quaternion"; +export * from "./Renderer"; export * from "./Scene"; export * from "./Texture2D"; export * from "./Vector3";