Add local glTF viewer and visual coverage
Amp-Thread-ID: https://ampcode.com/threads/T-01a02d1c-27fc-7761-9f51-ed97b2dd4e3e Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
+592
-130
@@ -28,6 +28,7 @@ type PostProcessState = {
|
||||
type TextureState = GraphTexture & {
|
||||
number: number;
|
||||
source?: string | ImageBitmap;
|
||||
sampler?: Omit<GraphSampler, "id">;
|
||||
};
|
||||
|
||||
const rows = [
|
||||
@@ -40,7 +41,9 @@ const rows = [
|
||||
["cameras", 80, "f32"],
|
||||
["cameraMatrices", 80, "f32"],
|
||||
["materials", 48, "f32"],
|
||||
["materialTextures", 32, "u32"],
|
||||
["materialTextures", 64, "u32"],
|
||||
["materialExtensions", 112, "f32"],
|
||||
["materialTextureInfo", 416, "f32"],
|
||||
["pointLights", 32, "f32"],
|
||||
["rectAreaLights", 48, "f32"],
|
||||
["spotLights", 32, "f32"],
|
||||
@@ -49,6 +52,11 @@ const rows = [
|
||||
["sceneAccent", 16, "f32"],
|
||||
] as const;
|
||||
|
||||
const compactLightLimit = 500;
|
||||
const compactPointStart = 11;
|
||||
const compactSpotCount = compactPointStart + compactLightLimit;
|
||||
const compactSpotStart = compactSpotCount + 1;
|
||||
|
||||
const clusterShader = /* wgsl */ `
|
||||
@group(0) @binding(0) var<storage, read> pointLights: array<vec4<f32>>;
|
||||
@group(0) @binding(1) var<storage, read> rectLights: array<vec4<f32>>;
|
||||
@@ -61,12 +69,20 @@ const clusterShader = /* wgsl */ `
|
||||
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
||||
if (id.x != 0u) { return; }
|
||||
var count = 0u;
|
||||
var pointCount = 0u;
|
||||
var spotCount = 0u;
|
||||
var light = 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++) {
|
||||
let enabled = pointLights[i * 2u + 1u].y;
|
||||
count += select(0u, 1u, enabled != 0.0);
|
||||
if (enabled != 0.0) {
|
||||
if (pointCount < ${compactLightLimit}u) {
|
||||
clusters[${compactPointStart}u + pointCount] = i;
|
||||
}
|
||||
pointCount += 1u;
|
||||
}
|
||||
let contribution = pointLights[i * 2u].rgb * pointLights[i * 2u].a * enabled * 0.02;
|
||||
light += contribution;
|
||||
pointLight += contribution;
|
||||
@@ -79,6 +95,12 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
||||
for (var i = 0u; i < arrayLength(&spotLights) / 2u; i++) {
|
||||
let enabled = spotLights[i * 2u + 1u].w;
|
||||
count += select(0u, 1u, enabled != 0.0);
|
||||
if (enabled != 0.0) {
|
||||
if (spotCount < ${compactLightLimit}u) {
|
||||
clusters[${compactSpotStart}u + spotCount] = i;
|
||||
}
|
||||
spotCount += 1u;
|
||||
}
|
||||
let contribution = spotLights[i * 2u].rgb * spotLights[i * 2u].a * enabled * 0.02;
|
||||
light += contribution;
|
||||
spotLight += contribution;
|
||||
@@ -102,6 +124,8 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
||||
clusters[7] = bitcast<u32>(spotLight.r);
|
||||
clusters[8] = bitcast<u32>(spotLight.g);
|
||||
clusters[9] = bitcast<u32>(spotLight.b);
|
||||
clusters[10] = pointCount;
|
||||
clusters[${compactSpotCount}] = spotCount;
|
||||
}`;
|
||||
|
||||
const basicForwardShader = /* wgsl */ `
|
||||
@@ -162,10 +186,70 @@ fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(color, base.a);
|
||||
}`;
|
||||
|
||||
function pbrShader(mask: number) {
|
||||
const baseTexture = mask & 1;
|
||||
const materialTexture = mask & 2;
|
||||
const normalTexture = mask & 4;
|
||||
function pbrShader(
|
||||
mask: number,
|
||||
flags: number,
|
||||
hasColors: boolean,
|
||||
hasUvs2: boolean,
|
||||
transmission: boolean,
|
||||
textureInfo: ArrayLike<number>,
|
||||
extensionInfo: ArrayLike<number>,
|
||||
) {
|
||||
const names = [
|
||||
"baseTexture",
|
||||
"materialTexture",
|
||||
"normalTexture",
|
||||
"emissiveTexture",
|
||||
"occlusionTexture",
|
||||
"transmissionTexture",
|
||||
"thicknessTexture",
|
||||
"clearcoatTexture",
|
||||
"iridescenceTexture",
|
||||
"iridescenceThicknessTexture",
|
||||
"anisotropyTexture",
|
||||
"specularTexture",
|
||||
"specularColorTexture",
|
||||
];
|
||||
const texture = (slot: number) => mask & (1 << slot);
|
||||
const number = (value: number) =>
|
||||
Number.isFinite(value)
|
||||
? Number.isInteger(value)
|
||||
? `${value}.0`
|
||||
: `${value}`
|
||||
: "0.0";
|
||||
const textureUv = (slot: number) => {
|
||||
const at = slot * 8;
|
||||
const source = textureInfo[at + 6] > 0.5 ? "input.uv2" : "input.uv";
|
||||
if (
|
||||
textureInfo[at] === 0 &&
|
||||
textureInfo[at + 1] === 0 &&
|
||||
textureInfo[at + 2] === 1 &&
|
||||
textureInfo[at + 3] === 1 &&
|
||||
textureInfo[at + 4] === 0 &&
|
||||
textureInfo[at + 5] === 1
|
||||
)
|
||||
return source;
|
||||
const x = `(${source}.x * ${number(textureInfo[at + 2])})`;
|
||||
const y = `(${source}.y * ${number(textureInfo[at + 3])})`;
|
||||
return `vec2(${number(textureInfo[at + 5])} * ${x} - ${number(textureInfo[at + 4])} * ${y}, ${number(textureInfo[at + 4])} * ${x} + ${number(textureInfo[at + 5])} * ${y}) + vec2(${number(textureInfo[at])}, ${number(textureInfo[at + 1])})`;
|
||||
};
|
||||
const sample = (slot: number, fallback = "vec4(1.0)") =>
|
||||
texture(slot)
|
||||
? `textureSample(${names[slot]}, materialSampler, ${textureUv(slot)})`
|
||||
: fallback;
|
||||
const normalTexture = texture(2);
|
||||
const alphaMode = flags & 3;
|
||||
const doubleSided = flags & 4;
|
||||
const unlit = flags & 8;
|
||||
const specularGlossiness = flags & 16;
|
||||
const clearcoat = extensionInfo[8] > 0;
|
||||
const sheen =
|
||||
extensionInfo[12] > 0 || extensionInfo[13] > 0 || extensionInfo[14] > 0;
|
||||
const iridescence = extensionInfo[16] > 0;
|
||||
const anisotropy = extensionInfo[20] > 0;
|
||||
const attenuation = transmission && extensionInfo[3] > 0;
|
||||
const ior = Math.max(extensionInfo[1], 1);
|
||||
const dielectric = ((ior - 1) / (ior + 1)) ** 2;
|
||||
return /* wgsl */ `
|
||||
struct VertexOutput {
|
||||
@invariant @builtin(position) position: vec4<f32>,
|
||||
@@ -173,8 +257,9 @@ struct VertexOutput {
|
||||
@location(1) normal: vec3<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
@location(3) tangent: vec4<f32>,
|
||||
@location(4) @interpolate(flat) mesh: u32,
|
||||
@location(5) @interpolate(flat) material: u32,
|
||||
@location(4) color: vec4<f32>,
|
||||
@location(5) uv2: vec2<f32>,
|
||||
@location(6) @interpolate(flat) material: u32,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<storage, read> clusters: array<u32>;
|
||||
@@ -188,9 +273,8 @@ struct VertexOutput {
|
||||
@group(0) @binding(8) var<storage, read> pointLights: array<vec4<f32>>;
|
||||
@group(0) @binding(9) var<storage, read> spotLights: array<vec4<f32>>;
|
||||
${mask ? "@group(1) @binding(0) var materialSampler: sampler;" : ""}
|
||||
${baseTexture ? "@group(1) @binding(1) var baseTexture: texture_2d<f32>;" : ""}
|
||||
${materialTexture ? "@group(1) @binding(2) var materialTexture: texture_2d<f32>;" : ""}
|
||||
${normalTexture ? "@group(1) @binding(3) var normalTexture: texture_2d<f32>;" : ""}
|
||||
${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;" : ""}
|
||||
|
||||
fn rotate(rotor: vec4<f32>, value: vec3<f32>) -> vec3<f32> {
|
||||
return value + 2.0 * cross(rotor.xyz, cross(rotor.xyz, value) + rotor.w * value);
|
||||
@@ -202,6 +286,8 @@ fn vertex(
|
||||
@location(1) localNormal: vec3<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
${normalTexture ? "@location(3) localTangent: vec4<f32>," : ""}
|
||||
${hasColors ? "@location(4) color: vec4<f32>," : ""}
|
||||
${hasUvs2 ? "@location(5) uv2: vec2<f32>," : ""}
|
||||
@builtin(instance_index) packed: u32,
|
||||
) -> VertexOutput {
|
||||
let instance = packed & 65535u;
|
||||
@@ -223,79 +309,184 @@ fn vertex(
|
||||
output.normal = normalize(rotate(rotors[instance], localNormal / scale));
|
||||
output.uv = uv;
|
||||
output.tangent = ${normalTexture ? "vec4(normalize(rotate(rotors[instance], localTangent.xyz * scale)), localTangent.w)" : "vec4(1.0, 0.0, 0.0, 1.0)"};
|
||||
output.mesh = instance;
|
||||
output.material = packed >> 16u;
|
||||
output.color = ${hasColors ? "color" : "vec4(1.0)"};
|
||||
output.uv2 = ${hasUvs2 ? "uv2" : "uv"};
|
||||
output.material = (packed >> 16u) - 1u;
|
||||
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> {
|
||||
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;
|
||||
}
|
||||
|
||||
fn spotContribution(index: u32, normal: vec3<f32>, world: 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); }
|
||||
let fromLight = world - positions[index].xyz;
|
||||
let distance = length(fromLight);
|
||||
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;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let fallback = meshInfo[input.mesh * 4u + 1u];
|
||||
let material = select(fallback, input.material - 1u, input.material != 0u);
|
||||
let factor = materials[material * 3u];
|
||||
let properties = materials[material * 3u + 1u];
|
||||
let extra = materials[material * 3u + 2u];
|
||||
let base = factor * ${baseTexture ? "textureSample(baseTexture, materialSampler, input.uv)" : "vec4(1.0)"};
|
||||
let packedMaterial = ${materialTexture ? "textureSample(materialTexture, materialSampler, input.uv)" : "vec4(1.0)"};
|
||||
let metallic = clamp(properties.x * ${materialTexture ? "packedMaterial.b" : "1.0"}, 0.0, 1.0);
|
||||
let roughness = clamp(properties.y * ${materialTexture ? "packedMaterial.g" : "1.0"}, 0.04, 1.0);
|
||||
fn fragment(input: VertexOutput, @builtin(front_facing) frontFacing: bool) -> @location(0) vec4<f32> {
|
||||
let factor = materials[input.material * 3u];
|
||||
let properties = materials[input.material * 3u + 1u];
|
||||
let extra = materials[input.material * 3u + 2u];
|
||||
let extension0 = vec4(${Array.from(extensionInfo).slice(0, 4).map(number).join(", ")});
|
||||
let extension1 = vec4(${Array.from(extensionInfo).slice(4, 8).map(number).join(", ")});
|
||||
let extension2 = vec4(${Array.from(extensionInfo).slice(8, 12).map(number).join(", ")});
|
||||
let extension3 = vec4(${Array.from(extensionInfo).slice(12, 16).map(number).join(", ")});
|
||||
let extension4 = vec4(${Array.from(extensionInfo).slice(16, 20).map(number).join(", ")});
|
||||
let extension5 = vec4(${Array.from(extensionInfo).slice(20, 24).map(number).join(", ")});
|
||||
let extension6 = vec4(${Array.from(extensionInfo).slice(24, 28).map(number).join(", ")});
|
||||
let base = factor * input.color * ${sample(0)};
|
||||
${alphaMode === 1 ? "if (base.a < extra.z) { discard; }" : ""}
|
||||
let packedMaterial = ${sample(1)};
|
||||
let metallic = clamp(properties.x * ${specularGlossiness ? "0.0" : texture(1) ? "packedMaterial.b" : "1.0"}, 0.0, 1.0);
|
||||
${anisotropy ? `let anisotropyAmount = clamp(extension5.x * ${sample(10)}.b, 0.0, 1.0);` : ""}
|
||||
let roughness = clamp(${specularGlossiness ? `1.0 - (1.0 - properties.y) * ${texture(1) ? "packedMaterial.a" : "1.0"}` : `properties.y * ${texture(1) ? "packedMaterial.g" : "1.0"}`}${anisotropy ? " * mix(1.0, 0.7, anisotropyAmount)" : ""}, 0.04, 1.0);
|
||||
var normal = normalize(input.normal);
|
||||
${normalTexture ? "let tangent = normalize(input.tangent.xyz - normal * dot(input.tangent.xyz, normal)); let bitangent = cross(normal, tangent) * input.tangent.w; let mapped = textureSample(normalTexture, materialSampler, input.uv).xyz * 2.0 - 1.0; normal = normalize(mat3x3<f32>(tangent, bitangent, normal) * vec3(mapped.xy * extra.y, mapped.z));" : ""}
|
||||
${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 f0 = mix(vec3(0.04), base.rgb, metallic);
|
||||
let specular = f0 * pow(nDotH, max(2.0, 2.0 / (roughness * roughness) - 2.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)"};
|
||||
${
|
||||
iridescence
|
||||
? `let iridescenceAmount = clamp(extension4.x * ${sample(8)}.r, 0.0, 1.0);
|
||||
let iridescenceThickness = mix(extension4.z, extension4.w, ${sample(9)}.g);
|
||||
let iridescencePhase = iridescenceThickness * 0.025 + (1.0 - nDotV) * 6.0;
|
||||
let iridescenceTint = 0.12 + 0.88 * (vec3(0.5) + vec3(0.5) * cos(vec3(iridescencePhase, iridescencePhase + 2.0944, iridescencePhase + 4.1888)));
|
||||
let iridescenceLevel = max(max(f0.r, f0.g), max(f0.b, 0.04));
|
||||
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.08) + max(clusterLight - pointAmbient - spotAmbient, vec3(0.0));
|
||||
let ambient = vec3(0.14) + max(clusterLight - pointAmbient - spotAmbient, vec3(0.0));
|
||||
var pointDiffuse = vec3<f32>(0.0);
|
||||
for (var i = 0u; i < arrayLength(&pointLights) / 2u; i++) {
|
||||
let colorIntensity = pointLights[i * 2u];
|
||||
let state = pointLights[i * 2u + 1u];
|
||||
if (state.y == 0.0 || state.x <= 0.0) { continue; }
|
||||
let offset = positions[i].xyz - input.world;
|
||||
let distance = length(offset);
|
||||
let attenuation = pow(max(1.0 - distance / state.x, 0.0), 2.0);
|
||||
let incidence = max(dot(normal, offset / max(distance, 0.0001)), 0.0);
|
||||
pointDiffuse += colorIntensity.rgb * colorIntensity.a * attenuation * incidence * 0.08;
|
||||
let pointCount = clusters[10];
|
||||
if (pointCount <= ${compactLightLimit}u) {
|
||||
for (var light = 0u; light < pointCount; light++) {
|
||||
pointDiffuse += pointContribution(clusters[${compactPointStart}u + light], normal, input.world);
|
||||
}
|
||||
} else {
|
||||
for (var light = 0u; light < arrayLength(&pointLights) / 2u; light++) {
|
||||
pointDiffuse += pointContribution(light, normal, input.world);
|
||||
}
|
||||
}
|
||||
var spotDiffuse = vec3<f32>(0.0);
|
||||
for (var i = 0u; i < arrayLength(&spotLights) / 2u; i++) {
|
||||
let colorIntensity = spotLights[i * 2u];
|
||||
let state = spotLights[i * 2u + 1u];
|
||||
if (state.w == 0.0 || state.x <= 0.0) { continue; }
|
||||
let fromLight = input.world - positions[i].xyz;
|
||||
let distance = length(fromLight);
|
||||
let lightDirection = normalize(rotate(rotors[i], vec3<f32>(0.0, 0.0, -1.0)));
|
||||
let cone = smoothstep(cos(state.z), cos(state.y), dot(lightDirection, fromLight / max(distance, 0.0001)));
|
||||
let attenuation = pow(max(1.0 - distance / state.x, 0.0), 2.0);
|
||||
let incidence = max(dot(normal, -fromLight / max(distance, 0.0001)), 0.0);
|
||||
spotDiffuse += colorIntensity.rgb * colorIntensity.a * cone * attenuation * incidence * 0.08;
|
||||
let spotCount = clusters[${compactSpotCount}];
|
||||
if (spotCount <= ${compactLightLimit}u) {
|
||||
for (var light = 0u; light < spotCount; light++) {
|
||||
spotDiffuse += spotContribution(clusters[${compactSpotStart}u + light], normal, input.world);
|
||||
}
|
||||
} else {
|
||||
for (var light = 0u; light < arrayLength(&spotLights) / 2u; light++) {
|
||||
spotDiffuse += spotContribution(light, normal, input.world);
|
||||
}
|
||||
}
|
||||
let defaultLight = select(1.0, 0.18, clusters[0] != 0u);
|
||||
let diffuse = base.rgb * (1.0 - metallic) * nDotL * defaultLight;
|
||||
let emissive = vec3(properties.z, properties.w, extra.x);
|
||||
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);
|
||||
return vec4((base.rgb * ambient + diffuse + localDiffuse + specular * nDotL * defaultLight + emissive) * accent.a, base.a);
|
||||
${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));`
|
||||
: ""
|
||||
}
|
||||
var color = base.rgb * ambient * occlusion + diffuse + environment + localDiffuse${sheen ? " + sheen" : ""}${clearcoat ? " + vec3(clearcoat)" : ""} + emissive;
|
||||
${unlit ? "color = base.rgb + emissive;" : ""}
|
||||
${
|
||||
transmission
|
||||
? `let transmissionAmount = clamp(extension0.x * ${sample(5)}.r, 0.0, 1.0);
|
||||
let thickness = max(extension0.z * ${sample(6)}.g, 0.0);
|
||||
let screenUv = input.position.xy / vec2<f32>(textureDimensions(sceneColor));
|
||||
let refractionOffset = normal.xy * ${number(1 - 1 / ior)} * mix(0.006, 0.035, clamp(thickness, 0.0, 1.0));
|
||||
let refractedUv = clamp(screenUv + refractionOffset, vec2(0.001), vec2(0.999));
|
||||
let transmissionBlur = vec2(0.02078461 * roughness);
|
||||
var transmitted = textureSampleLevel(sceneColor, sceneSampler, refractedUv, 0.0).rgb * 0.2;
|
||||
transmitted += textureSampleLevel(sceneColor, sceneSampler, clamp(refractedUv + transmissionBlur, vec2(0.001), vec2(0.999)), 0.0).rgb * 0.2;
|
||||
transmitted += textureSampleLevel(sceneColor, sceneSampler, clamp(refractedUv - transmissionBlur, vec2(0.001), vec2(0.999)), 0.0).rgb * 0.2;
|
||||
transmitted += textureSampleLevel(sceneColor, sceneSampler, clamp(refractedUv + vec2(transmissionBlur.x, -transmissionBlur.y), vec2(0.001), vec2(0.999)), 0.0).rgb * 0.2;
|
||||
transmitted += textureSampleLevel(sceneColor, sceneSampler, clamp(refractedUv + vec2(-transmissionBlur.x, transmissionBlur.y), vec2(0.001), vec2(0.999)), 0.0).rgb * 0.2;
|
||||
${
|
||||
attenuation
|
||||
? `{
|
||||
let travel = thickness / max(abs(dot(normal, view)), 0.15);
|
||||
transmitted *= pow(clamp(extension1.rgb, vec3(0.001), vec3(1.0)), vec3(travel / extension0.w));
|
||||
}`
|
||||
: ""
|
||||
}
|
||||
let normalizedTransmissionTint = base.rgb / max(max(base.r, base.g), max(base.b, 0.001));
|
||||
transmitted *= normalizedTransmissionTint;
|
||||
color = mix(color, transmitted + reflected * environmentFresnel, transmissionAmount);`
|
||||
: ""
|
||||
}
|
||||
return vec4(color * accent.a, ${alphaMode === 2 ? "base.a" : "1.0"});
|
||||
}`;
|
||||
}
|
||||
|
||||
const emptyForwardShader = /* wgsl */ `
|
||||
struct Accent { color: vec4<f32> }
|
||||
@group(0) @binding(0) var<uniform> accent: Accent;
|
||||
struct VertexOutput { @builtin(position) position: vec4<f32> }
|
||||
@vertex fn vertex(@builtin(vertex_index) index: u32) -> VertexOutput {
|
||||
let points = array(vec2(-0.72, -0.6), vec2(0.72, -0.6), vec2(0.0, 0.72));
|
||||
var output: VertexOutput;
|
||||
output.position = vec4(points[index], 0.0, 1.0);
|
||||
return output;
|
||||
const clearShader = /* wgsl */ `
|
||||
@vertex fn vertex() -> @builtin(position) vec4<f32> {
|
||||
return vec4(0.0);
|
||||
}
|
||||
@fragment fn fragment() -> @location(0) vec4<f32> {
|
||||
return vec4(accent.color.rgb, 1.0);
|
||||
return vec4(0.0);
|
||||
}`;
|
||||
|
||||
const fullscreenVertex = /* wgsl */ `
|
||||
@@ -311,6 +502,28 @@ struct VertexOutput {
|
||||
return output;
|
||||
}`;
|
||||
|
||||
const copyShader = `${fullscreenVertex}
|
||||
@group(0) @binding(0) var source: texture_2d<f32>;
|
||||
@group(0) @binding(1) var sourceSampler: sampler;
|
||||
@fragment fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
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;
|
||||
@@ -330,7 +543,10 @@ function effectFragment(kind: string, options: Record<string, unknown>) {
|
||||
}`;
|
||||
}
|
||||
|
||||
function presentShader(toneMap: string) {
|
||||
function presentShader(
|
||||
toneMap: string,
|
||||
composite: "studio" | "scene" | undefined,
|
||||
) {
|
||||
const tone =
|
||||
toneMap === "reinhard"
|
||||
? "color / (color + vec3(1.0))"
|
||||
@@ -338,12 +554,14 @@ function presentShader(toneMap: string) {
|
||||
? "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(value.rgb, vec3(0.0));
|
||||
return vec4(${tone}, value.a);
|
||||
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));
|
||||
return vec4(${tone}, ${composite ? "1.0" : "value.a"});
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -402,7 +620,12 @@ export class Scene {
|
||||
|
||||
constructor(
|
||||
canvas: HTMLCanvasElement,
|
||||
options: { arenaBytes?: number; debug?: boolean; fps?: number; hdr?: boolean } = {},
|
||||
options: {
|
||||
arenaBytes?: number;
|
||||
debug?: boolean;
|
||||
fps?: number;
|
||||
hdr?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
this.hdr = options.hdr ?? true;
|
||||
this.core = new YawnCore(canvas, {
|
||||
@@ -429,7 +652,22 @@ export class Scene {
|
||||
const material = await this.core.allocateObject("materials");
|
||||
this.core
|
||||
.array("materials")
|
||||
.write(material, [1, 1, 1, 1, 0, 0.7, 0, 0, 0, 0, 1, 0.5]);
|
||||
.write(material, [1, 1, 1, 1, 0, 0.7, 0, 0, 0, 1, 0.5, 0]);
|
||||
this.core
|
||||
.array("materialExtensions")
|
||||
.write(
|
||||
material,
|
||||
[
|
||||
0, 1.5, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1.3, 100, 400, 0,
|
||||
0, 0, 0, 1, 1, 1, 0,
|
||||
],
|
||||
);
|
||||
const textureInfo = this.core.array("materialTextureInfo").row(material);
|
||||
for (let slot = 0; slot < 13; slot++) {
|
||||
textureInfo[slot * 8 + 2] = 1;
|
||||
textureInfo[slot * 8 + 3] = 1;
|
||||
textureInfo[slot * 8 + 5] = 1;
|
||||
}
|
||||
if (fps !== undefined) await this.core.setFps(fps);
|
||||
await this.#compileRenderGraph();
|
||||
return this;
|
||||
@@ -471,7 +709,8 @@ export class Scene {
|
||||
const value = Reflect.get(target, property, target);
|
||||
if (typeof value !== "function") return value;
|
||||
if (property === "constructor") return value;
|
||||
if (!typedArrayMutators.has(String(property))) return value.bind(target);
|
||||
if (!typedArrayMutators.has(String(property)))
|
||||
return value.bind(target);
|
||||
return (...arguments_: unknown[]) => {
|
||||
const result = Reflect.apply(value, target, arguments_);
|
||||
this.markDirty();
|
||||
@@ -570,9 +809,21 @@ export class Scene {
|
||||
{
|
||||
name: "materialTextures",
|
||||
rows: materialCapacity,
|
||||
stride: 32,
|
||||
stride: 64,
|
||||
format: "u32" as const,
|
||||
},
|
||||
{
|
||||
name: "materialExtensions",
|
||||
rows: materialCapacity,
|
||||
stride: 112,
|
||||
format: "f32" as const,
|
||||
},
|
||||
{
|
||||
name: "materialTextureInfo",
|
||||
rows: materialCapacity,
|
||||
stride: 416,
|
||||
format: "f32" as const,
|
||||
},
|
||||
].filter((request) => this.array(request.name).rows < request.rows);
|
||||
if (growth.length) await this.core.createRowsBatch(growth);
|
||||
}
|
||||
@@ -623,7 +874,11 @@ export class Scene {
|
||||
async allocateMaterial() {
|
||||
await this.ready;
|
||||
const id = await this.core.allocateObject("materials");
|
||||
await this.ensureRows("materialTextures", id + 1, 32, "u32");
|
||||
await Promise.all([
|
||||
this.ensureRows("materialTextures", id + 1, 64, "u32"),
|
||||
this.ensureRows("materialExtensions", id + 1, 112, "f32"),
|
||||
this.ensureRows("materialTextureInfo", id + 1, 416, "f32"),
|
||||
]);
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -713,6 +968,7 @@ export class Scene {
|
||||
normals: 3,
|
||||
tangents: 4,
|
||||
uvs: 2,
|
||||
uvs2: 2,
|
||||
colors: 4,
|
||||
indices: 1,
|
||||
};
|
||||
@@ -850,9 +1106,34 @@ export class Scene {
|
||||
}
|
||||
|
||||
const computeIds = [...this.#computePasses.keys()];
|
||||
const renderedMeshes = [...this.#meshes.values()].filter(
|
||||
(mesh) => mesh.vertexCount > 0,
|
||||
);
|
||||
const renderCategory = (material: number) => {
|
||||
const flags = Math.round(this.array("materials").row(material)[11]);
|
||||
const transmitted = this.array("materialExtensions").row(material)[0] > 0;
|
||||
return transmitted ? 1 : (flags & 3) === 2 ? 2 : 0;
|
||||
};
|
||||
const renderedMeshes = [...this.#meshes.values()]
|
||||
.filter((mesh) => mesh.vertexCount > 0)
|
||||
.sort(
|
||||
(left, right) =>
|
||||
renderCategory(Number(this.array("meshInfo").row(left.id)[1])) -
|
||||
renderCategory(Number(this.array("meshInfo").row(right.id)[1])),
|
||||
);
|
||||
const hasTransmission = renderedMeshes.some((mesh) => {
|
||||
const materials = new Set([
|
||||
Number(this.array("meshInfo").row(mesh.id)[1]),
|
||||
...mesh.faceMaterials.values(),
|
||||
]);
|
||||
return [...materials].some(
|
||||
(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";
|
||||
addTexture({
|
||||
id: "hdr",
|
||||
@@ -868,6 +1149,14 @@ export class Scene {
|
||||
usage: ["render"],
|
||||
transient: true,
|
||||
});
|
||||
if (compositeSceneColor)
|
||||
addTexture({
|
||||
id: "scene-color",
|
||||
format: hdrFormat,
|
||||
size: ["canvas", "canvas", 1],
|
||||
usage: ["render", "sampled"],
|
||||
transient: true,
|
||||
});
|
||||
addSampler({ id: "linear", magFilter: "linear", minFilter: "linear" });
|
||||
addSampler({
|
||||
id: "material-linear",
|
||||
@@ -878,14 +1167,21 @@ export class Scene {
|
||||
addressModeV: "repeat",
|
||||
anisotropyClamp: 16,
|
||||
});
|
||||
for (const { number: _, source: __, ...texture } of this.#textures.values())
|
||||
for (const {
|
||||
number: _,
|
||||
source: __,
|
||||
sampler,
|
||||
...texture
|
||||
} of this.#textures.values()) {
|
||||
addTexture(texture);
|
||||
if (sampler) addSampler({ id: `${texture.id}-sampler`, ...sampler });
|
||||
}
|
||||
|
||||
let previous = computeIds.length ? computeIds : ["cluster-lights"];
|
||||
if (!renderedMeshes.length) {
|
||||
renderPipelines.push({
|
||||
id: "empty-forward",
|
||||
code: emptyForwardShader,
|
||||
code: clearShader,
|
||||
vertex: { entry: "vertex" },
|
||||
fragment: { entry: "fragment", targets: [{ format: hdrFormat }] },
|
||||
});
|
||||
@@ -894,9 +1190,8 @@ export class Scene {
|
||||
type: "render",
|
||||
pipeline: "empty-forward",
|
||||
after: previous,
|
||||
bindings: [{ group: 0, binding: 0, resource: "accent" }],
|
||||
color: [{ resource: "hdr", clear: [0.015, 0.025, 0.05, 1] }],
|
||||
draw: { vertices: 3 },
|
||||
draw: { vertices: 0 },
|
||||
});
|
||||
previous = ["forward-empty"];
|
||||
} else {
|
||||
@@ -909,8 +1204,33 @@ export class Scene {
|
||||
["camera-matrices", "cameraMatrices"],
|
||||
])
|
||||
addBuffer({ id, array, usage: ["storage"] });
|
||||
const forwardPipelines = new Set<string>();
|
||||
let firstRender = true;
|
||||
if (!compositeStudioBackground) {
|
||||
renderPipelines.push({
|
||||
id: "studio-background",
|
||||
code: studioBackgroundShader,
|
||||
vertex: { entry: "vertex" },
|
||||
fragment: { entry: "fragment", targets: [{ format: hdrFormat }] },
|
||||
});
|
||||
passes.push({
|
||||
id: "studio-background",
|
||||
type: "render",
|
||||
pipeline: "studio-background",
|
||||
after: previous,
|
||||
color: [
|
||||
{
|
||||
resource: compositeSceneColor ? "scene-color" : "hdr",
|
||||
clear: [0, 0, 0, 1],
|
||||
},
|
||||
],
|
||||
draw: { vertices: 3 },
|
||||
});
|
||||
previous = ["studio-background"];
|
||||
}
|
||||
const forwardPipelines = new Map<string, string>();
|
||||
let firstColor = true;
|
||||
let firstDepth = true;
|
||||
let sceneColorReady = false;
|
||||
let forwardTarget = compositeSceneColor ? "scene-color" : "hdr";
|
||||
for (const mesh of renderedMeshes) {
|
||||
const vertex = `geometry-${mesh.geometryId}-positions`;
|
||||
addBuffer({
|
||||
@@ -921,14 +1241,20 @@ export class Scene {
|
||||
});
|
||||
const normal = `geometry-${mesh.geometryId}-normals`;
|
||||
const uv = `geometry-${mesh.geometryId}-uvs`;
|
||||
const uv2 = `geometry-${mesh.geometryId}-uvs2`;
|
||||
const tangent = `geometry-${mesh.geometryId}-tangents`;
|
||||
const color = `geometry-${mesh.geometryId}-colors`;
|
||||
const hasNormals = !!this.geometryData(mesh.geometryId, "normals");
|
||||
const hasUvs = !!this.geometryData(mesh.geometryId, "uvs");
|
||||
const hasUvs2 = !!this.geometryData(mesh.geometryId, "uvs2");
|
||||
const hasTangents = !!this.geometryData(mesh.geometryId, "tangents");
|
||||
const hasColors = !!this.geometryData(mesh.geometryId, "colors");
|
||||
for (const [present, id, kind] of [
|
||||
[hasNormals, normal, "normals"],
|
||||
[hasUvs, uv, "uvs"],
|
||||
[hasUvs2, uv2, "uvs2"],
|
||||
[hasTangents, tangent, "tangents"],
|
||||
[hasColors, color, "colors"],
|
||||
] as const)
|
||||
if (present)
|
||||
addBuffer({
|
||||
@@ -977,21 +1303,91 @@ export class Scene {
|
||||
pointers[lane]
|
||||
? this.#textures.get(Number(pointers[lane]) - 1)
|
||||
: undefined;
|
||||
const baseTexture = texture(0);
|
||||
const materialTexture = texture(1);
|
||||
const normalTexture = hasTangents ? texture(2) : undefined;
|
||||
const materialTextures = Array.from({ length: 13 }, (_, lane) =>
|
||||
lane === 2 && !hasTangents ? undefined : texture(lane),
|
||||
);
|
||||
const normalTexture = materialTextures[2];
|
||||
const detailed = hasNormals && hasUvs;
|
||||
const mask = detailed
|
||||
? (baseTexture ? 1 : 0) |
|
||||
(materialTexture ? 2 : 0) |
|
||||
(normalTexture ? 4 : 0)
|
||||
const flags = Math.round(this.array("materials").row(material)[11]);
|
||||
const extensionInfo = this.array("materialExtensions").row(material);
|
||||
const transmitted = extensionInfo[0] > 0;
|
||||
let mask = detailed
|
||||
? materialTextures.reduce(
|
||||
(value, current, lane) =>
|
||||
value | (current === undefined ? 0 : 1 << lane),
|
||||
0,
|
||||
)
|
||||
: 0;
|
||||
const pipeline = detailed ? `forward-pbr-${mask}` : "forward-basic";
|
||||
if (!forwardPipelines.has(pipeline)) {
|
||||
forwardPipelines.add(pipeline);
|
||||
if (!transmitted) mask &= ~((1 << 5) | (1 << 6));
|
||||
else if (extensionInfo[2] <= 0) mask &= ~(1 << 6);
|
||||
if (extensionInfo[8] <= 0) mask &= ~(1 << 7);
|
||||
if (extensionInfo[16] <= 0) mask &= ~((1 << 8) | (1 << 9));
|
||||
if (extensionInfo[20] <= 0) mask &= ~(1 << 10);
|
||||
if (extensionInfo[11] <= 0) mask &= ~(1 << 4);
|
||||
if ((flags & 8) !== 0 && !transmitted) mask &= (1 << 0) | (1 << 3);
|
||||
const tangentSlot = normalTexture ? 3 : -1;
|
||||
const colorSlot = hasColors ? 3 + Number(!!normalTexture) : -1;
|
||||
const uv2Slot = hasUvs2
|
||||
? 3 + Number(!!normalTexture) + Number(hasColors)
|
||||
: -1;
|
||||
const textureInfo = this.array("materialTextureInfo").row(material);
|
||||
let clearCompositeLayer = false;
|
||||
if (transmitted && !sceneColorReady) {
|
||||
sceneColorReady = true;
|
||||
if (compositeSceneColor) {
|
||||
forwardTarget = "hdr";
|
||||
clearCompositeLayer = true;
|
||||
} else {
|
||||
addTexture({
|
||||
id: "scene-color",
|
||||
format: hdrFormat,
|
||||
size: ["canvas", "canvas", 1],
|
||||
usage: ["render", "sampled"],
|
||||
transient: true,
|
||||
});
|
||||
renderPipelines.push({
|
||||
id: "copy-scene-color",
|
||||
code: copyShader,
|
||||
vertex: { entry: "vertex" },
|
||||
fragment: {
|
||||
entry: "fragment",
|
||||
targets: [{ format: hdrFormat }],
|
||||
},
|
||||
});
|
||||
passes.push({
|
||||
id: "copy-scene-color",
|
||||
type: "render",
|
||||
pipeline: "copy-scene-color",
|
||||
after: previous,
|
||||
bindings: [
|
||||
{ group: 0, binding: 0, resource: "hdr" },
|
||||
{ group: 0, binding: 1, resource: "linear" },
|
||||
],
|
||||
color: [{ resource: "scene-color", clear: [0, 0, 0, 1] }],
|
||||
draw: { vertices: 3 },
|
||||
});
|
||||
previous = ["copy-scene-color"];
|
||||
}
|
||||
}
|
||||
const code = detailed
|
||||
? pbrShader(
|
||||
mask,
|
||||
flags,
|
||||
hasColors,
|
||||
hasUvs2,
|
||||
transmitted,
|
||||
textureInfo,
|
||||
extensionInfo,
|
||||
)
|
||||
: basicForwardShader;
|
||||
const pipelineKey = `${flags}-${transmitted ? 1 : 0}\n${code}`;
|
||||
let pipeline = forwardPipelines.get(pipelineKey);
|
||||
if (!pipeline) {
|
||||
pipeline = `forward-${detailed ? "pbr" : "basic"}-${forwardPipelines.size}`;
|
||||
forwardPipelines.set(pipelineKey, pipeline);
|
||||
renderPipelines.push({
|
||||
id: pipeline,
|
||||
code: detailed ? pbrShader(mask) : basicForwardShader,
|
||||
code,
|
||||
vertex: {
|
||||
entry: "vertex",
|
||||
buffers: detailed
|
||||
@@ -1040,6 +1436,34 @@ export class Scene {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(hasColors
|
||||
? [
|
||||
{
|
||||
arrayStride: 16,
|
||||
attributes: [
|
||||
{
|
||||
format: "float32x4",
|
||||
offset: 0,
|
||||
shaderLocation: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(hasUvs2
|
||||
? [
|
||||
{
|
||||
arrayStride: 16,
|
||||
attributes: [
|
||||
{
|
||||
format: "float32x2",
|
||||
offset: 0,
|
||||
shaderLocation: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]
|
||||
: [
|
||||
{
|
||||
@@ -1056,21 +1480,45 @@ export class Scene {
|
||||
},
|
||||
fragment: {
|
||||
entry: "fragment",
|
||||
targets: [{ format: hdrFormat }],
|
||||
targets: [
|
||||
{
|
||||
format: hdrFormat,
|
||||
...((flags & 3) === 2
|
||||
? {
|
||||
blend: {
|
||||
color: {
|
||||
srcFactor: "src-alpha",
|
||||
dstFactor: "one-minus-src-alpha",
|
||||
operation: "add",
|
||||
},
|
||||
alpha: {
|
||||
srcFactor: "one",
|
||||
dstFactor: "one-minus-src-alpha",
|
||||
operation: "add",
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
],
|
||||
},
|
||||
depthStencil: {
|
||||
format: "depth24plus",
|
||||
depth_write_enabled: true,
|
||||
depth_write_enabled: (flags & 3) !== 2 && !transmitted,
|
||||
depth_compare: "less",
|
||||
},
|
||||
primitive: {
|
||||
topology: "triangle-list",
|
||||
frontFace: "ccw",
|
||||
cullMode: (flags & 4) !== 0 ? null : "back",
|
||||
conservative: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
const instance =
|
||||
(mesh.id +
|
||||
(draw.material === undefined
|
||||
? 0
|
||||
: (draw.material + 1) * 65536)) >>>
|
||||
0;
|
||||
const instance = (mesh.id + (material + 1) * 65536) >>> 0;
|
||||
const samplerTexture = materialTextures.find(
|
||||
(value, lane) => value !== undefined && (mask & (1 << lane)) !== 0,
|
||||
);
|
||||
passes.push({
|
||||
id,
|
||||
type: "render",
|
||||
@@ -1099,48 +1547,45 @@ export class Scene {
|
||||
: []),
|
||||
...(detailed && mask
|
||||
? [
|
||||
{ group: 1, binding: 0, resource: "material-linear" },
|
||||
...(baseTexture
|
||||
? [
|
||||
{
|
||||
group: 1,
|
||||
binding: 1,
|
||||
resource: baseTexture.id,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(materialTexture
|
||||
? [
|
||||
{
|
||||
group: 1,
|
||||
binding: 2,
|
||||
resource: materialTexture.id,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(normalTexture
|
||||
? [
|
||||
{
|
||||
group: 1,
|
||||
binding: 3,
|
||||
resource: normalTexture.id,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
group: 1,
|
||||
binding: 0,
|
||||
resource: samplerTexture?.sampler
|
||||
? `${samplerTexture.id}-sampler`
|
||||
: "material-linear",
|
||||
},
|
||||
...materialTextures.flatMap((value, lane) =>
|
||||
value && (mask & (1 << lane)) !== 0
|
||||
? [
|
||||
{
|
||||
group: 1,
|
||||
binding: lane + 1,
|
||||
resource: value.id,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
),
|
||||
]
|
||||
: []),
|
||||
...(detailed && transmitted
|
||||
? [
|
||||
{ group: 2, binding: 0, resource: "scene-color" },
|
||||
{ group: 2, binding: 1, resource: "linear" },
|
||||
]
|
||||
: []),
|
||||
],
|
||||
color: [
|
||||
{
|
||||
resource: "hdr",
|
||||
...(firstRender
|
||||
? { clear: [0.015, 0.025, 0.05, 1] }
|
||||
resource: forwardTarget,
|
||||
...((firstColor && compositeStudioBackground) ||
|
||||
clearCompositeLayer
|
||||
? { clear: [0, 0, 0, 0] }
|
||||
: { load: "load" }),
|
||||
},
|
||||
],
|
||||
depth: {
|
||||
resource: "depth",
|
||||
...(firstRender ? { clear: 1 } : { load: "load" }),
|
||||
...(firstDepth ? { clear: 1 } : { load: "load" }),
|
||||
},
|
||||
vertexBuffers: [
|
||||
{ slot: 0, resource: vertex },
|
||||
@@ -1148,7 +1593,13 @@ export class Scene {
|
||||
? [
|
||||
{ slot: 1, resource: normal },
|
||||
{ slot: 2, resource: uv },
|
||||
...(normalTexture ? [{ slot: 3, resource: tangent }] : []),
|
||||
...(normalTexture
|
||||
? [{ slot: tangentSlot, resource: tangent }]
|
||||
: []),
|
||||
...(hasColors
|
||||
? [{ slot: colorSlot, resource: color }]
|
||||
: []),
|
||||
...(hasUvs2 ? [{ slot: uv2Slot, resource: uv2 }] : []),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
@@ -1169,7 +1620,8 @@ export class Scene {
|
||||
}
|
||||
: { vertices: draw.count, instances: 1, firstInstance: instance },
|
||||
});
|
||||
firstRender = false;
|
||||
firstColor = false;
|
||||
firstDepth = false;
|
||||
previous = [id];
|
||||
}
|
||||
}
|
||||
@@ -1237,7 +1689,14 @@ export class Scene {
|
||||
);
|
||||
renderPipelines.push({
|
||||
id: "present",
|
||||
code: presentShader(toneMap),
|
||||
code: presentShader(
|
||||
toneMap,
|
||||
compositeStudioBackground
|
||||
? "studio"
|
||||
: compositeSceneColor
|
||||
? "scene"
|
||||
: undefined,
|
||||
),
|
||||
vertex: { entry: "vertex" },
|
||||
fragment: { entry: "fragment", targets: [{ format: "canvas" }] },
|
||||
});
|
||||
@@ -1249,6 +1708,9 @@ export class Scene {
|
||||
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: [0, 0, 0, 1] }],
|
||||
draw: { vertices: 3 },
|
||||
|
||||
Reference in New Issue
Block a user