Render continuously and optimize the glTF viewer
Remove vsync and dirty-flag frame gating, optimize PBR sampling and lighting, add a direct-to-canvas loadout, and preserve camera state on viewer resize. Add regression coverage and update rendering documentation. Amp-Thread-ID: https://ampcode.com/threads/T-01a0b382-1e79-712b-abd1-d29201c9bb5e Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
+214
-68
@@ -230,6 +230,9 @@ function pbrShader(
|
||||
transmission: boolean,
|
||||
textureInfo: ArrayLike<number>,
|
||||
extensionInfo: ArrayLike<number>,
|
||||
sharedOcclusion: boolean,
|
||||
lightMask: number,
|
||||
directToCanvas: boolean,
|
||||
) {
|
||||
const names = [
|
||||
"baseTexture",
|
||||
@@ -306,11 +309,11 @@ struct VertexOutput {
|
||||
@group(0) @binding(5) var<storage, read> meshInfo: array<u32>;
|
||||
@group(0) @binding(6) var<storage, read> materials: array<vec4<f32>>;
|
||||
@group(0) @binding(7) var<storage, read> cameraMatrices: array<vec4<f32>>;
|
||||
@group(0) @binding(8) var<storage, read> pointLights: array<vec4<f32>>;
|
||||
@group(0) @binding(9) var<storage, read> spotLights: array<vec4<f32>>;
|
||||
@group(0) @binding(10) var<storage, read> directionalLights: array<vec4<f32>>;
|
||||
${lightMask & 1 ? "@group(0) @binding(8) var<storage, read> pointLights: array<vec4<f32>>;" : ""}
|
||||
${lightMask & 2 ? "@group(0) @binding(9) var<storage, read> spotLights: array<vec4<f32>>;" : ""}
|
||||
${lightMask & 4 ? "@group(0) @binding(10) var<storage, read> directionalLights: array<vec4<f32>>;" : ""}
|
||||
${mask ? "@group(1) @binding(0) var materialSampler: sampler;" : ""}
|
||||
${names.map((name, slot) => (texture(slot) ? `@group(1) @binding(${slot + 1}) var ${name}: texture_2d<f32>;` : "")).join("\n")}
|
||||
${names.map((name, slot) => (texture(slot) && !(slot === 4 && sharedOcclusion) ? `@group(1) @binding(${slot + 1}) var ${name}: texture_2d<f32>;` : "")).join("\n")}
|
||||
${transmission ? "@group(2) @binding(0) var sceneColor: texture_2d<f32>;\n@group(2) @binding(1) var sceneSampler: sampler;" : ""}
|
||||
|
||||
fn rotate(rotor: vec4<f32>, value: vec3<f32>) -> vec3<f32> {
|
||||
@@ -357,11 +360,10 @@ fn pow5(value: f32) -> f32 {
|
||||
return squared * squared * value;
|
||||
}
|
||||
|
||||
fn directContribution(normal: vec3<f32>, view: vec3<f32>, lightDirection: vec3<f32>, radiance: vec3<f32>, baseColor: vec3<f32>, metallic: f32, roughness: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||
fn directContribution(normal: vec3<f32>, view: vec3<f32>, lightDirection: vec3<f32>, radiance: vec3<f32>, diffuseColor: vec3<f32>, nDotV: f32, roughness: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||
let nDotL = max(dot(normal, lightDirection), 0.0);
|
||||
if (nDotL <= 0.0) { return vec3(0.0); }
|
||||
let halfVector = normalize(lightDirection + view);
|
||||
let nDotV = max(dot(normal, view), 0.0001);
|
||||
let nDotH = max(dot(normal, halfVector), 0.0);
|
||||
let vDotH = max(dot(view, halfVector), 0.0);
|
||||
let fresnel = f0 + (vec3(1.0) - f0) * pow5(1.0 - vDotH);
|
||||
@@ -372,11 +374,14 @@ fn directContribution(normal: vec3<f32>, view: vec3<f32>, lightDirection: vec3<f
|
||||
let geometryV = nDotV / mix(nDotV, 1.0, alpha * 0.5);
|
||||
let geometryL = nDotL / mix(nDotL, 1.0, alpha * 0.5);
|
||||
let specular = distribution * geometryV * geometryL * fresnel / max(4.0 * nDotV * nDotL, 0.0001);
|
||||
let diffuse = baseColor * (vec3(1.0) - fresnel) * (1.0 - metallic) / 3.14159265;
|
||||
let diffuse = diffuseColor * (vec3(1.0) - fresnel);
|
||||
return (diffuse + specular) * radiance * nDotL;
|
||||
}
|
||||
|
||||
fn pointContribution(index: u32, normal: vec3<f32>, view: vec3<f32>, world: vec3<f32>, baseColor: vec3<f32>, metallic: f32, roughness: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||
${
|
||||
lightMask & 1
|
||||
? `
|
||||
fn pointContribution(index: u32, normal: vec3<f32>, view: vec3<f32>, world: vec3<f32>, diffuseColor: vec3<f32>, nDotV: f32, roughness: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||
let colorIntensity = pointLights[index * 2u];
|
||||
let state = pointLights[index * 2u + 1u];
|
||||
if (state.y == 0.0 || state.x <= 0.0) { return vec3(0.0); }
|
||||
@@ -385,10 +390,16 @@ fn pointContribution(index: u32, normal: vec3<f32>, view: vec3<f32>, world: vec3
|
||||
let falloff = max(1.0 - distance / state.x, 0.0);
|
||||
let radiance = colorIntensity.rgb * colorIntensity.a * falloff * falloff * 0.08;
|
||||
let sourceRoughness = clamp(roughness + max(state.z, 0.0) / max(distance, 0.0001), roughness, 1.0);
|
||||
return directContribution(normal, view, offset / max(distance, 0.0001), radiance, baseColor, metallic, sourceRoughness, f0);
|
||||
return directContribution(normal, view, offset / max(distance, 0.0001), radiance, diffuseColor, nDotV, sourceRoughness, f0);
|
||||
}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
fn spotContribution(index: u32, normal: vec3<f32>, view: vec3<f32>, world: vec3<f32>, baseColor: vec3<f32>, metallic: f32, roughness: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||
${
|
||||
lightMask & 2
|
||||
? `
|
||||
fn spotContribution(index: u32, normal: vec3<f32>, view: vec3<f32>, world: vec3<f32>, diffuseColor: vec3<f32>, nDotV: f32, roughness: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||
let colorIntensity = spotLights[index * 2u];
|
||||
let state = spotLights[index * 2u + 1u];
|
||||
if (state.w == 0.0 || state.x <= 0.0) { return vec3(0.0); }
|
||||
@@ -398,16 +409,27 @@ fn spotContribution(index: u32, normal: vec3<f32>, view: vec3<f32>, world: vec3<
|
||||
let cone = smoothstep(cos(state.z), cos(state.y), dot(direction, fromLight / max(distance, 0.0001)));
|
||||
let falloff = max(1.0 - distance / state.x, 0.0);
|
||||
let radiance = colorIntensity.rgb * colorIntensity.a * cone * falloff * falloff * 0.08;
|
||||
return directContribution(normal, view, -fromLight / max(distance, 0.0001), radiance, baseColor, metallic, roughness, f0);
|
||||
return directContribution(normal, view, -fromLight / max(distance, 0.0001), radiance, diffuseColor, nDotV, roughness, f0);
|
||||
}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
fn directionalContribution(index: u32, normal: vec3<f32>, view: vec3<f32>, baseColor: vec3<f32>, metallic: f32, roughness: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||
${
|
||||
lightMask & 4
|
||||
? `
|
||||
fn directionalContribution(index: u32, normal: vec3<f32>, view: vec3<f32>, diffuseColor: vec3<f32>, nDotV: f32, roughness: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||
let colorIntensity = directionalLights[index * 2u];
|
||||
let state = directionalLights[index * 2u + 1u];
|
||||
if (state.x == 0.0) { return vec3(0.0); }
|
||||
let lightDirection = normalize(rotate(rotors[index], vec3<f32>(0.0, 0.0, 1.0)));
|
||||
return directContribution(normal, view, lightDirection, colorIntensity.rgb * colorIntensity.a * 0.1, baseColor, metallic, roughness, f0);
|
||||
return directContribution(normal, view, lightDirection, colorIntensity.rgb * colorIntensity.a * 0.1, diffuseColor, nDotV, roughness, f0);
|
||||
}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
${directToCanvas ? canvasOutputShader : ""}
|
||||
|
||||
@fragment
|
||||
fn fragment(input: VertexOutput, @builtin(front_facing) frontFacing: bool) -> @location(0) vec4<f32> {
|
||||
@@ -432,6 +454,7 @@ fn fragment(input: VertexOutput, @builtin(front_facing) frontFacing: bool) -> @l
|
||||
${normalTexture ? `let tangent = normalize(input.tangent.xyz - normal * dot(input.tangent.xyz, normal)); let bitangent = cross(normal, tangent) * input.tangent.w; let mapped = ${sample(2)}.xyz * 2.0 - 1.0; normal = normalize(mat3x3<f32>(tangent, bitangent, normal) * vec3(mapped.xy * extra.y, mapped.z));` : ""}
|
||||
let view = normalize(cameraMatrices[4].xyz - input.world);
|
||||
let nDotV = max(dot(normal, view), 0.0001);
|
||||
let diffuseColor = base.rgb * (1.0 - metallic) / 3.14159265;
|
||||
let specularWeight = extension2.z * ${sample(11)}.a;
|
||||
let dielectricColor = vec3(${number(dielectric)} * specularWeight) * extension6.rgb * ${sample(12)}.rgb;
|
||||
var f0 = ${specularGlossiness ? "packedMaterial.rgb * extension3.rgb" : "mix(dielectricColor, base.rgb, metallic)"};
|
||||
@@ -448,30 +471,48 @@ fn fragment(input: VertexOutput, @builtin(front_facing) frontFacing: bool) -> @l
|
||||
let fresnelPower = pow5(1.0 - nDotV);
|
||||
let ambient = vec3<f32>(bitcast<f32>(clusters[${compactAmbientStart}]), bitcast<f32>(clusters[${compactAmbientStart + 1}]), bitcast<f32>(clusters[${compactAmbientStart + 2}]));
|
||||
var directLight = vec3<f32>(0.0);
|
||||
${
|
||||
lightMask & 1
|
||||
? `
|
||||
let pointCount = clusters[10];
|
||||
if (pointCount <= ${compactLightLimit}u) {
|
||||
for (var light = 0u; light < pointCount; light++) {
|
||||
directLight += pointContribution(clusters[${compactPointStart}u + light], normal, view, input.world, base.rgb, metallic, roughness, f0);
|
||||
directLight += pointContribution(clusters[${compactPointStart}u + light], normal, view, input.world, diffuseColor, nDotV, roughness, f0);
|
||||
}
|
||||
} else {
|
||||
for (var light = 0u; light < arrayLength(&pointLights) / 2u; light++) {
|
||||
directLight += pointContribution(light, normal, view, input.world, base.rgb, metallic, roughness, f0);
|
||||
directLight += pointContribution(light, normal, view, input.world, diffuseColor, nDotV, roughness, f0);
|
||||
}
|
||||
}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
lightMask & 2
|
||||
? `
|
||||
let spotCount = clusters[${compactSpotCount}];
|
||||
if (spotCount <= ${compactLightLimit}u) {
|
||||
for (var light = 0u; light < spotCount; light++) {
|
||||
directLight += spotContribution(clusters[${compactSpotStart}u + light], normal, view, input.world, base.rgb, metallic, roughness, f0);
|
||||
directLight += spotContribution(clusters[${compactSpotStart}u + light], normal, view, input.world, diffuseColor, nDotV, roughness, f0);
|
||||
}
|
||||
} else {
|
||||
for (var light = 0u; light < arrayLength(&spotLights) / 2u; light++) {
|
||||
directLight += spotContribution(light, normal, view, input.world, base.rgb, metallic, roughness, f0);
|
||||
directLight += spotContribution(light, normal, view, input.world, diffuseColor, nDotV, roughness, f0);
|
||||
}
|
||||
}
|
||||
for (var light = 0u; light < arrayLength(&directionalLights) / 2u; light++) {
|
||||
directLight += directionalContribution(light, normal, view, base.rgb, metallic, roughness, f0);
|
||||
`
|
||||
: ""
|
||||
}
|
||||
let occlusion = mix(1.0, ${sample(4)}.r, extension2.w);
|
||||
${
|
||||
lightMask & 4
|
||||
? `
|
||||
for (var light = 0u; light < arrayLength(&directionalLights) / 2u; light++) {
|
||||
directLight += directionalContribution(light, normal, view, diffuseColor, nDotV, roughness, f0);
|
||||
}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
let occlusion = mix(1.0, ${sharedOcclusion ? "packedMaterial" : sample(4)}.r, extension2.w);
|
||||
let emissive = vec3(properties.z, properties.w, extra.x) * ${sample(3, "vec4(1.0)")}.rgb * extension1.w;
|
||||
${sheen ? "let sheen = extension3.rgb * pow(1.0 - nDotV, mix(2.0, 8.0, extension3.w));" : ""}
|
||||
${
|
||||
@@ -508,7 +549,7 @@ fn fragment(input: VertexOutput, @builtin(front_facing) frontFacing: bool) -> @l
|
||||
color = mix(color, transmitted, transmissionAmount);`
|
||||
: ""
|
||||
}
|
||||
return vec4(color * accent.a, ${alphaMode === 2 ? "base.a" : "1.0"});
|
||||
return ${directToCanvas ? "canvasOutput(vec4(color * accent.a, 1.0))" : `vec4(color * accent.a, ${alphaMode === 2 ? "base.a" : "1.0"})`};
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -559,13 +600,23 @@ function effectFragment(kind: string, options: Record<string, unknown>) {
|
||||
}`;
|
||||
}
|
||||
|
||||
function toneMapping(toneMap: string) {
|
||||
return toneMap === "reinhard"
|
||||
? "color / (color + vec3(1.0))"
|
||||
: toneMap === "linear"
|
||||
? "clamp(color, vec3(0.0), vec3(1.0))"
|
||||
: "clamp((color * (2.51 * color + vec3(0.03))) / (color * (2.43 * color + vec3(0.59)) + vec3(0.14)), vec3(0.0), vec3(1.0))";
|
||||
}
|
||||
|
||||
// Match the intermediate rgba16float rounding before applying the same presentation transform.
|
||||
const canvasOutputShader = /* wgsl */ `
|
||||
fn canvasOutput(value: vec4<f32>) -> vec4<f32> {
|
||||
let rounded = vec4(unpack2x16float(pack2x16float(value.rg)), unpack2x16float(pack2x16float(value.ba)));
|
||||
let color = max(rounded.rgb, vec3(0.0));
|
||||
return vec4(${toneMapping("aces")}, rounded.a);
|
||||
}`;
|
||||
|
||||
function presentShader(toneMap: string, composite: "scene" | undefined) {
|
||||
const tone =
|
||||
toneMap === "reinhard"
|
||||
? "color / (color + vec3(1.0))"
|
||||
: toneMap === "linear"
|
||||
? "clamp(color, vec3(0.0), vec3(1.0))"
|
||||
: "clamp((color * (2.51 * color + vec3(0.03))) / (color * (2.43 * color + vec3(0.59)) + vec3(0.14)), vec3(0.0), vec3(1.0))";
|
||||
return `${fullscreenVertex}
|
||||
@group(0) @binding(0) var source: texture_2d<f32>;
|
||||
@group(0) @binding(1) var sourceSampler: sampler;
|
||||
@@ -573,7 +624,7 @@ ${composite === "scene" ? "@group(0) @binding(2) var scene: texture_2d<f32>;" :
|
||||
@fragment fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let value = textureSample(source, sourceSampler, input.uv);
|
||||
let color = max(${composite === "scene" ? "value.rgb + textureSample(scene, sourceSampler, input.uv).rgb * (1.0 - value.a)" : "value.rgb"}, vec3(0.0));
|
||||
return vec4(${tone}, ${composite ? "1.0" : "value.a"});
|
||||
return vec4(${toneMapping(toneMap)}, ${composite ? "1.0" : "value.a"});
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -611,6 +662,8 @@ export class Scene {
|
||||
readonly core: YawnCore;
|
||||
readonly ready: Promise<this>;
|
||||
readonly hdr: boolean;
|
||||
/** Fuse tone mapping into opaque PBR draws when no linear-color consumers need HDR. */
|
||||
readonly directToCanvas: boolean;
|
||||
readonly clearColor: readonly [number, number, number, number];
|
||||
#graphUpdates = Promise.resolve();
|
||||
#renderGraph?: object;
|
||||
@@ -635,6 +688,11 @@ export class Scene {
|
||||
#signals?: Float32Array;
|
||||
#arrays = new WeakMap<object, object>();
|
||||
#views = new WeakMap<object, object>();
|
||||
#lights = {
|
||||
point: new Set<number>(),
|
||||
spot: new Set<number>(),
|
||||
directional: new Set<number>(),
|
||||
};
|
||||
|
||||
constructor(
|
||||
canvas: HTMLCanvasElement,
|
||||
@@ -643,6 +701,7 @@ export class Scene {
|
||||
debug?: boolean;
|
||||
fps?: number;
|
||||
hdr?: boolean;
|
||||
directToCanvas?: boolean;
|
||||
clearColor?: ArrayLike<number>;
|
||||
} = {},
|
||||
) {
|
||||
@@ -653,6 +712,7 @@ export class Scene {
|
||||
)
|
||||
throw new TypeError("Scene clearColor must contain four finite numbers");
|
||||
this.hdr = options.hdr ?? true;
|
||||
this.directToCanvas = options.directToCanvas ?? false;
|
||||
this.clearColor = clearColor as [number, number, number, number];
|
||||
this.core = new YawnCore(canvas, {
|
||||
arenaBytes: options.arenaBytes,
|
||||
@@ -1171,6 +1231,21 @@ export class Scene {
|
||||
return this.updateRenderGraph();
|
||||
}
|
||||
|
||||
/** Light handles register their lifetime, not intensity; hot edits never select a new variant. */
|
||||
setLight(
|
||||
kind: "point" | "spot" | "directional",
|
||||
id: number,
|
||||
present: boolean,
|
||||
) {
|
||||
const lights = this.#lights[kind];
|
||||
const hadType = lights.size > 0;
|
||||
if (present) lights.add(id);
|
||||
else lights.delete(id);
|
||||
return hadType !== Boolean(lights.size)
|
||||
? this.updateRenderGraph()
|
||||
: Promise.resolve();
|
||||
}
|
||||
|
||||
updateRenderGraph() {
|
||||
this.markDirty(true);
|
||||
if (this.#graphBatchDepth) {
|
||||
@@ -1327,16 +1402,38 @@ export class Scene {
|
||||
const hasTransmission = forwardDraws.some(
|
||||
(draw) => this.array("materialExtensions").row(draw.material)[0] > 0,
|
||||
);
|
||||
// Custom compute can author light rows directly, so retain the generic shader in that case.
|
||||
const lightMask = this.#computePasses.size
|
||||
? 7
|
||||
: (this.#lights.point.size ? 1 : 0) |
|
||||
(this.#lights.spot.size ? 2 : 0) |
|
||||
(this.#lights.directional.size ? 4 : 0);
|
||||
const directToCanvas =
|
||||
this.directToCanvas &&
|
||||
this.hdr &&
|
||||
!this.#computePasses.size &&
|
||||
!this.#effects.size &&
|
||||
!this.#shaders.size &&
|
||||
!hasTransmission &&
|
||||
forwardDraws.length > 0 &&
|
||||
forwardDraws.every(
|
||||
(draw) =>
|
||||
(Math.round(this.array("materials").row(draw.material)[11]) & 3) !==
|
||||
2 &&
|
||||
draw.geometry.data.has("normals") &&
|
||||
draw.geometry.data.has("uvs"),
|
||||
);
|
||||
const compositeSceneColor =
|
||||
hasTransmission && this.#effects.size === 0 && this.#shaders.size === 0;
|
||||
const hdrFormat = this.hdr ? "rgba16float" : "rgba8unorm";
|
||||
addTexture({
|
||||
id: "hdr",
|
||||
format: hdrFormat,
|
||||
size: ["canvas", "canvas", 1],
|
||||
usage: ["render", "sampled"],
|
||||
transient: false,
|
||||
});
|
||||
if (!directToCanvas)
|
||||
addTexture({
|
||||
id: "hdr",
|
||||
format: hdrFormat,
|
||||
size: ["canvas", "canvas", 1],
|
||||
usage: ["render", "sampled"],
|
||||
transient: false,
|
||||
});
|
||||
addTexture({
|
||||
id: "depth",
|
||||
format: "depth24plus",
|
||||
@@ -1374,6 +1471,31 @@ export class Scene {
|
||||
|
||||
let previous = computeIds.length ? computeIds : ["cluster-lights"];
|
||||
const bucketRows = new Map<number, number[][]>();
|
||||
if (directToCanvas) {
|
||||
// Shares attachments with the forward draws, so core merges this constant background
|
||||
// into their physical render pass. Use the same HDR rounding/tone mapping for the clear.
|
||||
renderPipelines.push({
|
||||
id: "canvas-background",
|
||||
code: `${fullscreenVertex}\n${canvasOutputShader}\n@fragment fn fragment() -> @location(0) vec4<f32> { return canvasOutput(vec4<f32>(${this.clearColor.map((value) => (Number.isInteger(value) ? `${value}.0` : String(value))).join(", ")})); }`,
|
||||
vertex: { entry: "vertex" },
|
||||
fragment: { entry: "fragment", targets: [{ format: "canvas" }] },
|
||||
depthStencil: {
|
||||
format: "depth24plus",
|
||||
depth_write_enabled: false,
|
||||
depth_compare: "always",
|
||||
},
|
||||
});
|
||||
passes.push({
|
||||
id: "canvas-background",
|
||||
type: "render",
|
||||
pipeline: "canvas-background",
|
||||
after: previous,
|
||||
color: [{ resource: "canvas", clear: [0, 0, 0, 0] }],
|
||||
depth: { resource: "depth", clear: 1 },
|
||||
draw: { vertices: 3 },
|
||||
});
|
||||
previous = ["canvas-background"];
|
||||
}
|
||||
if (!forwardDraws.length) {
|
||||
renderPipelines.push({
|
||||
id: "empty-forward",
|
||||
@@ -1435,10 +1557,14 @@ export class Scene {
|
||||
sync: "manual",
|
||||
});
|
||||
const forwardPipelines = new Map<string, string>();
|
||||
let firstColor = true;
|
||||
let firstDepth = true;
|
||||
let firstColor = !directToCanvas;
|
||||
let firstDepth = !directToCanvas;
|
||||
let sceneColorReady = false;
|
||||
let forwardTarget = compositeSceneColor ? "scene-color" : "hdr";
|
||||
let forwardTarget = directToCanvas
|
||||
? "canvas"
|
||||
: compositeSceneColor
|
||||
? "scene-color"
|
||||
: "hdr";
|
||||
const attachments = () => {
|
||||
const color = firstColor
|
||||
? { clear: [...this.clearColor] }
|
||||
@@ -1518,6 +1644,13 @@ export class Scene {
|
||||
if (extensionInfo[11] <= 0) mask &= ~(1 << 4);
|
||||
if ((flags & 8) !== 0 && !transmitted) mask &= (1 << 0) | (1 << 3);
|
||||
const textureInfo = this.array("materialTextureInfo").row(material);
|
||||
// All material samples currently use one sampler. Reuse only identical texture/UV reads.
|
||||
const sharedOcclusion =
|
||||
Boolean(mask & (1 << 1) && mask & (1 << 4)) &&
|
||||
materialTextures[1] === materialTextures[4] &&
|
||||
Array.from(textureInfo)
|
||||
.slice(8, 15)
|
||||
.every((value, lane) => value === textureInfo[32 + lane]);
|
||||
let clearCompositeLayer = false;
|
||||
if (transmitted || (flags & 3) === 2) flushBuckets();
|
||||
if (transmitted && !sceneColorReady) {
|
||||
@@ -1558,7 +1691,16 @@ export class Scene {
|
||||
}
|
||||
}
|
||||
const code = detailed
|
||||
? pbrShader(mask, flags, transmitted, textureInfo, extensionInfo)
|
||||
? pbrShader(
|
||||
mask,
|
||||
flags,
|
||||
transmitted,
|
||||
textureInfo,
|
||||
extensionInfo,
|
||||
sharedOcclusion,
|
||||
lightMask,
|
||||
directToCanvas,
|
||||
)
|
||||
: basicForwardShader;
|
||||
const pipelineKey = `${flags}-${transmitted ? 1 : 0}\n${code}`;
|
||||
let pipeline = forwardPipelines.get(pipelineKey);
|
||||
@@ -1583,7 +1725,7 @@ export class Scene {
|
||||
entry: "fragment",
|
||||
targets: [
|
||||
{
|
||||
format: hdrFormat,
|
||||
format: directToCanvas ? "canvas" : hdrFormat,
|
||||
...((flags & 3) === 2
|
||||
? {
|
||||
blend: {
|
||||
@@ -1644,7 +1786,7 @@ export class Scene {
|
||||
binding: 10,
|
||||
resource: "directional-lights",
|
||||
},
|
||||
]
|
||||
].filter((_, index) => lightMask & (1 << index))
|
||||
: []),
|
||||
...(detailed && mask
|
||||
? [
|
||||
@@ -1656,7 +1798,9 @@ export class Scene {
|
||||
: "material-linear",
|
||||
},
|
||||
...materialTextures.flatMap((value, lane) =>
|
||||
value && (mask & (1 << lane)) !== 0
|
||||
value &&
|
||||
(mask & (1 << lane)) !== 0 &&
|
||||
!(lane === 4 && sharedOcclusion)
|
||||
? [
|
||||
{
|
||||
group: 1,
|
||||
@@ -1770,32 +1914,34 @@ export class Scene {
|
||||
previous = [pipeline];
|
||||
}
|
||||
|
||||
const toneMap = String(
|
||||
[...this.#effects.values()].find(
|
||||
(effect) => effect.kind === "colorGrading",
|
||||
)?.options.toneMap ?? "aces",
|
||||
);
|
||||
renderPipelines.push({
|
||||
id: "present",
|
||||
code: presentShader(toneMap, compositeSceneColor ? "scene" : undefined),
|
||||
vertex: { entry: "vertex" },
|
||||
fragment: { entry: "fragment", targets: [{ format: "canvas" }] },
|
||||
});
|
||||
passes.push({
|
||||
id: "present",
|
||||
type: "render",
|
||||
pipeline: "present",
|
||||
after: previous,
|
||||
bindings: [
|
||||
{ group: 0, binding: 0, resource: input },
|
||||
{ group: 0, binding: 1, resource: "linear" },
|
||||
...(compositeSceneColor
|
||||
? [{ group: 0, binding: 2, resource: "scene-color" }]
|
||||
: []),
|
||||
],
|
||||
color: [{ resource: "canvas", clear: [...this.clearColor] }],
|
||||
draw: { vertices: 3 },
|
||||
});
|
||||
if (!directToCanvas) {
|
||||
const toneMap = String(
|
||||
[...this.#effects.values()].find(
|
||||
(effect) => effect.kind === "colorGrading",
|
||||
)?.options.toneMap ?? "aces",
|
||||
);
|
||||
renderPipelines.push({
|
||||
id: "present",
|
||||
code: presentShader(toneMap, compositeSceneColor ? "scene" : undefined),
|
||||
vertex: { entry: "vertex" },
|
||||
fragment: { entry: "fragment", targets: [{ format: "canvas" }] },
|
||||
});
|
||||
passes.push({
|
||||
id: "present",
|
||||
type: "render",
|
||||
pipeline: "present",
|
||||
after: previous,
|
||||
bindings: [
|
||||
{ group: 0, binding: 0, resource: input },
|
||||
{ group: 0, binding: 1, resource: "linear" },
|
||||
...(compositeSceneColor
|
||||
? [{ group: 0, binding: 2, resource: "scene-color" }]
|
||||
: []),
|
||||
],
|
||||
color: [{ resource: "canvas", clear: [...this.clearColor] }],
|
||||
draw: { vertices: 3 },
|
||||
});
|
||||
}
|
||||
|
||||
const graph = {
|
||||
id: "scene",
|
||||
|
||||
Reference in New Issue
Block a user