Expand material data structure, renderer class

This commit is contained in:
Szymon Nowakowski 2023-07-27 22:52:52 +02:00
parent ddf586990a
commit d0c1ecbd5d
10 changed files with 213 additions and 49 deletions

View File

@ -35,7 +35,7 @@ export class CameraOrthographic {
_farPlane: number; _farPlane: number;
/** backreference */ /** backreference */
_node: Node | undefined; _node: Node | null;
constructor({ constructor({
name = "", name = "",
@ -51,16 +51,16 @@ export class CameraOrthographic {
this._nearPlane = nearPlane; this._nearPlane = nearPlane;
this._farPlane = farPlane; this._farPlane = farPlane;
this._node = undefined; this._node = null;
} }
detach(): Camera { detach(): Camera {
if (this._node === undefined) { if (this._node === null) {
return this; return this;
} }
this._node._camera = undefined; this._node._camera = null;
this._node = undefined; this._node = null;
return this; return this;
} }
} }
@ -76,7 +76,7 @@ export class CameraPerspective {
_farPlane: number; _farPlane: number;
/** backreference */ /** backreference */
_node: Node | undefined; _node: Node | null;
constructor({ constructor({
name = "", name = "",
@ -92,16 +92,16 @@ export class CameraPerspective {
this._nearPlane = nearPlane; this._nearPlane = nearPlane;
this._farPlane = farPlane; this._farPlane = farPlane;
this._node = undefined; this._node = null;
} }
detach(): Camera { detach(): Camera {
if (this._node === undefined) { if (this._node === null) {
return this; return this;
} }
this._node._camera = undefined; this._node._camera = null;
this._node = undefined; this._node = null;
return this; return this;
} }
} }

View File

@ -4,20 +4,23 @@
* obtain one at http://mozilla.org/MPL/2.0/. * obtain one at http://mozilla.org/MPL/2.0/.
*/ */
import { Renderer } from "./Renderer";
export const INDEX_SIZE = 2; export const INDEX_SIZE = 2;
export class IndexBuffer { export class IndexBuffer {
readonly type!: "IndexBuffer"; readonly type!: "IndexBuffer";
_renderer: Renderer;
_device: GPUDevice;
_buffer: GPUBuffer; _buffer: GPUBuffer;
constructor(device: GPUDevice, indexCount: number) { constructor(renderer: Renderer, indexCount: number) {
Object.defineProperty(this, "type", { value: "IndexBuffer" }); Object.defineProperty(this, "type", { value: "IndexBuffer" });
this._device = device; this._renderer = renderer;
this._buffer = device.createBuffer({
this._buffer = renderer._device.createBuffer({
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.INDEX, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.INDEX,
size: indexCount * INDEX_SIZE, size: indexCount * INDEX_SIZE,
}); });
@ -34,12 +37,12 @@ export class IndexBuffer {
writeArray(offset: number, indices: readonly number[]): IndexBuffer { writeArray(offset: number, indices: readonly number[]): IndexBuffer {
const array = new Uint16Array(indices); 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; return this;
} }
writeTypedArray(offset: number, indices: Uint16Array): IndexBuffer { 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; return this;
} }
} }

View File

@ -5,69 +5,111 @@
*/ */
import { Color, ColorObject } from "./Color"; import { Color, ColorObject } from "./Color";
import { Renderer } from "./Renderer";
import { Texture2D } from "./Texture2D";
export const UNIFORM_BUFFER_SIZE = 64;
export interface MaterialProps { export interface MaterialProps {
name?: string; name?: string;
baseColor?: ColorObject; baseColor?: ColorObject;
partialCoverage?: number;
occlusionTextureStrength?: number;
metallic?: number; metallic?: number;
roughness?: number; roughness?: number;
normalScale?: number;
emissive?: ColorObject; emissive?: ColorObject;
partialCoverage?: number;
transmission?: ColorObject; transmission?: ColorObject;
collimation?: number; collimation?: number;
ior?: number; ior?: number;
baseColorPartialCoverageTexture?: Texture2D | null;
occlusionMetallicRoughnessTexture?: Texture2D | null;
normalTexture?: Texture2D | null;
emissiveTexture?: Texture2D | null;
transmissionCollimationTexture?: Texture2D | null;
transparent?: boolean;
doubleSided?: boolean; doubleSided?: boolean;
} }
export class Material { export class Material {
readonly type!: "Material"; readonly type!: "Material";
_renderer: Renderer;
_name: string; _name: string;
_baseColor: Color; _baseColor: Color;
_partialCoverage: number;
_occlusionTextureStrength: number;
_metallic: number; _metallic: number;
_roughness: number; _roughness: number;
_normalScale: number;
_emissive: Color; _emissive: Color;
_partialCoverage: number;
_transmission: Color; _transmission: Color;
_collimation: number; _collimation: number;
_ior: number; _ior: number;
_baseColorPartialCoverageTexture: Texture2D | null;
_occlusionMetallicRoughnessTexture: Texture2D | null;
_normalTexture: Texture2D | null;
_emissiveTexture: Texture2D | null;
_transmissionCollimationTexture: Texture2D | null;
_transparent: boolean;
_doubleSided: boolean; _doubleSided: boolean;
constructor({ constructor(renderer: Renderer, {
name = "", name = "",
baseColor, baseColor,
partialCoverage = 1,
occlusionTextureStrength = 1,
metallic = 1, metallic = 1,
roughness = 1, roughness = 1,
normalScale = 1,
emissive, emissive,
partialCoverage = 1,
transmission, transmission,
collimation = 1, collimation = 1,
ior = 1.45, ior = 1.45,
baseColorPartialCoverageTexture = null,
occlusionMetallicRoughnessTexture = null,
normalTexture = null,
emissiveTexture = null,
transmissionCollimationTexture = null,
transparent = false,
doubleSided = false, doubleSided = false,
}: MaterialProps) { }: MaterialProps) {
Object.defineProperty(this, "type", { value: "Material" }); Object.defineProperty(this, "type", { value: "Material" });
this._renderer = renderer;
this._name = name; this._name = name;
this._baseColor = baseColor !== undefined ? Color.fromObject(baseColor) : Color.white(); this._baseColor = baseColor !== undefined ? Color.fromObject(baseColor) : Color.white();
this._partialCoverage = partialCoverage;
this._occlusionTextureStrength = occlusionTextureStrength;
this._metallic = metallic; this._metallic = metallic;
this._roughness = roughness; this._roughness = roughness;
this._normalScale = normalScale;
this._emissive = emissive !== undefined ? Color.fromObject(emissive) : Color.black(); this._emissive = emissive !== undefined ? Color.fromObject(emissive) : Color.black();
this._partialCoverage = partialCoverage;
this._transmission = transmission !== undefined ? Color.fromObject(transmission) : Color.black(); this._transmission = transmission !== undefined ? Color.fromObject(transmission) : Color.black();
this._collimation = collimation; this._collimation = collimation;
this._ior = ior; this._ior = ior;
this._baseColorPartialCoverageTexture = baseColorPartialCoverageTexture;
this._occlusionMetallicRoughnessTexture = occlusionMetallicRoughnessTexture;
this._normalTexture = normalTexture;
this._emissiveTexture = emissiveTexture;
this._transmissionCollimationTexture = transmissionCollimationTexture;
this._transparent = transparent;
this._doubleSided = doubleSided; this._doubleSided = doubleSided;
} }
get isTransparent(): boolean { dispose(): Material {
return this._partialCoverage < 1 || this._transmission.r > 0 || this._transmission.g > 0 || this._transmission.b > 0; return this;
} }
} }

View File

@ -7,11 +7,17 @@
import { IndexBuffer } from "./IndexBuffer"; import { IndexBuffer } from "./IndexBuffer";
import { VertexBuffer } from "./VertexBuffer"; import { VertexBuffer } from "./VertexBuffer";
export type Submesh = {
start: number,
length: number,
};
export interface MeshProps { export interface MeshProps {
readonly name?: string; readonly name?: string;
readonly vertexBuffer: VertexBuffer; readonly vertexBuffer: VertexBuffer;
readonly indexBuffer: IndexBuffer; readonly indexBuffer: IndexBuffer;
readonly submeshes: Submesh[];
} }
export class Mesh { export class Mesh {
@ -22,11 +28,13 @@ export class Mesh {
_vertexBuffer: VertexBuffer; _vertexBuffer: VertexBuffer;
_indexBuffer: IndexBuffer; _indexBuffer: IndexBuffer;
_submeshes: Submesh[];
constructor({ constructor({
name = "", name = "",
vertexBuffer, vertexBuffer,
indexBuffer, indexBuffer,
submeshes,
}: MeshProps) { }: MeshProps) {
Object.defineProperty(this, "type", { value: "Mesh" }); Object.defineProperty(this, "type", { value: "Mesh" });
@ -34,6 +42,11 @@ export class Mesh {
this._vertexBuffer = vertexBuffer; this._vertexBuffer = vertexBuffer;
this._indexBuffer = indexBuffer this._indexBuffer = indexBuffer
this._submeshes = submeshes;
}
get submeshCount(): number {
return this._submeshes.length;
} }
} }

View File

@ -5,6 +5,7 @@
*/ */
import { Camera } from "./Camera"; import { Camera } from "./Camera";
import { Material } from "./Material";
import { Mesh } from "./Mesh"; import { Mesh } from "./Mesh";
import { Quaternion, QuaternionObject } from "./Quaternion"; import { Quaternion, QuaternionObject } from "./Quaternion";
import { Vector3, Vector3Object } from "./Vector3"; import { Vector3, Vector3Object } from "./Vector3";
@ -16,8 +17,9 @@ export interface NodeProps {
readonly rotation?: QuaternionObject; readonly rotation?: QuaternionObject;
readonly scale?: Vector3Object; readonly scale?: Vector3Object;
readonly camera?: Camera; readonly camera?: Camera | null;
readonly mesh?: Mesh; readonly mesh?: Mesh | null;
readonly materials?: Material[];
readonly children?: Node[]; readonly children?: Node[];
} }
@ -33,23 +35,26 @@ export class Node {
_scale: Vector3; _scale: Vector3;
/** unique */ /** unique */
_camera: Camera | undefined; _camera: Camera | null;
/** shared */ /** shared */
_mesh: Mesh | undefined; _mesh: Mesh | null;
/** shared */
_materials: Material[];
/** unique */ /** unique */
_children: Node[]; _children: Node[];
/** backreference */ /** backreference */
_parent: Node | undefined; _parent: Node | null;
constructor({ constructor({
name = "", name = "",
translation, translation,
rotation, rotation,
scale, scale,
camera, camera = null,
mesh, mesh = null,
materials = [],
children = [], children = [],
}: NodeProps) { }: NodeProps) {
Object.defineProperty(this, "type", { value: "Node" }); Object.defineProperty(this, "type", { value: "Node" });
@ -62,16 +67,17 @@ export class Node {
this._camera = camera; this._camera = camera;
this._mesh = mesh; this._mesh = mesh;
this._materials = materials;
this._children = children; this._children = children;
this._parent = undefined; this._parent = null;
if (this._camera !== undefined) { if (this._camera !== null) {
this._camera._node = this; this._camera._node = this;
} }
if (this._children !== undefined) { if (this._children !== null) {
for (const child of this._children) { for (const child of this._children) {
child._parent = this; child._parent = this;
} }

91
src/Renderer.ts Normal file
View 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);
}
}

View File

@ -16,7 +16,7 @@ export class Scene {
readonly type!: "Scene"; readonly type!: "Scene";
_name: string | undefined; _name: string;
_nodes: Node[]; _nodes: Node[];

View File

@ -4,41 +4,47 @@
* obtain one at http://mozilla.org/MPL/2.0/. * obtain one at http://mozilla.org/MPL/2.0/.
*/ */
import { Renderer } from "./Renderer";
export interface Texture2DProps { export interface Texture2DProps {
name?: string; name?: string;
device: GPUDevice;
width: number; width: number;
height: number; height: number;
sRGB?: boolean;
} }
export class Texture2D { export class Texture2D {
readonly type!: "Texture2D"; readonly type!: "Texture2D";
_renderer: Renderer;
_name: string; _name: string;
_device: GPUDevice;
_texture: GPUTexture; _texture: GPUTexture;
_textureView: GPUTextureView; _textureView: GPUTextureView;
constructor({ constructor(renderer: Renderer, {
name = "", name = "",
device,
width, width,
height, height,
sRGB = false,
}: Texture2DProps) { }: Texture2DProps) {
Object.defineProperty(this, "type", { value: "Texture2D" }); Object.defineProperty(this, "type", { value: "Texture2D" });
this._renderer = renderer;
this._name = name; this._name = name;
this._device = device; this._renderer = renderer;
this._texture = device.createTexture({ this._texture = renderer._device.createTexture({
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
size: { width, height }, size: { width, height },
format: "rgba8unorm", format: sRGB ? "rgba8unorm-srgb" : "rgba8unorm",
}); });
this._textureView = this._texture.createView({ this._textureView = this._texture.createView({
format: "rgba8unorm", format: sRGB ? "rgba8unorm-srgb" : "rgba8unorm",
dimension: "2d", dimension: "2d",
}); });
} }
@ -57,7 +63,7 @@ export class Texture2D {
} }
writeTypedArray(data: Uint8Array): Texture2D { writeTypedArray(data: Uint8Array): Texture2D {
this._device.queue.writeTexture( this._renderer._device.queue.writeTexture(
{ texture: this._texture }, { texture: this._texture },
data, data,
{ bytesPerRow: 4 * this._texture.width }, { bytesPerRow: 4 * this._texture.width },

View File

@ -4,6 +4,7 @@
* obtain one at http://mozilla.org/MPL/2.0/. * obtain one at http://mozilla.org/MPL/2.0/.
*/ */
import { Renderer } from "./Renderer";
import { Vector3Object } from "./Vector3"; import { Vector3Object } from "./Vector3";
export const VERTEX_SIZE = 12; export const VERTEX_SIZE = 12;
@ -11,15 +12,16 @@ export const VERTEX_SIZE = 12;
export class VertexBuffer { export class VertexBuffer {
readonly type!: "VertexBuffer"; readonly type!: "VertexBuffer";
_renderer: Renderer;
_device: GPUDevice;
_buffer: GPUBuffer; _buffer: GPUBuffer;
constructor(device: GPUDevice, vertexCount: number) { constructor(renderer: Renderer, vertexCount: number) {
Object.defineProperty(this, "type", { value: "VertexBuffer" }); Object.defineProperty(this, "type", { value: "VertexBuffer" });
this._device = device; this._renderer = renderer;
this._buffer = device.createBuffer({
this._buffer = renderer._device.createBuffer({
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
size: vertexCount * VERTEX_SIZE, size: vertexCount * VERTEX_SIZE,
}); });
@ -42,12 +44,12 @@ export class VertexBuffer {
array[ptr++] = vertex.y; array[ptr++] = vertex.y;
array[ptr++] = vertex.z; 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; return this;
} }
writeTypedArray(offset: number, vertices: Float32Array): VertexBuffer { 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; return this;
} }
} }

View File

@ -12,6 +12,7 @@ export * from "./Matrix4x4";
export * from "./Mesh"; export * from "./Mesh";
export * from "./Node"; export * from "./Node";
export * from "./Quaternion"; export * from "./Quaternion";
export * from "./Renderer";
export * from "./Scene"; export * from "./Scene";
export * from "./Texture2D"; export * from "./Texture2D";
export * from "./Vector3"; export * from "./Vector3";