Expand material data structure, renderer class
This commit is contained in:
		| @@ -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; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										13
									
								
								src/Mesh.ts
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								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; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										26
									
								
								src/Node.ts
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								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; | ||||
| 			} | ||||
|   | ||||
							
								
								
									
										91
									
								
								src/Renderer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/Renderer.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
| 	} | ||||
| } | ||||
| @@ -16,7 +16,7 @@ export class Scene { | ||||
|  | ||||
| 	readonly type!: "Scene"; | ||||
|  | ||||
| 	_name: string | undefined; | ||||
| 	_name: string; | ||||
|  | ||||
| 	_nodes: Node[]; | ||||
|  | ||||
|   | ||||
| @@ -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 }, | ||||
|   | ||||
| @@ -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; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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"; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user