Provide the single-loadout Scene API, SAB-backed meshes, cameras, materials, lights, workers, post effects, and tutorial playgrounds. Batch matching row growth so active GPU loadouts refresh once. Amp-Thread-ID: https://ampcode.com/threads/T-01a01380-b478-77d0-84a0-102880a5c5ae Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
import type { Scene } from "../Scene";
|
|
import type { Texture } from "./Texture";
|
|
|
|
export type PBRMaterialOptions = {
|
|
baseColor?: ArrayLike<number>;
|
|
metallic?: number;
|
|
roughness?: number;
|
|
emissive?: ArrayLike<number>;
|
|
normalScale?: number;
|
|
alphaCutoff?: number;
|
|
baseColorTexture?: Texture;
|
|
metallicRoughnessTexture?: Texture;
|
|
normalTexture?: Texture;
|
|
emissiveTexture?: Texture;
|
|
};
|
|
|
|
/** Conventional PBR values and texture IDs stored in two shared SOA rows. */
|
|
export class PBRMaterial {
|
|
readonly scene: Scene;
|
|
id = -1;
|
|
readonly ready: Promise<this>;
|
|
#disposed = false;
|
|
|
|
constructor(scene: Scene, options: PBRMaterialOptions = {}) {
|
|
this.scene = scene;
|
|
this.ready = scene.allocateMaterial().then((id) => {
|
|
this.id = id;
|
|
const color = Array.from(options.baseColor ?? [1, 1, 1, 1]);
|
|
const emissive = Array.from(options.emissive ?? [0, 0, 0]);
|
|
if (color.length !== 4 || emissive.length !== 3) throw new TypeError("PBR material vectors");
|
|
scene.array("materials").write(id, [
|
|
...color,
|
|
options.metallic ?? 0,
|
|
options.roughness ?? 0.7,
|
|
...emissive,
|
|
options.normalScale ?? 1,
|
|
options.alphaCutoff ?? 0.5,
|
|
0,
|
|
]);
|
|
scene.array("materialTextures").write(id, [
|
|
(options.baseColorTexture?.id ?? -1) + 1,
|
|
(options.metallicRoughnessTexture?.id ?? -1) + 1,
|
|
(options.normalTexture?.id ?? -1) + 1,
|
|
(options.emissiveTexture?.id ?? -1) + 1,
|
|
0, 0, 0, 0,
|
|
]);
|
|
return this;
|
|
});
|
|
}
|
|
|
|
#values() {
|
|
if (this.id < 0) throw new Error("Await material.ready before reading or writing it");
|
|
return this.scene.array("materials").row(this.id);
|
|
}
|
|
|
|
get baseColor() { return this.#values().subarray(0, 4); }
|
|
set baseColor(value: ArrayLike<number>) {
|
|
if (value.length !== 4) throw new RangeError("baseColor");
|
|
this.#values().set(value, 0);
|
|
}
|
|
get metallic() { return this.#values()[4]; }
|
|
set metallic(value: number) { this.#values()[4] = value; }
|
|
get roughness() { return this.#values()[5]; }
|
|
set roughness(value: number) { this.#values()[5] = value; }
|
|
get emissive() { return this.#values().subarray(6, 9); }
|
|
set emissive(value: ArrayLike<number>) {
|
|
if (value.length !== 3) throw new RangeError("emissive");
|
|
this.#values().set(value, 6);
|
|
}
|
|
|
|
async dispose() {
|
|
await this.ready;
|
|
if (this.#disposed) return;
|
|
this.#disposed = true;
|
|
await this.scene.core.deleteObject("materials", this.id);
|
|
}
|
|
}
|