Enhance CDN packages and playground
Amp-Thread-ID: https://ampcode.com/threads/T-01a02485-5574-707c-bff4-5668d83bee8a Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
@@ -62,10 +62,14 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
||||
if (id.x != 0u) { return; }
|
||||
var count = 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);
|
||||
light += pointLights[i * 2u].rgb * pointLights[i * 2u].a * enabled * 0.02;
|
||||
let contribution = pointLights[i * 2u].rgb * pointLights[i * 2u].a * enabled * 0.02;
|
||||
light += contribution;
|
||||
pointLight += contribution;
|
||||
}
|
||||
for (var i = 0u; i < arrayLength(&rectLights) / 3u; i++) {
|
||||
let enabled = rectLights[i * 3u + 1u].z;
|
||||
@@ -75,7 +79,9 @@ 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);
|
||||
light += spotLights[i * 2u].rgb * spotLights[i * 2u].a * enabled * 0.02;
|
||||
let contribution = spotLights[i * 2u].rgb * spotLights[i * 2u].a * enabled * 0.02;
|
||||
light += contribution;
|
||||
spotLight += contribution;
|
||||
}
|
||||
for (var i = 0u; i < arrayLength(&directionalLights) / 2u; i++) {
|
||||
let enabled = directionalLights[i * 2u + 1u].x;
|
||||
@@ -90,6 +96,12 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
||||
clusters[1] = bitcast<u32>(light.r);
|
||||
clusters[2] = bitcast<u32>(light.g);
|
||||
clusters[3] = bitcast<u32>(light.b);
|
||||
clusters[4] = bitcast<u32>(pointLight.r);
|
||||
clusters[5] = bitcast<u32>(pointLight.g);
|
||||
clusters[6] = bitcast<u32>(pointLight.b);
|
||||
clusters[7] = bitcast<u32>(spotLight.r);
|
||||
clusters[8] = bitcast<u32>(spotLight.g);
|
||||
clusters[9] = bitcast<u32>(spotLight.b);
|
||||
}`;
|
||||
|
||||
const basicForwardShader = /* wgsl */ `
|
||||
@@ -173,6 +185,8 @@ 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>>;
|
||||
${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>;" : ""}
|
||||
@@ -235,10 +249,38 @@ fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let f0 = mix(vec3(0.04), base.rgb, metallic);
|
||||
let specular = f0 * pow(nDotH, max(2.0, 2.0 / (roughness * roughness) - 2.0));
|
||||
let clusterLight = vec3<f32>(bitcast<f32>(clusters[1]), bitcast<f32>(clusters[2]), bitcast<f32>(clusters[3]));
|
||||
let ambient = vec3(0.08) + clusterLight;
|
||||
let diffuse = base.rgb * (1.0 - metallic) * nDotL;
|
||||
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));
|
||||
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;
|
||||
}
|
||||
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 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);
|
||||
return vec4((base.rgb * ambient + diffuse + specular * nDotL + emissive) * accent.a, base.a);
|
||||
let localDiffuse = base.rgb * (1.0 - metallic) * (pointDiffuse + spotDiffuse);
|
||||
return vec4((base.rgb * ambient + diffuse + localDiffuse + specular * nDotL * defaultLight + emissive) * accent.a, base.a);
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -1049,6 +1091,12 @@ export class Scene {
|
||||
binding,
|
||||
resource,
|
||||
})),
|
||||
...(detailed
|
||||
? [
|
||||
{ group: 0, binding: 8, resource: "point-lights" },
|
||||
{ group: 0, binding: 9, resource: "spot-lights" },
|
||||
]
|
||||
: []),
|
||||
...(detailed && mask
|
||||
? [
|
||||
{ group: 1, binding: 0, resource: "material-linear" },
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from "./Scene";
|
||||
export * from "./ComputePass";
|
||||
export * from "./Node";
|
||||
export * from "./Mesh";
|
||||
export * from "./shapes";
|
||||
export * from "./camera/Camera";
|
||||
export * from "./camera/ArcRotateCamera";
|
||||
export * from "./camera/FreeCamera";
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Mesh, type MeshOptions } from "../Mesh";
|
||||
import type { Scene } from "../Scene";
|
||||
|
||||
export type CubeOptions = Omit<MeshOptions, "geometryId" | "vertexData"> & {
|
||||
size?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
depth?: number;
|
||||
};
|
||||
|
||||
function dimension(value: number, name: string) {
|
||||
if (!Number.isFinite(value) || value <= 0) throw new RangeError(name);
|
||||
return value / 2;
|
||||
}
|
||||
|
||||
/** A six-sided mesh with independent vertices and normals for every face. */
|
||||
export class Cube extends Mesh {
|
||||
constructor(scene: Scene, options: CubeOptions = {}) {
|
||||
const {
|
||||
size = 1,
|
||||
width = size,
|
||||
height = size,
|
||||
depth = size,
|
||||
...meshOptions
|
||||
} = options;
|
||||
const x = dimension(width, "width");
|
||||
const y = dimension(height, "height");
|
||||
const z = dimension(depth, "depth");
|
||||
const positions = [
|
||||
[-x, -y, z, x, -y, z, x, y, z, -x, y, z],
|
||||
[x, -y, -z, -x, -y, -z, -x, y, -z, x, y, -z],
|
||||
[x, -y, z, x, -y, -z, x, y, -z, x, y, z],
|
||||
[-x, -y, -z, -x, -y, z, -x, y, z, -x, y, -z],
|
||||
[-x, y, z, x, y, z, x, y, -z, -x, y, -z],
|
||||
[-x, -y, -z, x, -y, -z, x, -y, z, -x, -y, z],
|
||||
].flat();
|
||||
const normals = [
|
||||
[0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1],
|
||||
[0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1],
|
||||
[1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0],
|
||||
[-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0],
|
||||
[0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0],
|
||||
[0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0],
|
||||
].flat();
|
||||
const uvs = Array.from({ length: 6 }, () => [
|
||||
0, 0, 1, 0, 1, 1, 0, 1,
|
||||
]).flat();
|
||||
const indices = Array.from({ length: 6 }, (_, face) => {
|
||||
const vertex = face * 4;
|
||||
return [vertex, vertex + 1, vertex + 2, vertex, vertex + 2, vertex + 3];
|
||||
}).flat();
|
||||
super(scene, {
|
||||
...meshOptions,
|
||||
vertexData: { positions, normals, uvs, indices },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Mesh, type MeshOptions } from "../Mesh";
|
||||
import type { Scene } from "../Scene";
|
||||
|
||||
export type IcosphereOptions = Omit<
|
||||
MeshOptions,
|
||||
"geometryId" | "vertexData"
|
||||
> & {
|
||||
radius?: number;
|
||||
subdivisions?: number;
|
||||
};
|
||||
|
||||
function normalized(vertex: number[]) {
|
||||
const length = Math.hypot(...vertex);
|
||||
return vertex.map((lane) => lane / length);
|
||||
}
|
||||
|
||||
/** A sphere produced by recursively subdividing an icosahedron. */
|
||||
export class Icosphere extends Mesh {
|
||||
constructor(scene: Scene, options: IcosphereOptions = {}) {
|
||||
const { radius = 0.5, subdivisions = 2, ...meshOptions } = options;
|
||||
if (!Number.isFinite(radius) || radius <= 0) throw new RangeError("radius");
|
||||
if (!Number.isInteger(subdivisions) || subdivisions < 0 || subdivisions > 6)
|
||||
throw new RangeError("subdivisions");
|
||||
|
||||
const golden = (1 + Math.sqrt(5)) / 2;
|
||||
const vertices = [
|
||||
[-1, golden, 0],
|
||||
[1, golden, 0],
|
||||
[-1, -golden, 0],
|
||||
[1, -golden, 0],
|
||||
[0, -1, golden],
|
||||
[0, 1, golden],
|
||||
[0, -1, -golden],
|
||||
[0, 1, -golden],
|
||||
[golden, 0, -1],
|
||||
[golden, 0, 1],
|
||||
[-golden, 0, -1],
|
||||
[-golden, 0, 1],
|
||||
].map(normalized);
|
||||
let faces = [
|
||||
[0, 11, 5],
|
||||
[0, 5, 1],
|
||||
[0, 1, 7],
|
||||
[0, 7, 10],
|
||||
[0, 10, 11],
|
||||
[1, 5, 9],
|
||||
[5, 11, 4],
|
||||
[11, 10, 2],
|
||||
[10, 7, 6],
|
||||
[7, 1, 8],
|
||||
[3, 9, 4],
|
||||
[3, 4, 2],
|
||||
[3, 2, 6],
|
||||
[3, 6, 8],
|
||||
[3, 8, 9],
|
||||
[4, 9, 5],
|
||||
[2, 4, 11],
|
||||
[6, 2, 10],
|
||||
[8, 6, 7],
|
||||
[9, 8, 1],
|
||||
];
|
||||
|
||||
for (let level = 0; level < subdivisions; level++) {
|
||||
const midpoints = new Map<string, number>();
|
||||
const midpoint = (left: number, right: number) => {
|
||||
const key = left < right ? `${left}:${right}` : `${right}:${left}`;
|
||||
const existing = midpoints.get(key);
|
||||
if (existing !== undefined) return existing;
|
||||
const vertex = normalized(
|
||||
vertices[left].map((lane, index) => lane + vertices[right][index]),
|
||||
);
|
||||
const index = vertices.push(vertex) - 1;
|
||||
midpoints.set(key, index);
|
||||
return index;
|
||||
};
|
||||
faces = faces.flatMap(([a, b, c]) => {
|
||||
const ab = midpoint(a, b);
|
||||
const bc = midpoint(b, c);
|
||||
const ca = midpoint(c, a);
|
||||
return [
|
||||
[a, ab, ca],
|
||||
[b, bc, ab],
|
||||
[c, ca, bc],
|
||||
[ab, bc, ca],
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
const normals = vertices.flat();
|
||||
const positions = normals.map((lane) => lane * radius);
|
||||
const uvs = vertices.flatMap(([x, y, z]) => [
|
||||
0.5 + Math.atan2(z, x) / (Math.PI * 2),
|
||||
0.5 - Math.asin(y) / Math.PI,
|
||||
]);
|
||||
super(scene, {
|
||||
...meshOptions,
|
||||
vertexData: { positions, normals, uvs, indices: faces.flat() },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Mesh, type MeshOptions } from "../Mesh";
|
||||
import type { Scene } from "../Scene";
|
||||
|
||||
export type PlaneOptions = Omit<MeshOptions, "geometryId" | "vertexData"> & {
|
||||
size?: number;
|
||||
width?: number;
|
||||
depth?: number;
|
||||
};
|
||||
|
||||
function dimension(value: number, name: string) {
|
||||
if (!Number.isFinite(value) || value <= 0) throw new RangeError(name);
|
||||
return value / 2;
|
||||
}
|
||||
|
||||
/** A horizontal, upward-facing plane centered on its transform. */
|
||||
export class Plane extends Mesh {
|
||||
constructor(scene: Scene, options: PlaneOptions = {}) {
|
||||
const { size = 1, width = size, depth = size, ...meshOptions } = options;
|
||||
const x = dimension(width, "width");
|
||||
const z = dimension(depth, "depth");
|
||||
super(scene, {
|
||||
...meshOptions,
|
||||
vertexData: {
|
||||
positions: [-x, 0, -z, x, 0, -z, x, 0, z, -x, 0, z],
|
||||
normals: [0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0],
|
||||
uvs: [0, 0, 1, 0, 1, 1, 0, 1],
|
||||
indices: [0, 2, 1, 0, 3, 2],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Mesh, type MeshOptions } from "../Mesh";
|
||||
import type { Scene } from "../Scene";
|
||||
|
||||
export type SphereOptions = Omit<MeshOptions, "geometryId" | "vertexData"> & {
|
||||
radius?: number;
|
||||
segments?: number;
|
||||
rings?: number;
|
||||
};
|
||||
|
||||
function count(value: number, minimum: number, name: string) {
|
||||
if (!Number.isInteger(value) || value < minimum) throw new RangeError(name);
|
||||
return value;
|
||||
}
|
||||
|
||||
/** A UV sphere with configurable horizontal segments and vertical rings. */
|
||||
export class Sphere extends Mesh {
|
||||
constructor(scene: Scene, options: SphereOptions = {}) {
|
||||
const { radius = 0.5, segments = 32, rings = 16, ...meshOptions } = options;
|
||||
if (!Number.isFinite(radius) || radius <= 0) throw new RangeError("radius");
|
||||
const columns = count(segments, 3, "segments");
|
||||
const rows = count(rings, 2, "rings");
|
||||
const positions: number[] = [];
|
||||
const normals: number[] = [];
|
||||
const uvs: number[] = [];
|
||||
const indices: number[] = [];
|
||||
|
||||
for (let ring = 0; ring <= rows; ring++) {
|
||||
const vertical = ring / rows;
|
||||
const theta = vertical * Math.PI;
|
||||
const y = Math.cos(theta);
|
||||
const scale = Math.sin(theta);
|
||||
for (let segment = 0; segment <= columns; segment++) {
|
||||
const horizontal = segment / columns;
|
||||
const phi = horizontal * Math.PI * 2;
|
||||
const x = scale * Math.cos(phi);
|
||||
const z = scale * Math.sin(phi);
|
||||
normals.push(x, y, z);
|
||||
positions.push(x * radius, y * radius, z * radius);
|
||||
uvs.push(horizontal, 1 - vertical);
|
||||
}
|
||||
}
|
||||
|
||||
for (let ring = 0; ring < rows; ring++) {
|
||||
for (let segment = 0; segment < columns; segment++) {
|
||||
const current = ring * (columns + 1) + segment;
|
||||
const next = current + columns + 1;
|
||||
indices.push(current, current + 1, next, current + 1, next + 1, next);
|
||||
}
|
||||
}
|
||||
|
||||
super(scene, {
|
||||
...meshOptions,
|
||||
vertexData: { positions, normals, uvs, indices },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./Cube";
|
||||
export * from "./Icosphere";
|
||||
export * from "./Plane";
|
||||
export * from "./Sphere";
|
||||
Reference in New Issue
Block a user