Introduce static and dynamic materials
This commit is contained in:
		| @@ -6,35 +6,11 @@ | ||||
| 
 | ||||
| import { Color, ColorObject } from "."; | ||||
| import { Texture2D } from "../resources"; | ||||
| import { MaterialProps } from "./MaterialProps"; | ||||
| 
 | ||||
| export interface MaterialProps { | ||||
| 	name?: string; | ||||
| export class DynamicMaterial { | ||||
| 
 | ||||
| 	baseColor?: ColorObject; | ||||
| 	partialCoverage?: number; | ||||
| 	transmission?: ColorObject; | ||||
| 	collimation?: number; | ||||
| 	occlusionTextureStrength?: number; | ||||
| 	roughness?: number; | ||||
| 	metallic?: number; | ||||
| 	normalScale?: number; | ||||
| 	emissive?: ColorObject; | ||||
| 	ior?: number; | ||||
| 
 | ||||
| 	baseColorPartialCoverageTexture?: Texture2D | null; | ||||
| 	occlusionTexture?: Texture2D | null; | ||||
| 	roughnessMetallicTexture?: Texture2D | null; | ||||
| 	normalTexture?: Texture2D | null; | ||||
| 	emissiveTexture?: Texture2D | null; | ||||
| 	transmissionCollimationTexture?: Texture2D | null; | ||||
| 
 | ||||
| 	transparent?: boolean; | ||||
| 	doubleSided?: boolean; | ||||
| } | ||||
| 
 | ||||
| export class Material { | ||||
| 
 | ||||
| 	declare readonly type: "Material"; | ||||
| 	declare readonly type: "DynamicMaterial"; | ||||
| 
 | ||||
| 	_name: string; | ||||
| 
 | ||||
| @@ -107,7 +83,7 @@ export class Material { | ||||
| 	set name(value: string) { this._name = value; } | ||||
| 	get name(): string { return this._name; } | ||||
| 
 | ||||
| 	setBaseColor(value: ColorObject): Material { | ||||
| 	setBaseColor(value: ColorObject): DynamicMaterial { | ||||
| 		this._baseColor.setObject(value); | ||||
| 		return this; | ||||
| 	} | ||||
| @@ -130,7 +106,7 @@ export class Material { | ||||
| 	set normalScale(value: number) { this._normalScale = value; } | ||||
| 	get normalScale(): number { return this._normalScale; } | ||||
| 
 | ||||
| 	setEmissive(value: ColorObject): Material { | ||||
| 	setEmissive(value: ColorObject): DynamicMaterial { | ||||
| 		this._emissive.setObject(value); | ||||
| 		return this; | ||||
| 	} | ||||
| @@ -138,7 +114,7 @@ export class Material { | ||||
| 		return res.setObject(this._emissive); | ||||
| 	} | ||||
| 
 | ||||
| 	setTransmission(value: ColorObject): Material { | ||||
| 	setTransmission(value: ColorObject): DynamicMaterial { | ||||
| 		this._transmission.setObject(value); | ||||
| 		return this; | ||||
| 	} | ||||
| @@ -152,22 +128,22 @@ export class Material { | ||||
| 	set ior(value: number) { this._ior = value; } | ||||
| 	get ior(): number { return this._ior; } | ||||
| 
 | ||||
| 	set baseColorPartialCoverageTexture(value: Texture2D | null) { this._baseColorPartialCoverageTexture = value;} | ||||
| 	set baseColorPartialCoverageTexture(value: Texture2D | null) { this._baseColorPartialCoverageTexture = value; } | ||||
| 	get baseColorPartialCoverageTexture(): Texture2D | null { return this._baseColorPartialCoverageTexture; } | ||||
| 
 | ||||
| 	set occlusionTexture(value: Texture2D | null) { this._occlusionTexture = value;} | ||||
| 	set occlusionTexture(value: Texture2D | null) { this._occlusionTexture = value; } | ||||
| 	get occlusionTexture(): Texture2D | null { return this._occlusionTexture; } | ||||
| 
 | ||||
| 	set roughnessMetallicTexture(value: Texture2D | null) { this._roughnessMetallicTexture = value;} | ||||
| 	set roughnessMetallicTexture(value: Texture2D | null) { this._roughnessMetallicTexture = value; } | ||||
| 	get roughnessMetallicTexture(): Texture2D | null { return this._roughnessMetallicTexture; } | ||||
| 
 | ||||
| 	set normalTexture(value: Texture2D | null) { this._normalTexture = value;} | ||||
| 	set normalTexture(value: Texture2D | null) { this._normalTexture = value; } | ||||
| 	get normalTexture(): Texture2D | null { return this._normalTexture; } | ||||
| 
 | ||||
| 	set emissiveTexture(value: Texture2D | null) { this._emissiveTexture = value;} | ||||
| 	set emissiveTexture(value: Texture2D | null) { this._emissiveTexture = value; } | ||||
| 	get emissiveTexture(): Texture2D | null { return this._emissiveTexture; } | ||||
| 
 | ||||
| 	set transmissionCollimationTexture(value: Texture2D | null) { this._transmissionCollimationTexture = value;} | ||||
| 	set transmissionCollimationTexture(value: Texture2D | null) { this._transmissionCollimationTexture = value; } | ||||
| 	get transmissionCollimationTexture(): Texture2D | null { return this._transmissionCollimationTexture; } | ||||
| 
 | ||||
| 	set transparent(value: boolean) { this._transparent = value; } | ||||
| @@ -177,8 +153,8 @@ export class Material { | ||||
| 	get doubleSided(): boolean { return this._doubleSided; } | ||||
| } | ||||
| 
 | ||||
| Object.defineProperty(Material.prototype, "type", { value: "Material" }); | ||||
| Object.defineProperty(DynamicMaterial.prototype, "type", { value: "DynamicMaterial" }); | ||||
| 
 | ||||
| export function isMaterial(value: unknown): value is Material { | ||||
| 	return Boolean(value) && (value as Material).type === "Material"; | ||||
| export function isDynamicMaterial(value: unknown): value is DynamicMaterial { | ||||
| 	return Boolean(value) && (value as DynamicMaterial).type === "DynamicMaterial"; | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/data/MaterialProps.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/data/MaterialProps.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| /*! | ||||
|  * 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 { ColorObject } from "."; | ||||
| import { Texture2D } from "../resources"; | ||||
|  | ||||
| export interface MaterialProps { | ||||
| 	name?: string; | ||||
|  | ||||
| 	baseColor?: ColorObject; | ||||
| 	partialCoverage?: number; | ||||
| 	transmission?: ColorObject; | ||||
| 	collimation?: number; | ||||
| 	occlusionTextureStrength?: number; | ||||
| 	roughness?: number; | ||||
| 	metallic?: number; | ||||
| 	normalScale?: number; | ||||
| 	emissive?: ColorObject; | ||||
| 	ior?: number; | ||||
|  | ||||
| 	baseColorPartialCoverageTexture?: Texture2D | null; | ||||
| 	occlusionTexture?: Texture2D | null; | ||||
| 	roughnessMetallicTexture?: Texture2D | null; | ||||
| 	normalTexture?: Texture2D | null; | ||||
| 	emissiveTexture?: Texture2D | null; | ||||
| 	transmissionCollimationTexture?: Texture2D | null; | ||||
|  | ||||
| 	transparent?: boolean; | ||||
| 	doubleSided?: boolean; | ||||
| } | ||||
| @@ -4,7 +4,8 @@ | ||||
|  * obtain one at http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| import { Camera, Light, Material, Matrix4x4, Mesh, Quaternion, QuaternionObject, Vector3, Vector3Object } from "."; | ||||
| import { Camera, DynamicMaterial, Light, Matrix4x4, Mesh, Quaternion, QuaternionObject, Vector3, Vector3Object } from "."; | ||||
| import { Material } from "../resources"; | ||||
|  | ||||
| export interface NodeProps { | ||||
| 	readonly name?: string; | ||||
| @@ -16,7 +17,7 @@ export interface NodeProps { | ||||
| 	readonly camera?: Camera | null; | ||||
| 	readonly light?: Light | null; | ||||
| 	readonly mesh?: Mesh | null; | ||||
| 	readonly materials?: Material[]; | ||||
| 	readonly materials?: (Material | DynamicMaterial)[]; | ||||
|  | ||||
| 	readonly children?: Node[]; | ||||
| } | ||||
| @@ -38,7 +39,7 @@ export class Node { | ||||
| 	/** shared */ | ||||
| 	_mesh: Mesh | null; | ||||
| 	/** shared */ | ||||
| 	_materials: Material[]; | ||||
| 	_materials: (Material | DynamicMaterial)[]; | ||||
|  | ||||
| 	/** unique */ | ||||
| 	_children: Node[]; | ||||
| @@ -218,13 +219,13 @@ export class Node { | ||||
| 	set mesh(value: Mesh | null) { this._mesh = value; } | ||||
| 	get mesh(): Mesh | null { return this._mesh; } | ||||
|  | ||||
| 	setMaterials(value: readonly Material[]): Node { | ||||
| 	setMaterials(value: readonly (Material | DynamicMaterial)[]): Node { | ||||
| 		this._materials.length = 0; | ||||
| 		this._materials.push(...value); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	getMaterials(res: Material[]): Material[] { | ||||
| 	getMaterials(res: (Material | DynamicMaterial)[]): (Material | DynamicMaterial)[] { | ||||
| 		res.length = 0; | ||||
| 		res.push(...this._materials); | ||||
| 		return res; | ||||
|   | ||||
| @@ -6,8 +6,9 @@ | ||||
|  | ||||
| export * from "./Camera"; | ||||
| export * from "./Color"; | ||||
| export * from "./DynamicMaterial"; | ||||
| export * from "./Light"; | ||||
| export * from "./Material"; | ||||
| export * from "./MaterialProps"; | ||||
| export * from "./Matrix4x4"; | ||||
| export * from "./Mesh"; | ||||
| export * from "./Node"; | ||||
|   | ||||
| @@ -10,8 +10,8 @@ export * from "./shader"; | ||||
|  | ||||
| import { _BinaryWriter as BinaryWriter } from "./_BinaryWriter"; | ||||
| import { _Mapping as Mapping } from "./_Mapping"; | ||||
| import { Camera, Material, Matrix4x4, Node, Scene, Vector3, isDirectionalLight, isPointLight, preOrder } from "./data"; | ||||
| import { IndexBuffer, IndexBufferProps, Texture2D, Texture2DProps, VertexBuffer, VertexBufferProps } from "./resources"; | ||||
| import { Camera, DynamicMaterial, MaterialProps, Matrix4x4, Node, Scene, Vector3, isDirectionalLight, isDynamicMaterial, isPointLight, preOrder } from "./data"; | ||||
| import { IndexBuffer, IndexBufferProps, Material, Texture2D, Texture2DProps, VertexBuffer, VertexBufferProps, isMaterial } from "./resources"; | ||||
| import { GLOBAL_UNIFORMS_SIZE, MATERIAL_UNIFORMS_SIZE, OBJECT_UNIFORMS_SIZE, ShaderFlagKey, ShaderFlags, _createPipeline, _shaderFlagsKey } from "./shader"; | ||||
|  | ||||
| const _matrixOStoWSNormal = new Matrix4x4( | ||||
| @@ -329,6 +329,10 @@ export class Renderer { | ||||
| 		return new IndexBuffer(this, props); | ||||
| 	} | ||||
|  | ||||
| 	createMaterial(props: MaterialProps): Material { | ||||
| 		return new Material(this, props); | ||||
| 	} | ||||
|  | ||||
| 	createTexture(props: Texture2DProps): Texture2D { | ||||
| 		return new Texture2D(this, props); | ||||
| 	} | ||||
| @@ -382,16 +386,18 @@ export class Renderer { | ||||
|  | ||||
| 		this._uniformWriter.clear(); | ||||
|  | ||||
| 		// gather materials | ||||
| 		// gather dynamic materials | ||||
|  | ||||
| 		const materialMapping = new Mapping<Material>(); | ||||
| 		const dynamicMaterialMapping = new Mapping<DynamicMaterial>(); | ||||
| 		for (const node of preOrder(scene._nodes)) { | ||||
| 			for (const material of node._materials) { | ||||
| 				materialMapping.add(material); | ||||
| 				if (isDynamicMaterial(material)) { | ||||
| 					dynamicMaterialMapping.add(material); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		const materialBindGroups = materialMapping.table.map((material) => { | ||||
| 		const dynamicMaterialBindGroups = dynamicMaterialMapping.table.map((material) => { | ||||
| 			const offset = this._uniformWriter._length; | ||||
| 			this._uniformWriter.writeColorF32(material._baseColor); | ||||
| 			this._uniformWriter.writeF32(material._partialCoverage); | ||||
| @@ -541,9 +547,17 @@ export class Renderer { | ||||
| 			for (let si = 0; si < mesh._submeshes.length; ++si) { | ||||
| 				const submesh = mesh._submeshes[si]!; | ||||
| 				const material = object._materials[si]!; | ||||
| 				const { bindGroup: materialBindGroup, offset: materialOffset } = materialBindGroups[materialMapping.get(material)!]!; | ||||
|  | ||||
| 				pass.setBindGroup(1, materialBindGroup, [materialOffset]); | ||||
| 				if (isMaterial(material)) { | ||||
| 					pass.setBindGroup(1, material._bindGroup, [0]); | ||||
| 				} else if (isDynamicMaterial(material)) { | ||||
| 					const { | ||||
| 						bindGroup: materialBindGroup, | ||||
| 						offset: materialOffset | ||||
| 					} = dynamicMaterialBindGroups[dynamicMaterialMapping.get(material)!]!; | ||||
| 					pass.setBindGroup(1, materialBindGroup, [materialOffset]); | ||||
| 				} | ||||
|  | ||||
| 				pass.drawIndexed(submesh.length, 1, submesh.start, 0, 0); | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										169
									
								
								src/resources/Material.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								src/resources/Material.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| /*! | ||||
|  * 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 { Texture2D } from "."; | ||||
| import { Color, MaterialProps } from "../data"; | ||||
| import { Renderer, _BinaryWriter } from "../oktaeder"; | ||||
|  | ||||
| export class Material { | ||||
|  | ||||
| 	declare readonly type: "Material"; | ||||
| 	_renderer: Renderer; | ||||
|  | ||||
| 	_uniformBuffer: GPUBuffer; | ||||
| 	_bindGroup: GPUBindGroup; | ||||
|  | ||||
| 	_name: string; | ||||
|  | ||||
| 	readonly _baseColor: Color; | ||||
| 	readonly _partialCoverage: number; | ||||
| 	readonly _occlusionTextureStrength: number; | ||||
| 	readonly _metallic: number; | ||||
| 	readonly _roughness: number; | ||||
| 	readonly _normalScale: number; | ||||
| 	readonly _emissive: Color; | ||||
| 	readonly _transmission: Color; | ||||
| 	readonly _collimation: number; | ||||
| 	readonly _ior: number; | ||||
|  | ||||
| 	readonly _baseColorPartialCoverageTexture: Texture2D | null; | ||||
| 	readonly _occlusionTexture: Texture2D | null; | ||||
| 	readonly _roughnessMetallicTexture: Texture2D | null; | ||||
| 	readonly _normalTexture: Texture2D | null; | ||||
| 	readonly _emissiveTexture: Texture2D | null; | ||||
| 	readonly _transmissionCollimationTexture: Texture2D | null; | ||||
|  | ||||
| 	readonly _transparent: boolean; | ||||
| 	readonly _doubleSided: boolean; | ||||
|  | ||||
| 	constructor(renderer: Renderer, { | ||||
| 		name = "", | ||||
| 		baseColor, | ||||
| 		partialCoverage = 1, | ||||
| 		occlusionTextureStrength = 1, | ||||
| 		metallic = 1, | ||||
| 		roughness = 1, | ||||
| 		normalScale = 1, | ||||
| 		emissive, | ||||
| 		transmission, | ||||
| 		collimation = 1, | ||||
| 		ior = 1.45, | ||||
| 		baseColorPartialCoverageTexture = null, | ||||
| 		occlusionTexture = null, | ||||
| 		roughnessMetallicTexture = null, | ||||
| 		normalTexture = null, | ||||
| 		emissiveTexture = null, | ||||
| 		transmissionCollimationTexture = null, | ||||
| 		transparent = false, | ||||
| 		doubleSided = false, | ||||
| 	}: MaterialProps) { | ||||
| 		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._transmission = transmission !== undefined ? Color.fromObject(transmission) : Color.black(); | ||||
| 		this._collimation = collimation; | ||||
| 		this._ior = ior; | ||||
|  | ||||
| 		this._baseColorPartialCoverageTexture = baseColorPartialCoverageTexture; | ||||
| 		this._occlusionTexture = occlusionTexture; | ||||
| 		this._roughnessMetallicTexture = roughnessMetallicTexture; | ||||
| 		this._normalTexture = normalTexture; | ||||
| 		this._emissiveTexture = emissiveTexture; | ||||
| 		this._transmissionCollimationTexture = transmissionCollimationTexture; | ||||
|  | ||||
| 		this._transparent = transparent; | ||||
| 		this._doubleSided = doubleSided; | ||||
|  | ||||
| 		this._uniformBuffer = renderer._device.createBuffer({ | ||||
| 			usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM, | ||||
| 			size: 64, | ||||
| 			label: name, | ||||
| 		}); | ||||
|  | ||||
| 		const writer = new _BinaryWriter(64); | ||||
| 		writer.writeColorF32(this._baseColor); | ||||
| 		writer.writeF32(this._partialCoverage); | ||||
| 		writer.writeColorF32(this._transmission); | ||||
| 		writer.writeF32(this._collimation); | ||||
| 		writer.writeF32(this._occlusionTextureStrength); | ||||
| 		writer.writeF32(this._roughness); | ||||
| 		writer.writeF32(this._metallic); | ||||
| 		writer.writeF32(this._normalScale); | ||||
| 		writer.writeColorF32(this._emissive); | ||||
| 		writer.writeF32(this._ior); | ||||
|  | ||||
| 		renderer._device.queue.writeBuffer(this._uniformBuffer, 0, writer.subarray); | ||||
|  | ||||
| 		this._bindGroup = renderer._device.createBindGroup({ | ||||
| 			layout: renderer._materialBindGroupLayout, | ||||
| 			entries: [ | ||||
| 				{ binding: 0, resource: { buffer: this._uniformBuffer, size: 64 } }, | ||||
| 				{ binding: 1, resource: renderer._sampler }, | ||||
| 				{ binding: 2, resource: this._baseColorPartialCoverageTexture?._textureView ?? renderer._textureWhite._textureView }, | ||||
| 				{ binding: 3, resource: this._occlusionTexture?._textureView ?? renderer._textureWhite._textureView }, | ||||
| 				{ binding: 4, resource: this._roughnessMetallicTexture?._textureView ?? renderer._textureWhite._textureView }, | ||||
| 				{ binding: 5, resource: this._normalTexture?._textureView ?? renderer._textureNormal._textureView }, | ||||
| 				{ binding: 6, resource: this._emissiveTexture?._textureView ?? renderer._textureWhite._textureView }, | ||||
| 				{ binding: 7, resource: this._transmissionCollimationTexture?._textureView ?? renderer._textureBlack._textureView }, | ||||
| 			], | ||||
| 			label: name, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Destroys owned GPU resources. The index buffer should not be used after | ||||
| 	 * calling this method. | ||||
| 	 * @returns `this` for chaining | ||||
| 	 */ | ||||
| 	dispose(): Material { | ||||
| 		this._uniformBuffer.destroy(); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	getBaseColor(res: Color): Color { | ||||
| 		return res.setObject(this._baseColor); | ||||
| 	} | ||||
|  | ||||
| 	get partialCoverage(): number { return this._partialCoverage; } | ||||
| 	get occlusionTextureStrength(): number { return this._occlusionTextureStrength; } | ||||
| 	get metallic(): number { return this._metallic; } | ||||
| 	get roughness(): number { return this._roughness; } | ||||
| 	get normalScale(): number { return this._normalScale; } | ||||
|  | ||||
| 	getEmissive(res: Color): Color { | ||||
| 		return res.setObject(this._emissive); | ||||
| 	} | ||||
|  | ||||
| 	getTransmission(res: Color): Color { | ||||
| 		return res.setObject(this._transmission); | ||||
| 	} | ||||
|  | ||||
| 	get collimation(): number { return this._collimation; } | ||||
| 	get ior(): number { return this._ior; } | ||||
| 	get baseColorPartialCoverageTexture(): Texture2D | null { return this._baseColorPartialCoverageTexture; } | ||||
| 	get occlusionTexture(): Texture2D | null { return this._occlusionTexture; } | ||||
| 	get roughnessMetallicTexture(): Texture2D | null { return this._roughnessMetallicTexture; } | ||||
| 	get normalTexture(): Texture2D | null { return this._normalTexture; } | ||||
| 	get emissiveTexture(): Texture2D | null { return this._emissiveTexture; } | ||||
| 	get transmissionCollimationTexture(): Texture2D | null { return this._transmissionCollimationTexture; } | ||||
|  | ||||
| 	get transparent(): boolean { return this._transparent; } | ||||
| 	get doubleSided(): boolean { return this._doubleSided; } | ||||
| } | ||||
|  | ||||
| Object.defineProperty(Material.prototype, "type", { value: "Material" }); | ||||
|  | ||||
| export function isMaterial(value: unknown): value is Material { | ||||
| 	return Boolean(value) && (value as Material).type === "Material"; | ||||
| } | ||||
| @@ -5,5 +5,6 @@ | ||||
|  */ | ||||
|  | ||||
| export * from "./IndexBuffer"; | ||||
| export * from "./Material"; | ||||
| export * from "./Texture2D"; | ||||
| export * from "./VertexBuffer"; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user