2023-07-26 21:13:16 +00:00
|
|
|
|
/*!
|
|
|
|
|
* 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/.
|
|
|
|
|
*/
|
|
|
|
|
|
2023-08-02 18:00:15 +00:00
|
|
|
|
export * from "./_BinaryWriter";
|
|
|
|
|
export * from "./shader";
|
|
|
|
|
|
|
|
|
|
import { _BinaryWriter as BinaryWriter } from "./_BinaryWriter";
|
2023-08-03 18:05:28 +00:00
|
|
|
|
import { _Mapping as Mapping } from "./_Mapping";
|
2023-08-05 13:41:10 +00:00
|
|
|
|
import { Camera, Material, Matrix4x4, Node, Scene, Vector3, isDirectionalLight, isPointLight, preOrder } from "./data";
|
2023-08-03 18:05:28 +00:00
|
|
|
|
import { IndexBuffer, IndexBufferProps, Texture2D, Texture2DProps, VertexBuffer, VertexBufferProps } from "./resources";
|
2023-08-02 18:00:15 +00:00
|
|
|
|
import { ShaderFlagKey, ShaderFlags, createPipeline, shaderFlagsKey } from "./shader";
|
2023-07-29 00:01:22 +00:00
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
const _matrixOStoWSNormal = new Matrix4x4(
|
2023-08-04 19:45:10 +00:00
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
);
|
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
const _matrixWStoVS = new Matrix4x4(
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const _matrixVStoCS = new Matrix4x4(
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
NaN, NaN, NaN, NaN,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const _directionWS = new Vector3(NaN, NaN, NaN);
|
|
|
|
|
const _positionWS = new Vector3(NaN, NaN, NaN);
|
|
|
|
|
|
2023-07-29 00:01:22 +00:00
|
|
|
|
export class Renderer {
|
|
|
|
|
|
|
|
|
|
_adapter: GPUAdapter;
|
|
|
|
|
_device: GPUDevice;
|
|
|
|
|
_context: GPUCanvasContext;
|
|
|
|
|
_format: GPUTextureFormat;
|
|
|
|
|
|
|
|
|
|
/** 1×1 rgba8unorm texture of [255, 255, 255, 255] */
|
|
|
|
|
_textureWhite: Texture2D;
|
|
|
|
|
/** 1×1 rgba8unorm texture of [0, 0, 0, 255] */
|
|
|
|
|
_textureBlack: Texture2D;
|
|
|
|
|
/** 1×1 rgba8unorm texture of [128, 128, 128, 255] */
|
|
|
|
|
_textureNormal: Texture2D;
|
|
|
|
|
|
2023-07-29 20:06:46 +00:00
|
|
|
|
_depthBuffer: Texture2D;
|
|
|
|
|
|
2023-08-02 18:00:15 +00:00
|
|
|
|
_globalBindGroupLayout: GPUBindGroupLayout;
|
|
|
|
|
_materialBindGroupLayout: GPUBindGroupLayout;
|
|
|
|
|
_objectBindGroupLayout: GPUBindGroupLayout;
|
|
|
|
|
_pipelineLayout: GPUPipelineLayout;
|
|
|
|
|
|
|
|
|
|
_pipelineCache: Map<ShaderFlagKey, GPURenderPipeline>;
|
|
|
|
|
|
|
|
|
|
_uniformWriter: BinaryWriter;
|
2023-08-03 18:05:28 +00:00
|
|
|
|
_uniformBuffer: GPUBuffer;
|
2023-08-05 13:41:10 +00:00
|
|
|
|
|
|
|
|
|
_lightWriter: BinaryWriter;
|
2023-08-03 18:05:28 +00:00
|
|
|
|
_pointLightBuffer: GPUBuffer;
|
2023-08-05 13:41:10 +00:00
|
|
|
|
_directionalLightBuffer: GPUBuffer;
|
2023-08-03 18:05:28 +00:00
|
|
|
|
|
|
|
|
|
_sampler: GPUSampler;
|
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
_globalBindGroup: GPUBindGroup;
|
2023-08-03 18:05:28 +00:00
|
|
|
|
_objectBindGroup: GPUBindGroup;
|
2023-08-02 18:00:15 +00:00
|
|
|
|
|
2023-07-29 00:01:22 +00:00
|
|
|
|
/**
|
|
|
|
|
* This constructor is intended primarily for internal use. Consider using
|
|
|
|
|
* `Renderer.createIndexBuffer` instead.
|
|
|
|
|
*/
|
2023-08-02 18:00:15 +00:00
|
|
|
|
private constructor(
|
2023-07-29 00:01:22 +00:00
|
|
|
|
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, {
|
2023-07-29 20:06:46 +00:00
|
|
|
|
name: "White",
|
2023-07-29 00:01:22 +00:00
|
|
|
|
width: 1,
|
|
|
|
|
height: 1,
|
|
|
|
|
format: "linear",
|
|
|
|
|
});
|
|
|
|
|
this._textureWhite.writeFull(new Uint8Array([255, 255, 255, 255]));
|
|
|
|
|
|
|
|
|
|
this._textureBlack = new Texture2D(this, {
|
2023-07-29 20:06:46 +00:00
|
|
|
|
name: "Black",
|
2023-07-29 00:01:22 +00:00
|
|
|
|
width: 1,
|
|
|
|
|
height: 1,
|
|
|
|
|
format: "linear",
|
|
|
|
|
});
|
|
|
|
|
this._textureBlack.writeFull(new Uint8Array([0, 0, 0, 255]));
|
|
|
|
|
|
|
|
|
|
this._textureNormal = new Texture2D(this, {
|
2023-07-29 20:06:46 +00:00
|
|
|
|
name: "Normal",
|
2023-07-29 00:01:22 +00:00
|
|
|
|
width: 1,
|
|
|
|
|
height: 1,
|
|
|
|
|
format: "linear",
|
|
|
|
|
});
|
|
|
|
|
this._textureNormal.writeFull(new Uint8Array([128, 128, 128, 255]));
|
2023-07-29 20:06:46 +00:00
|
|
|
|
|
|
|
|
|
const framebufferTexture = this._context.getCurrentTexture();
|
|
|
|
|
this._depthBuffer = new Texture2D(this, {
|
|
|
|
|
name: "Depth Buffer",
|
|
|
|
|
width: framebufferTexture.width,
|
|
|
|
|
height: framebufferTexture.height,
|
|
|
|
|
format: "depth",
|
|
|
|
|
});
|
2023-08-02 18:00:15 +00:00
|
|
|
|
|
|
|
|
|
this._globalBindGroupLayout = device.createBindGroupLayout({
|
|
|
|
|
entries: [
|
|
|
|
|
{
|
|
|
|
|
binding: 0,
|
|
|
|
|
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
|
|
|
buffer: {
|
|
|
|
|
hasDynamicOffset: true,
|
|
|
|
|
type: "uniform",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
binding: 1,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
buffer: {
|
|
|
|
|
type: "read-only-storage",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
binding: 2,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
buffer: {
|
|
|
|
|
type: "read-only-storage",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
label: "Global",
|
|
|
|
|
});
|
|
|
|
|
this._materialBindGroupLayout = device.createBindGroupLayout({
|
|
|
|
|
entries: [
|
|
|
|
|
{
|
|
|
|
|
binding: 0,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
buffer: {
|
|
|
|
|
hasDynamicOffset: true,
|
|
|
|
|
type: "uniform",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
binding: 1,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
sampler: { type: "filtering" },
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
binding: 2,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
texture: {
|
|
|
|
|
sampleType: "float",
|
|
|
|
|
viewDimension: "2d",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
binding: 3,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
texture: {
|
|
|
|
|
sampleType: "float",
|
|
|
|
|
viewDimension: "2d",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
binding: 4,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
texture: {
|
|
|
|
|
sampleType: "float",
|
|
|
|
|
viewDimension: "2d",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
binding: 5,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
texture: {
|
|
|
|
|
sampleType: "float",
|
|
|
|
|
viewDimension: "2d",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
binding: 6,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
texture: {
|
|
|
|
|
sampleType: "float",
|
|
|
|
|
viewDimension: "2d",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
binding: 7,
|
|
|
|
|
visibility: GPUShaderStage.FRAGMENT,
|
|
|
|
|
texture: {
|
|
|
|
|
sampleType: "float",
|
|
|
|
|
viewDimension: "2d",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
label: "Material",
|
|
|
|
|
});
|
|
|
|
|
this._objectBindGroupLayout = device.createBindGroupLayout({
|
|
|
|
|
entries: [
|
|
|
|
|
{
|
|
|
|
|
binding: 0,
|
|
|
|
|
visibility: GPUShaderStage.VERTEX,
|
|
|
|
|
buffer: {
|
|
|
|
|
hasDynamicOffset: true,
|
|
|
|
|
type: "uniform",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
label: "Object",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._pipelineLayout = device.createPipelineLayout({
|
|
|
|
|
bindGroupLayouts: [
|
|
|
|
|
this._globalBindGroupLayout,
|
|
|
|
|
this._materialBindGroupLayout,
|
|
|
|
|
this._objectBindGroupLayout,
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._pipelineCache = new Map();
|
|
|
|
|
|
|
|
|
|
this._uniformWriter = new BinaryWriter();
|
2023-08-03 18:05:28 +00:00
|
|
|
|
this._uniformBuffer = device.createBuffer({
|
|
|
|
|
size: 4 * 1024 * 1024,
|
|
|
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
|
|
|
|
|
label: "Uniform",
|
|
|
|
|
});
|
2023-08-05 13:41:10 +00:00
|
|
|
|
|
|
|
|
|
this._lightWriter = new BinaryWriter();
|
|
|
|
|
this._pointLightBuffer = device.createBuffer({
|
2023-08-03 18:05:28 +00:00
|
|
|
|
size: 1024 * 32,
|
|
|
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
|
|
|
|
|
});
|
2023-08-05 13:41:10 +00:00
|
|
|
|
this._directionalLightBuffer = device.createBuffer({
|
2023-08-03 18:05:28 +00:00
|
|
|
|
size: 1024 * 32,
|
|
|
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._sampler = device.createSampler({
|
|
|
|
|
addressModeU: "repeat",
|
|
|
|
|
addressModeV: "repeat",
|
|
|
|
|
addressModeW: "repeat",
|
|
|
|
|
magFilter: "linear",
|
|
|
|
|
minFilter: "linear",
|
|
|
|
|
mipmapFilter: "linear",
|
|
|
|
|
maxAnisotropy: 16,
|
|
|
|
|
});
|
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
this._globalBindGroup = device.createBindGroup({
|
|
|
|
|
layout: this._globalBindGroupLayout,
|
|
|
|
|
entries: [
|
|
|
|
|
{ binding: 0, resource: { buffer: this._uniformBuffer } },
|
|
|
|
|
{ binding: 1, resource: { buffer: this._pointLightBuffer } },
|
|
|
|
|
{ binding: 2, resource: { buffer: this._directionalLightBuffer } },
|
|
|
|
|
],
|
|
|
|
|
label: "Global",
|
|
|
|
|
});
|
2023-08-03 18:05:28 +00:00
|
|
|
|
this._objectBindGroup = device.createBindGroup({
|
|
|
|
|
layout: this._objectBindGroupLayout,
|
|
|
|
|
entries: [
|
|
|
|
|
{ binding: 0, resource: { buffer: this._uniformBuffer } },
|
|
|
|
|
],
|
|
|
|
|
label: "Object",
|
|
|
|
|
});
|
2023-07-29 00:01:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-02 18:00:15 +00:00
|
|
|
|
static async init(canvas: HTMLCanvasElement): Promise<Renderer> {
|
2023-07-29 00:01:22 +00:00
|
|
|
|
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 });
|
2023-08-02 18:00:15 +00:00
|
|
|
|
|
|
|
|
|
return new Renderer(adapter, device, context, format);
|
2023-07-29 00:01:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Disposes resources internal to the renderer. Doesn't dispose any objects
|
|
|
|
|
* created with this renderer. The renderer should not be used after calling
|
|
|
|
|
* this method.
|
|
|
|
|
* @returns `this` for chaining
|
|
|
|
|
*/
|
|
|
|
|
dispose(): Renderer {
|
|
|
|
|
this._textureWhite.dispose();
|
|
|
|
|
this._textureBlack.dispose();
|
|
|
|
|
this._textureNormal.dispose();
|
2023-07-29 20:06:46 +00:00
|
|
|
|
this._depthBuffer.dispose();
|
2023-08-03 18:05:28 +00:00
|
|
|
|
this._uniformBuffer.destroy();
|
|
|
|
|
this._directionalLightBuffer.destroy();
|
|
|
|
|
this._pointLightBuffer.destroy();
|
2023-07-29 00:01:22 +00:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createIndexBuffer(props: IndexBufferProps): IndexBuffer {
|
|
|
|
|
return new IndexBuffer(this, props);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createTexture(props: Texture2DProps): Texture2D {
|
|
|
|
|
return new Texture2D(this, props);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createVertexBuffer(props: VertexBufferProps): VertexBuffer {
|
|
|
|
|
return new VertexBuffer(this, props);
|
|
|
|
|
}
|
2023-07-29 20:06:46 +00:00
|
|
|
|
|
2023-08-02 18:00:15 +00:00
|
|
|
|
_getOrCreatePipeline(flags: ShaderFlags): GPURenderPipeline {
|
|
|
|
|
const key = shaderFlagsKey(flags);
|
|
|
|
|
|
|
|
|
|
let pipeline = this._pipelineCache.get(key);
|
|
|
|
|
if (pipeline !== undefined) {
|
|
|
|
|
return pipeline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pipeline = createPipeline(this, flags);
|
|
|
|
|
this._pipelineCache.set(key, pipeline);
|
|
|
|
|
return pipeline;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-29 20:06:46 +00:00
|
|
|
|
render(scene: Scene, camera: Camera): Renderer {
|
2023-08-05 13:41:10 +00:00
|
|
|
|
const cameraNode = camera._node;
|
|
|
|
|
if (cameraNode === null) {
|
|
|
|
|
throw new Error(`Cannot render with a detached camera. Camera [${camera._name}] is not attached to a node.`);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-29 20:06:46 +00:00
|
|
|
|
const { width, height } = this._context.getCurrentTexture();
|
|
|
|
|
if (this._depthBuffer.width !== width || this._depthBuffer.height !== height) {
|
|
|
|
|
this._depthBuffer.resizeDiscard({
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const encoder = this._device.createCommandEncoder();
|
|
|
|
|
|
|
|
|
|
const pass = encoder.beginRenderPass({
|
|
|
|
|
colorAttachments: [{
|
|
|
|
|
view: this._context.getCurrentTexture().createView(),
|
|
|
|
|
loadOp: "clear",
|
|
|
|
|
storeOp: "store",
|
|
|
|
|
}],
|
|
|
|
|
depthStencilAttachment: {
|
|
|
|
|
view: this._depthBuffer._textureView,
|
|
|
|
|
depthClearValue: 0,
|
|
|
|
|
depthLoadOp: "clear",
|
|
|
|
|
depthStoreOp: "store",
|
|
|
|
|
},
|
|
|
|
|
});
|
2023-07-30 19:28:48 +00:00
|
|
|
|
|
2023-08-03 18:05:28 +00:00
|
|
|
|
this._uniformWriter.clear();
|
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
// gather materials
|
2023-08-03 18:05:28 +00:00
|
|
|
|
|
|
|
|
|
const materialMapping = new Mapping<Material>();
|
|
|
|
|
for (const node of preOrder(scene._nodes)) {
|
|
|
|
|
for (const material of node._materials) {
|
|
|
|
|
materialMapping.add(material);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const materialBindGroups = materialMapping.table.map((material) => {
|
|
|
|
|
const offset = this._uniformWriter._length;
|
|
|
|
|
this._uniformWriter.writeColorF32(material._baseColor);
|
|
|
|
|
this._uniformWriter.writeF32(material._partialCoverage);
|
|
|
|
|
this._uniformWriter.writeColorF32(material._transmission);
|
|
|
|
|
this._uniformWriter.writeF32(material._collimation);
|
|
|
|
|
this._uniformWriter.writeF32(material._occlusionTextureStrength);
|
|
|
|
|
this._uniformWriter.writeF32(material._roughness);
|
|
|
|
|
this._uniformWriter.writeF32(material._metallic);
|
|
|
|
|
this._uniformWriter.writeF32(material._normalScale);
|
|
|
|
|
this._uniformWriter.writeColorF32(material._emissive);
|
|
|
|
|
this._uniformWriter.writeF32(material._ior);
|
|
|
|
|
|
|
|
|
|
const bindGroup = this._device.createBindGroup({
|
|
|
|
|
layout: this._materialBindGroupLayout,
|
|
|
|
|
entries: [
|
|
|
|
|
{ binding: 0, resource: { buffer: this._uniformBuffer } },
|
|
|
|
|
{ binding: 1, resource: this._sampler },
|
|
|
|
|
{ binding: 2, resource: material._baseColorPartialCoverageTexture?._textureView ?? this._textureWhite._textureView },
|
|
|
|
|
{ binding: 3, resource: material._occlusionTexture?._textureView ?? this._textureWhite._textureView },
|
|
|
|
|
{ binding: 4, resource: material._roughnessMetallicTexture?._textureView ?? this._textureWhite._textureView },
|
|
|
|
|
{ binding: 5, resource: material._normalTexture?._textureView ?? this._textureNormal._textureView },
|
|
|
|
|
{ binding: 6, resource: material._emissiveTexture?._textureView ?? this._textureWhite._textureView },
|
|
|
|
|
{ binding: 7, resource: material._transmissionCollimationTexture?._textureView ?? this._textureBlack._textureView },
|
|
|
|
|
],
|
|
|
|
|
label: material._name,
|
|
|
|
|
});
|
|
|
|
|
return { offset, bindGroup };
|
|
|
|
|
});
|
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
// gather objects
|
2023-08-03 18:05:28 +00:00
|
|
|
|
|
|
|
|
|
const objectMapping = new Mapping<Node>();
|
|
|
|
|
for (const node of preOrder(scene._nodes)) {
|
|
|
|
|
if (node._mesh !== null) {
|
|
|
|
|
objectMapping.add(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const objectOffsets = objectMapping.table.map((object) => {
|
|
|
|
|
const offset = this._uniformWriter._length;
|
|
|
|
|
object._updateWorldMatrix();
|
|
|
|
|
this._uniformWriter.writeMatrix4x4(object._worldMatrix);
|
2023-08-05 13:41:10 +00:00
|
|
|
|
this._uniformWriter.writeMatrix4x4(_matrixOStoWSNormal.setObject(object._worldMatrix).inverseTransposeAffine());
|
2023-08-03 18:05:28 +00:00
|
|
|
|
return offset;
|
|
|
|
|
});
|
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
// gather point lights
|
|
|
|
|
|
|
|
|
|
this._lightWriter.clear();
|
|
|
|
|
let pointLightCount = 0;
|
|
|
|
|
for (const node of preOrder(scene._nodes)) {
|
|
|
|
|
const light = node._light;
|
|
|
|
|
if (!isPointLight(light)) continue;
|
|
|
|
|
|
|
|
|
|
node._updateWorldMatrix();
|
|
|
|
|
_positionWS.set(node._worldMatrix.tx, node._worldMatrix.ty, node._worldMatrix.tz);
|
|
|
|
|
|
|
|
|
|
this._lightWriter.writeVector3(_positionWS);
|
|
|
|
|
this._lightWriter.writeU32(0);
|
|
|
|
|
this._lightWriter.writeColorF32(light._color);
|
|
|
|
|
this._lightWriter.writeU32(0);
|
|
|
|
|
|
|
|
|
|
pointLightCount += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._device.queue.writeBuffer(this._pointLightBuffer, 0, this._lightWriter.subarray);
|
|
|
|
|
|
|
|
|
|
// gather directional lights
|
|
|
|
|
|
|
|
|
|
this._lightWriter.clear();
|
|
|
|
|
let directionalLightCount = 0;
|
|
|
|
|
for (const node of preOrder(scene._nodes)) {
|
|
|
|
|
const light = node._light;
|
|
|
|
|
if (!isDirectionalLight(light)) continue;
|
|
|
|
|
|
|
|
|
|
node._updateWorldMatrix();
|
|
|
|
|
_directionWS.set(-node._worldMatrix.kx, -node._worldMatrix.ky, -node._worldMatrix.kz);
|
|
|
|
|
_directionWS.normalize();
|
|
|
|
|
|
|
|
|
|
this._lightWriter.writeVector3(_directionWS);
|
|
|
|
|
this._lightWriter.writeU32(0);
|
|
|
|
|
this._lightWriter.writeColorF32(light._color);
|
|
|
|
|
this._lightWriter.writeU32(0);
|
|
|
|
|
|
|
|
|
|
directionalLightCount += 1;
|
|
|
|
|
}
|
2023-08-04 19:45:10 +00:00
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
this._device.queue.writeBuffer(this._directionalLightBuffer, 0, this._lightWriter.subarray);
|
2023-08-04 19:45:10 +00:00
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
// global uniforms
|
2023-08-04 19:45:10 +00:00
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
const globalUniformsOffset = this._uniformWriter._length;
|
|
|
|
|
cameraNode._updateWorldMatrix();
|
|
|
|
|
_matrixWStoVS.setObject(cameraNode._worldMatrix).inverseAffine();
|
|
|
|
|
camera.computeProjectionMatrix(width / height, _matrixVStoCS);
|
2023-08-04 19:45:10 +00:00
|
|
|
|
|
2023-08-05 13:41:10 +00:00
|
|
|
|
this._uniformWriter.writeMatrix4x4(_matrixWStoVS);
|
|
|
|
|
this._uniformWriter.writeMatrix4x4(_matrixVStoCS);
|
|
|
|
|
this._uniformWriter.writeColorF32(scene._ambientLight);
|
|
|
|
|
this._uniformWriter.writeU32(pointLightCount);
|
|
|
|
|
this._uniformWriter.writeU32(directionalLightCount);
|
|
|
|
|
this._uniformWriter.writeU32(0);
|
|
|
|
|
this._uniformWriter.writeU32(0);
|
|
|
|
|
this._uniformWriter.writeU32(0);
|
|
|
|
|
|
|
|
|
|
// upload uniforms
|
|
|
|
|
|
|
|
|
|
this._device.queue.writeBuffer(this._uniformBuffer, 0, this._uniformWriter.subarray);
|
|
|
|
|
|
|
|
|
|
// render
|
|
|
|
|
|
|
|
|
|
pass.setBindGroup(0, this._globalBindGroup, [globalUniformsOffset]);
|
|
|
|
|
|
|
|
|
|
for (let oi = 0; oi < objectMapping.table.length; ++oi) {
|
|
|
|
|
const object = objectMapping.table[oi]!;
|
|
|
|
|
const objectOffset = objectOffsets[oi]!;
|
|
|
|
|
const mesh = object.mesh!;
|
|
|
|
|
const { _vertexBuffer: vertexBuffer, _indexBuffer: indexBuffer } = mesh;
|
|
|
|
|
|
|
|
|
|
const flags: ShaderFlags = {
|
|
|
|
|
texCoord: vertexBuffer._texCoordBuffer !== null,
|
|
|
|
|
lightTexCoord: vertexBuffer._lightTexCoordBuffer !== null,
|
|
|
|
|
normal: vertexBuffer._normalBuffer !== null,
|
|
|
|
|
tangent: vertexBuffer._tangentBuffer !== null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const renderPipeline = this._getOrCreatePipeline(flags);
|
|
|
|
|
|
|
|
|
|
pass.setPipeline(renderPipeline);
|
|
|
|
|
|
|
|
|
|
pass.setVertexBuffer(0, vertexBuffer._positionBuffer);
|
|
|
|
|
pass.setVertexBuffer(1, vertexBuffer._texCoordBuffer);
|
|
|
|
|
pass.setVertexBuffer(2, vertexBuffer._lightTexCoordBuffer);
|
|
|
|
|
pass.setVertexBuffer(3, vertexBuffer._normalBuffer);
|
|
|
|
|
pass.setVertexBuffer(4, vertexBuffer._tangentBuffer);
|
|
|
|
|
pass.setIndexBuffer(indexBuffer._buffer, indexBuffer._indexFormat);
|
|
|
|
|
|
|
|
|
|
pass.setBindGroup(2, this._objectBindGroup, [objectOffset]);
|
|
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
pass.drawIndexed(submesh.length, 1, submesh.start, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-30 19:28:48 +00:00
|
|
|
|
|
2023-07-29 20:06:46 +00:00
|
|
|
|
pass.end();
|
|
|
|
|
|
|
|
|
|
const commandBuffer = encoder.finish();
|
|
|
|
|
this._device.queue.submit([commandBuffer]);
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
2023-07-29 00:01:22 +00:00
|
|
|
|
}
|