|
|
|
@@ -56,6 +56,7 @@ const compactLightLimit = 500;
|
|
|
|
|
const compactPointStart = 11;
|
|
|
|
|
const compactSpotCount = compactPointStart + compactLightLimit;
|
|
|
|
|
const compactSpotStart = compactSpotCount + 1;
|
|
|
|
|
const compactAmbientStart = compactSpotStart + compactLightLimit;
|
|
|
|
|
|
|
|
|
|
const clusterShader = /* wgsl */ `
|
|
|
|
|
@group(0) @binding(0) var<storage, read> pointLights: array<vec4<f32>>;
|
|
|
|
@@ -72,6 +73,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
|
|
var pointCount = 0u;
|
|
|
|
|
var spotCount = 0u;
|
|
|
|
|
var light = vec3<f32>(0.0);
|
|
|
|
|
var ambientLight = vec3<f32>(0.0);
|
|
|
|
|
var pointLight = vec3<f32>(0.0);
|
|
|
|
|
var spotLight = vec3<f32>(0.0);
|
|
|
|
|
for (var i = 0u; i < arrayLength(&pointLights) / 2u; i++) {
|
|
|
|
@@ -90,7 +92,9 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
|
|
for (var i = 0u; i < arrayLength(&rectLights) / 3u; i++) {
|
|
|
|
|
let enabled = rectLights[i * 3u + 1u].z;
|
|
|
|
|
count += select(0u, 1u, enabled != 0.0);
|
|
|
|
|
light += rectLights[i * 3u].rgb * rectLights[i * 3u].a * enabled * 0.02;
|
|
|
|
|
let contribution = rectLights[i * 3u].rgb * rectLights[i * 3u].a * enabled * 0.02;
|
|
|
|
|
light += contribution;
|
|
|
|
|
ambientLight += contribution;
|
|
|
|
|
}
|
|
|
|
|
for (var i = 0u; i < arrayLength(&spotLights) / 2u; i++) {
|
|
|
|
|
let enabled = spotLights[i * 2u + 1u].w;
|
|
|
|
@@ -112,7 +116,9 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
|
|
}
|
|
|
|
|
for (var i = 0u; i < arrayLength(&ambientLights); i++) {
|
|
|
|
|
count += select(0u, 1u, ambientLights[i].a != 0.0);
|
|
|
|
|
light += ambientLights[i].rgb * ambientLights[i].a;
|
|
|
|
|
let contribution = ambientLights[i].rgb * ambientLights[i].a;
|
|
|
|
|
light += contribution;
|
|
|
|
|
ambientLight += contribution;
|
|
|
|
|
}
|
|
|
|
|
clusters[0] = count;
|
|
|
|
|
clusters[1] = bitcast<u32>(light.r);
|
|
|
|
@@ -126,6 +132,9 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
|
|
clusters[9] = bitcast<u32>(spotLight.b);
|
|
|
|
|
clusters[10] = pointCount;
|
|
|
|
|
clusters[${compactSpotCount}] = spotCount;
|
|
|
|
|
clusters[${compactAmbientStart}] = bitcast<u32>(ambientLight.r);
|
|
|
|
|
clusters[${compactAmbientStart + 1}] = bitcast<u32>(ambientLight.g);
|
|
|
|
|
clusters[${compactAmbientStart + 2}] = bitcast<u32>(ambientLight.b);
|
|
|
|
|
}`;
|
|
|
|
|
|
|
|
|
|
const basicForwardShader = /* wgsl */ `
|
|
|
|
@@ -180,9 +189,7 @@ fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
|
|
|
let base = materials[material * 3u];
|
|
|
|
|
let properties = materials[material * 3u + 1u];
|
|
|
|
|
let clusterLight = vec3<f32>(bitcast<f32>(clusters[1]), bitcast<f32>(clusters[2]), bitcast<f32>(clusters[3]));
|
|
|
|
|
let light = vec3<f32>(0.12 + max(dot(input.normal, normalize(vec3<f32>(0.4, 0.7, 0.6))), 0.0) * 0.75) + clusterLight;
|
|
|
|
|
let clustered = min(f32(clusters[0]) * 0.002, 0.05);
|
|
|
|
|
let color = base.rgb * (light + clustered) * accent.color.rgb * mix(1.0, 1.1, properties.x);
|
|
|
|
|
let color = base.rgb * clusterLight * accent.color.rgb * mix(1.0, 1.1, properties.x);
|
|
|
|
|
return vec4<f32>(color, base.a);
|
|
|
|
|
}`;
|
|
|
|
|
|
|
|
|
@@ -272,6 +279,7 @@ struct VertexOutput {
|
|
|
|
|
@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>>;
|
|
|
|
|
${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")}
|
|
|
|
|
${transmission ? "@group(2) @binding(0) var sceneColor: texture_2d<f32>;\n@group(2) @binding(1) var sceneSampler: sampler;" : ""}
|
|
|
|
@@ -315,36 +323,43 @@ fn vertex(
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn studioEnvironment(direction: vec3<f32>) -> vec3<f32> {
|
|
|
|
|
let ray = direction;
|
|
|
|
|
let horizon = mix(vec3(0.14, 0.15, 0.16), vec3(0.62, 0.59, 0.54), clamp(ray.y * 0.5 + 0.5, 0.0, 1.0));
|
|
|
|
|
let key = max(dot(ray, normalize(vec3(-0.65, 0.7, 0.35))), 0.0);
|
|
|
|
|
let key2 = key * key;
|
|
|
|
|
let key4 = key2 * key2;
|
|
|
|
|
let key8 = key4 * key4;
|
|
|
|
|
let key16 = key8 * key8;
|
|
|
|
|
let key32 = key16 * key16;
|
|
|
|
|
let key64 = key32 * key32;
|
|
|
|
|
return horizon + vec3(5.0, 4.2, 3.1) * key64 * key32;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn pow5(value: f32) -> f32 {
|
|
|
|
|
let squared = value * value;
|
|
|
|
|
return squared * squared * value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn pointContribution(index: u32, normal: vec3<f32>, world: vec3<f32>) -> vec3<f32> {
|
|
|
|
|
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> {
|
|
|
|
|
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);
|
|
|
|
|
let alpha = roughness * roughness;
|
|
|
|
|
let alpha2 = alpha * alpha;
|
|
|
|
|
let denominator = nDotH * nDotH * (alpha2 - 1.0) + 1.0;
|
|
|
|
|
let distribution = alpha2 / max(3.14159265 * denominator * denominator, 0.0001);
|
|
|
|
|
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;
|
|
|
|
|
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> {
|
|
|
|
|
let colorIntensity = pointLights[index * 2u];
|
|
|
|
|
let state = pointLights[index * 2u + 1u];
|
|
|
|
|
if (state.y == 0.0 || state.x <= 0.0) { return vec3(0.0); }
|
|
|
|
|
let offset = positions[index].xyz - world;
|
|
|
|
|
let distance = length(offset);
|
|
|
|
|
let falloff = max(1.0 - distance / state.x, 0.0);
|
|
|
|
|
let incidence = max(dot(normal, offset / max(distance, 0.0001)), 0.0);
|
|
|
|
|
return colorIntensity.rgb * colorIntensity.a * falloff * falloff * incidence * 0.08;
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn spotContribution(index: u32, normal: vec3<f32>, world: vec3<f32>) -> vec3<f32> {
|
|
|
|
|
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> {
|
|
|
|
|
let colorIntensity = spotLights[index * 2u];
|
|
|
|
|
let state = spotLights[index * 2u + 1u];
|
|
|
|
|
if (state.w == 0.0 || state.x <= 0.0) { return vec3(0.0); }
|
|
|
|
@@ -353,8 +368,16 @@ fn spotContribution(index: u32, normal: vec3<f32>, world: vec3<f32>) -> vec3<f32
|
|
|
|
|
let direction = normalize(rotate(rotors[index], vec3<f32>(0.0, 0.0, -1.0)));
|
|
|
|
|
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 incidence = max(dot(normal, -fromLight / max(distance, 0.0001)), 0.0);
|
|
|
|
|
return colorIntensity.rgb * colorIntensity.a * cone * falloff * falloff * incidence * 0.08;
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn directionalContribution(index: u32, normal: vec3<f32>, view: vec3<f32>, baseColor: vec3<f32>, metallic: 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@fragment
|
|
|
|
@@ -379,12 +402,7 @@ fn fragment(input: VertexOutput, @builtin(front_facing) frontFacing: bool) -> @l
|
|
|
|
|
${doubleSided ? "normal *= select(-1.0, 1.0, frontFacing);" : ""}
|
|
|
|
|
${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 lightDirection = normalize(vec3<f32>(0.4, 0.7, 0.6));
|
|
|
|
|
let halfVector = normalize(lightDirection + view);
|
|
|
|
|
let nDotL = max(dot(normal, lightDirection), 0.0);
|
|
|
|
|
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 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)"};
|
|
|
|
@@ -398,58 +416,42 @@ fn fragment(input: VertexOutput, @builtin(front_facing) frontFacing: bool) -> @l
|
|
|
|
|
f0 = mix(f0, iridescenceTint * iridescenceLevel, iridescenceAmount * (1.0 - roughness * 0.5));`
|
|
|
|
|
: ""
|
|
|
|
|
}
|
|
|
|
|
let fresnelPower = pow5(1.0 - vDotH);
|
|
|
|
|
let fresnel = f0 + (vec3(1.0) - f0) * fresnelPower;
|
|
|
|
|
let alpha = roughness * roughness;
|
|
|
|
|
let alpha2 = alpha * alpha;
|
|
|
|
|
let denominator = nDotH * nDotH * (alpha2 - 1.0) + 1.0;
|
|
|
|
|
let distribution = alpha2 / max(3.14159265 * denominator * denominator, 0.0001);
|
|
|
|
|
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 clusterLight = vec3<f32>(bitcast<f32>(clusters[1]), bitcast<f32>(clusters[2]), bitcast<f32>(clusters[3]));
|
|
|
|
|
let pointAmbient = vec3<f32>(bitcast<f32>(clusters[4]), bitcast<f32>(clusters[5]), bitcast<f32>(clusters[6]));
|
|
|
|
|
let spotAmbient = vec3<f32>(bitcast<f32>(clusters[7]), bitcast<f32>(clusters[8]), bitcast<f32>(clusters[9]));
|
|
|
|
|
let ambient = vec3(0.14) + max(clusterLight - pointAmbient - spotAmbient, vec3(0.0));
|
|
|
|
|
var pointDiffuse = vec3<f32>(0.0);
|
|
|
|
|
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);
|
|
|
|
|
let pointCount = clusters[10];
|
|
|
|
|
if (pointCount <= ${compactLightLimit}u) {
|
|
|
|
|
for (var light = 0u; light < pointCount; light++) {
|
|
|
|
|
pointDiffuse += pointContribution(clusters[${compactPointStart}u + light], normal, input.world);
|
|
|
|
|
directLight += pointContribution(clusters[${compactPointStart}u + light], normal, view, input.world, base.rgb, metallic, roughness, f0);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (var light = 0u; light < arrayLength(&pointLights) / 2u; light++) {
|
|
|
|
|
pointDiffuse += pointContribution(light, normal, input.world);
|
|
|
|
|
directLight += pointContribution(light, normal, view, input.world, base.rgb, metallic, roughness, f0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var spotDiffuse = vec3<f32>(0.0);
|
|
|
|
|
let spotCount = clusters[${compactSpotCount}];
|
|
|
|
|
if (spotCount <= ${compactLightLimit}u) {
|
|
|
|
|
for (var light = 0u; light < spotCount; light++) {
|
|
|
|
|
spotDiffuse += spotContribution(clusters[${compactSpotStart}u + light], normal, input.world);
|
|
|
|
|
directLight += spotContribution(clusters[${compactSpotStart}u + light], normal, view, input.world, base.rgb, metallic, roughness, f0);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (var light = 0u; light < arrayLength(&spotLights) / 2u; light++) {
|
|
|
|
|
spotDiffuse += spotContribution(light, normal, input.world);
|
|
|
|
|
directLight += spotContribution(light, normal, view, input.world, base.rgb, metallic, roughness, f0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let defaultLight = select(1.0, 0.18, clusters[0] != 0u);
|
|
|
|
|
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);
|
|
|
|
|
let diffuseColor = base.rgb * (vec3(1.0) - fresnel) * (1.0 - metallic) / 3.14159265;
|
|
|
|
|
let diffuse = (diffuseColor + specular) * nDotL * defaultLight;
|
|
|
|
|
let reflected = studioEnvironment(reflect(-view, normal));
|
|
|
|
|
let environmentFresnel = f0 + (vec3(1.0) - f0) * pow5(1.0 - nDotV);
|
|
|
|
|
let environment = (base.rgb * (1.0 - metallic) * studioEnvironment(normal) * 0.5 + reflected * environmentFresnel * (1.0 - roughness * 0.75)) * occlusion;
|
|
|
|
|
let emissive = vec3(properties.z, properties.w, extra.x) * ${sample(3, "vec4(1.0)")}.rgb * extension1.w;
|
|
|
|
|
let localDiffuse = base.rgb * (1.0 - metallic) * (pointDiffuse + spotDiffuse);
|
|
|
|
|
${sheen ? "let sheen = extension3.rgb * pow(1.0 - nDotV, mix(2.0, 8.0, extension3.w));" : ""}
|
|
|
|
|
${
|
|
|
|
|
clearcoat
|
|
|
|
|
? `let clearcoatFresnel = 0.04 + 0.96 * fresnelPower;
|
|
|
|
|
let clearcoat = extension2.x * ${sample(7)}.r * clearcoatFresnel * pow(nDotH, mix(256.0, 4.0, extension2.y));`
|
|
|
|
|
let clearcoat = extension2.x * ${sample(7)}.r * clearcoatFresnel;`
|
|
|
|
|
: ""
|
|
|
|
|
}
|
|
|
|
|
var color = base.rgb * ambient * occlusion + diffuse + environment + localDiffuse${sheen ? " + sheen" : ""}${clearcoat ? " + vec3(clearcoat)" : ""} + emissive;
|
|
|
|
|
var color = base.rgb * ambient * occlusion + directLight${sheen ? " + directLight * sheen" : ""}${clearcoat ? " + directLight * clearcoat" : ""} + emissive;
|
|
|
|
|
${unlit ? "color = base.rgb + emissive;" : ""}
|
|
|
|
|
${
|
|
|
|
|
transmission
|
|
|
|
@@ -474,7 +476,7 @@ fn fragment(input: VertexOutput, @builtin(front_facing) frontFacing: bool) -> @l
|
|
|
|
|
}
|
|
|
|
|
let normalizedTransmissionTint = base.rgb / max(max(base.r, base.g), max(base.b, 0.001));
|
|
|
|
|
transmitted *= normalizedTransmissionTint;
|
|
|
|
|
color = mix(color, transmitted + reflected * environmentFresnel, transmissionAmount);`
|
|
|
|
|
color = mix(color, transmitted, transmissionAmount);`
|
|
|
|
|
: ""
|
|
|
|
|
}
|
|
|
|
|
return vec4(color * accent.a, ${alphaMode === 2 ? "base.a" : "1.0"});
|
|
|
|
@@ -509,21 +511,6 @@ const copyShader = `${fullscreenVertex}
|
|
|
|
|
return textureSample(source, sourceSampler, input.uv);
|
|
|
|
|
}`;
|
|
|
|
|
|
|
|
|
|
const studioBackground = /* wgsl */ `
|
|
|
|
|
fn studioBackground(uv: vec2<f32>) -> vec3<f32> {
|
|
|
|
|
let upper = vec3(0.42, 0.43, 0.44);
|
|
|
|
|
let lower = vec3(0.16, 0.17, 0.18);
|
|
|
|
|
let vertical = smoothstep(0.0, 1.0, uv.y);
|
|
|
|
|
let keyBase = max(1.0 - distance(uv, vec2(0.05, 0.05)) / 0.72, 0.0);
|
|
|
|
|
return mix(upper, lower, vertical) + vec3(4.0, 3.8, 3.45) * keyBase * keyBase;
|
|
|
|
|
}`;
|
|
|
|
|
|
|
|
|
|
const studioBackgroundShader = `${fullscreenVertex}
|
|
|
|
|
${studioBackground}
|
|
|
|
|
@fragment fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
|
|
|
return vec4(studioBackground(input.uv), 1.0);
|
|
|
|
|
}`;
|
|
|
|
|
|
|
|
|
|
function effectFragment(kind: string, options: Record<string, unknown>) {
|
|
|
|
|
const amount = Number(options.amount ?? options.exposure ?? 1);
|
|
|
|
|
const safeAmount = Number.isFinite(amount) ? amount : 1;
|
|
|
|
@@ -543,10 +530,7 @@ function effectFragment(kind: string, options: Record<string, unknown>) {
|
|
|
|
|
}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function presentShader(
|
|
|
|
|
toneMap: string,
|
|
|
|
|
composite: "studio" | "scene" | undefined,
|
|
|
|
|
) {
|
|
|
|
|
function presentShader(toneMap: string, composite: "scene" | undefined) {
|
|
|
|
|
const tone =
|
|
|
|
|
toneMap === "reinhard"
|
|
|
|
|
? "color / (color + vec3(1.0))"
|
|
|
|
@@ -554,13 +538,12 @@ function presentShader(
|
|
|
|
|
? "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}
|
|
|
|
|
${composite === "studio" ? studioBackground : ""}
|
|
|
|
|
@group(0) @binding(0) var source: texture_2d<f32>;
|
|
|
|
|
@group(0) @binding(1) var sourceSampler: sampler;
|
|
|
|
|
${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 === "studio" ? "value.rgb + studioBackground(input.uv) * (1.0 - value.a)" : composite === "scene" ? "value.rgb + textureSample(scene, sourceSampler, input.uv).rgb * (1.0 - value.a)" : "value.rgb"}, vec3(0.0));
|
|
|
|
|
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"});
|
|
|
|
|
}`;
|
|
|
|
|
}
|
|
|
|
@@ -599,7 +582,9 @@ export class Scene {
|
|
|
|
|
readonly core: YawnCore;
|
|
|
|
|
readonly ready: Promise<this>;
|
|
|
|
|
readonly hdr: boolean;
|
|
|
|
|
readonly clearColor: readonly [number, number, number, number];
|
|
|
|
|
#graphUpdates = Promise.resolve();
|
|
|
|
|
#renderGraph?: object;
|
|
|
|
|
#computePasses = new Map<string, ComputePass>();
|
|
|
|
|
#meshes = new Map<number, MeshLike>();
|
|
|
|
|
#shaders = new Map<number, ShaderLike>();
|
|
|
|
@@ -625,9 +610,17 @@ export class Scene {
|
|
|
|
|
debug?: boolean;
|
|
|
|
|
fps?: number;
|
|
|
|
|
hdr?: boolean;
|
|
|
|
|
clearColor?: ArrayLike<number>;
|
|
|
|
|
} = {},
|
|
|
|
|
) {
|
|
|
|
|
const clearColor = Array.from(options.clearColor ?? [0, 0, 0, 1]);
|
|
|
|
|
if (
|
|
|
|
|
clearColor.length !== 4 ||
|
|
|
|
|
clearColor.some((value) => !Number.isFinite(value))
|
|
|
|
|
)
|
|
|
|
|
throw new TypeError("Scene clearColor must contain four finite numbers");
|
|
|
|
|
this.hdr = options.hdr ?? true;
|
|
|
|
|
this.clearColor = clearColor as [number, number, number, number];
|
|
|
|
|
this.core = new YawnCore(canvas, {
|
|
|
|
|
arenaBytes: options.arenaBytes,
|
|
|
|
|
debug: options.debug,
|
|
|
|
@@ -648,7 +641,7 @@ export class Scene {
|
|
|
|
|
});
|
|
|
|
|
this.core.array("nodeRotors").write(0, [0, 0, 0, 1]);
|
|
|
|
|
this.core.array("nodeScales").write(0, [1, 1, 1, 0]);
|
|
|
|
|
this.core.array("sceneAccent").write(0, [0.28, 0.72, 1, 1]);
|
|
|
|
|
this.core.array("sceneAccent").write(0, [1, 1, 1, 1]);
|
|
|
|
|
const material = await this.core.allocateObject("materials");
|
|
|
|
|
this.core
|
|
|
|
|
.array("materials")
|
|
|
|
@@ -1045,6 +1038,29 @@ export class Scene {
|
|
|
|
|
return update;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Compiles a serialized render graph and returns its reusable loadout ID. */
|
|
|
|
|
async compileRenderGraph(serialized: string) {
|
|
|
|
|
if (typeof serialized !== "string")
|
|
|
|
|
throw new TypeError("Serialized render graph must be a string");
|
|
|
|
|
await this.ready;
|
|
|
|
|
return this.core.compileGraph(serialized);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Switches this Scene to a previously compiled render-graph loadout. */
|
|
|
|
|
async setLoadout(id: string) {
|
|
|
|
|
if (typeof id !== "string")
|
|
|
|
|
throw new TypeError("Loadout ID must be a string");
|
|
|
|
|
await this.ready;
|
|
|
|
|
await this.core.switchLoadout(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Returns an isolated snapshot of the Scene-authored render graph. */
|
|
|
|
|
async getRenderGraph() {
|
|
|
|
|
await this.ready;
|
|
|
|
|
if (!this.#renderGraph) throw new Error("Render graph is not ready");
|
|
|
|
|
return structuredClone(this.#renderGraph);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async #compileRenderGraph() {
|
|
|
|
|
const buffers = new Map<string, GraphBuffer>();
|
|
|
|
|
const textures = new Map<string, GraphTexture>();
|
|
|
|
@@ -1127,11 +1143,6 @@ export class Scene {
|
|
|
|
|
(material) => this.array("materialExtensions").row(material)[0] > 0,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
const compositeStudioBackground =
|
|
|
|
|
renderedMeshes.length > 0 &&
|
|
|
|
|
!hasTransmission &&
|
|
|
|
|
this.#effects.size === 0 &&
|
|
|
|
|
this.#shaders.size === 0;
|
|
|
|
|
const compositeSceneColor =
|
|
|
|
|
hasTransmission && this.#effects.size === 0 && this.#shaders.size === 0;
|
|
|
|
|
const hdrFormat = this.hdr ? "rgba16float" : "rgba8unorm";
|
|
|
|
@@ -1190,7 +1201,7 @@ export class Scene {
|
|
|
|
|
type: "render",
|
|
|
|
|
pipeline: "empty-forward",
|
|
|
|
|
after: previous,
|
|
|
|
|
color: [{ resource: "hdr", clear: [0.015, 0.025, 0.05, 1] }],
|
|
|
|
|
color: [{ resource: "hdr", clear: [...this.clearColor] }],
|
|
|
|
|
draw: { vertices: 0 },
|
|
|
|
|
});
|
|
|
|
|
previous = ["forward-empty"];
|
|
|
|
@@ -1204,27 +1215,27 @@ export class Scene {
|
|
|
|
|
["camera-matrices", "cameraMatrices"],
|
|
|
|
|
])
|
|
|
|
|
addBuffer({ id, array, usage: ["storage"] });
|
|
|
|
|
if (!compositeStudioBackground) {
|
|
|
|
|
if (compositeSceneColor) {
|
|
|
|
|
renderPipelines.push({
|
|
|
|
|
id: "studio-background",
|
|
|
|
|
code: studioBackgroundShader,
|
|
|
|
|
id: "scene-color-clear",
|
|
|
|
|
code: clearShader,
|
|
|
|
|
vertex: { entry: "vertex" },
|
|
|
|
|
fragment: { entry: "fragment", targets: [{ format: hdrFormat }] },
|
|
|
|
|
});
|
|
|
|
|
passes.push({
|
|
|
|
|
id: "studio-background",
|
|
|
|
|
id: "scene-color-clear",
|
|
|
|
|
type: "render",
|
|
|
|
|
pipeline: "studio-background",
|
|
|
|
|
pipeline: "scene-color-clear",
|
|
|
|
|
after: previous,
|
|
|
|
|
color: [
|
|
|
|
|
{
|
|
|
|
|
resource: compositeSceneColor ? "scene-color" : "hdr",
|
|
|
|
|
clear: [0, 0, 0, 1],
|
|
|
|
|
resource: "scene-color",
|
|
|
|
|
clear: [...this.clearColor],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
draw: { vertices: 3 },
|
|
|
|
|
draw: { vertices: 0 },
|
|
|
|
|
});
|
|
|
|
|
previous = ["studio-background"];
|
|
|
|
|
previous = ["scene-color-clear"];
|
|
|
|
|
}
|
|
|
|
|
const forwardPipelines = new Map<string, string>();
|
|
|
|
|
let firstColor = true;
|
|
|
|
@@ -1363,7 +1374,9 @@ export class Scene {
|
|
|
|
|
{ group: 0, binding: 0, resource: "hdr" },
|
|
|
|
|
{ group: 0, binding: 1, resource: "linear" },
|
|
|
|
|
],
|
|
|
|
|
color: [{ resource: "scene-color", clear: [0, 0, 0, 1] }],
|
|
|
|
|
color: [
|
|
|
|
|
{ resource: "scene-color", clear: [...this.clearColor] },
|
|
|
|
|
],
|
|
|
|
|
draw: { vertices: 3 },
|
|
|
|
|
});
|
|
|
|
|
previous = ["copy-scene-color"];
|
|
|
|
@@ -1543,6 +1556,11 @@ export class Scene {
|
|
|
|
|
? [
|
|
|
|
|
{ group: 0, binding: 8, resource: "point-lights" },
|
|
|
|
|
{ group: 0, binding: 9, resource: "spot-lights" },
|
|
|
|
|
{
|
|
|
|
|
group: 0,
|
|
|
|
|
binding: 10,
|
|
|
|
|
resource: "directional-lights",
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
: []),
|
|
|
|
|
...(detailed && mask
|
|
|
|
@@ -1577,10 +1595,11 @@ export class Scene {
|
|
|
|
|
color: [
|
|
|
|
|
{
|
|
|
|
|
resource: forwardTarget,
|
|
|
|
|
...((firstColor && compositeStudioBackground) ||
|
|
|
|
|
clearCompositeLayer
|
|
|
|
|
...(clearCompositeLayer
|
|
|
|
|
? { clear: [0, 0, 0, 0] }
|
|
|
|
|
: { load: "load" }),
|
|
|
|
|
: firstColor
|
|
|
|
|
? { clear: [...this.clearColor] }
|
|
|
|
|
: { load: "load" }),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
depth: {
|
|
|
|
@@ -1689,14 +1708,7 @@ export class Scene {
|
|
|
|
|
);
|
|
|
|
|
renderPipelines.push({
|
|
|
|
|
id: "present",
|
|
|
|
|
code: presentShader(
|
|
|
|
|
toneMap,
|
|
|
|
|
compositeStudioBackground
|
|
|
|
|
? "studio"
|
|
|
|
|
: compositeSceneColor
|
|
|
|
|
? "scene"
|
|
|
|
|
: undefined,
|
|
|
|
|
),
|
|
|
|
|
code: presentShader(toneMap, compositeSceneColor ? "scene" : undefined),
|
|
|
|
|
vertex: { entry: "vertex" },
|
|
|
|
|
fragment: { entry: "fragment", targets: [{ format: "canvas" }] },
|
|
|
|
|
});
|
|
|
|
@@ -1712,7 +1724,7 @@ export class Scene {
|
|
|
|
|
? [{ group: 0, binding: 2, resource: "scene-color" }]
|
|
|
|
|
: []),
|
|
|
|
|
],
|
|
|
|
|
color: [{ resource: "canvas", clear: [0, 0, 0, 1] }],
|
|
|
|
|
color: [{ resource: "canvas", clear: [...this.clearColor] }],
|
|
|
|
|
draw: { vertices: 3 },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
@@ -1729,6 +1741,7 @@ export class Scene {
|
|
|
|
|
pipelines: { render: renderPipelines, compute: computePipelines },
|
|
|
|
|
passes,
|
|
|
|
|
};
|
|
|
|
|
this.#renderGraph = structuredClone(graph);
|
|
|
|
|
const id = await this.core.compileGraph(serialize(graph));
|
|
|
|
|
await this.core.switchLoadout(id);
|
|
|
|
|
}
|
|
|
|
|