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;
/** 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
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";
_name: string | undefined;
_name: string;
_nodes: Node[];

View File

@ -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 },

View File

@ -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;
}
}

View File

@ -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";