Initial commit

This commit is contained in:
2025-11-17 15:06:39 +01:00
parent 2015516eca
commit 4d2432a1c2
91 changed files with 5074894 additions and 2 deletions

64
shaders/bitonicSort.wgsl Normal file
View File

@@ -0,0 +1,64 @@
@group(0) @binding(0)
var<storage, read_write> compare: array<f32>;
@group(0) @binding(1)
var<storage, read_write> indices: array<u32>;
@group(0) @binding(2)
var<uniform> k: u32; // current stage size (power of two)
@group(0) @binding(3)
var<uniform> j: u32; // current subsequence size
@group(0) @binding(4)
var<uniform> totalCount: u32;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let idx = global_id.x;
let ixj = idx ^ j;
if (idx >= totalCount || ixj >= totalCount) {
return;
}
if (ixj > idx && ixj < totalCount) {
let ascending = (idx & k) == 0u;
let dist_idx = compare[idx];
let dist_ixj = compare[ixj];
var swap = false;
if (ascending) {
if (dist_idx < dist_ixj) {
swap = true;
}
} else {
if (dist_idx > dist_ixj) {
swap = true;
}
}
if (swap) {
let tempDist = compare[idx];
let tempDist2 = compare[ixj];
let tempIndex = indices[idx];
let tempIndex2 = indices[ixj];
compare[idx] = tempDist2;
compare[ixj] = tempDist;
indices[idx] = tempIndex2;
indices[ixj] = tempIndex;
}
}
}

View File

@@ -0,0 +1,54 @@
@group(0) @binding(0)
var<storage, read_write> compare: array<u32>;
@group(0) @binding(1)
var<storage, read_write> indices: array<u32>;
@group(0) @binding(2)
var<uniform> k: u32;
@group(0) @binding(3)
var<uniform> j: u32;
@group(0) @binding(4)
var<uniform> totalCount: u32;
@compute @workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let idx = global_id.x;
let ixj = idx ^ j;
if (idx >= totalCount || ixj <= idx || ixj >= totalCount) {
return;
}
if (ixj > idx) {
let ascending = (idx & k) == 0u;
let dist_idx = compare[idx];
let dist_ixj = compare[ixj];
var swap = false;
if (ascending) {
if (dist_idx > dist_ixj) {
swap = true;
}
} else {
if (dist_idx < dist_ixj) {
swap = true;
}
}
if (swap) {
let tempDist = compare[idx];
let tempIndex = indices[idx];
compare[idx] = compare[ixj];
compare[ixj] = tempDist;
indices[idx] = indices[ixj];
indices[ixj] = tempIndex;
}
}
}

View File

@@ -0,0 +1,67 @@
@group(0) @binding(0)
var<storage, read_write> gridHashes: array<u32>;
@group(0) @binding(1)
var<storage, read_write> threadPassIndices: array<u32>;
@group(0) @binding(2)
var<storage, read_write> kArray: array<u32>;
@group(0) @binding(3)
var<storage, read_write> jArray: array<u32>;
@group(0) @binding(4)
var<storage, read_write> indices: array<u32>;
@group(0) @binding(5)
var<uniform> totalCount: u32;
@compute @workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
let idx = global_id.x;
let threadPassIndex = threadPassIndices[idx];
threadPassIndices[idx] = threadPassIndices[idx] + 1u;
let j = jArray[threadPassIndex];
let k = kArray[threadPassIndex];
let ixj = idx ^ j;
if (ixj <= idx || ixj >= totalCount) {
return;
}
if (ixj > idx) {
let ascending = (idx & k) == 0u;
let dist_idx = gridHashes[idx];
let dist_ixj = gridHashes[ixj];
var swap = false;
if (ascending) {
if (dist_idx > dist_ixj) {
swap = true;
}
} else {
if (dist_idx < dist_ixj) {
swap = true;
}
}
if (swap) {
let tempDist = gridHashes[idx];
let tempIndex = indices[idx];
gridHashes[idx] = gridHashes[ixj];
gridHashes[ixj] = tempDist;
indices[idx] = indices[ixj];
indices[ixj] = tempIndex;
}
}
}

View File

@@ -0,0 +1,187 @@
@group(0) @binding(0)
var<storage, read_write> positions: array<vec3<f32>>;
@group(0) @binding(1)
var<storage, read_write> velocities: array<vec3<f32>>;
@group(0) @binding(2)
var<storage, read_write> gridHashes: array<u32>;
@group(0) @binding(3)
var<storage, read_write> hashSortedIndices: array<u32>;
@group(0) @binding(4)
var<uniform> cellCount: u32;
@group(0) @binding(5)
var<uniform> gridMin: vec3<f32>;
@group(0) @binding(6)
var<storage, read> startIndices: array<u32>; // start index of particles in each cell
@group(0) @binding(7)
var<storage, read> endIndices: array<u32>; // end index (exclusive) of particles in each cell
@group(0) @binding(8)
var<uniform> collisionRadius: f32;
@group(0) @binding(9)
var<uniform> deltaTimeSeconds: f32;
@group(0) @binding(10) var<uniform> gridMax: vec3<f32>;
// particleIndex = hashSortedIndices[ startIndices[ i ] ]
fn getHash(gridCoordinate: vec3<i32>, cellCount: u32) -> u32 {
let maxIndex = i32(cellCount) - 1;
let x = max(0, min(gridCoordinate.x, maxIndex));
let y = max(0, min(gridCoordinate.y, maxIndex));
let z = max(0, min(gridCoordinate.z, maxIndex));
return u32(x + y * i32(cellCount) + z * i32(cellCount) * i32(cellCount));
}
@compute @workgroup_size(256)
fn computeMain(@builtin(global_invocation_id) globalInvocationId: vec3<u32>) {
let index = globalInvocationId.x;
let particleIndex = hashSortedIndices[ index ];
if ( particleIndex >= arrayLength(&positions) ) {
return;
}
var currentPosition = positions[ particleIndex ];
let cellSize = (gridMax - gridMin) / f32(cellCount); // 2.0 / 16 = 0.125
let relativePos = currentPosition - gridMin; // currentPosition + 1
let gridCoord = vec3<i32>(floor(relativePos / cellSize)); // relativePos divided by cellSize, then floored
let hash = getHash(gridCoord, cellCount);
var currentVelocity = velocities[particleIndex];
var push = vec3<f32>(0.0);
var count = 0u;
let collisionRadiusSquared = collisionRadius * collisionRadius;
for (var dz = -1; dz <= 1; dz = dz + 1) {
for (var dy = -1; dy <= 1; dy = dy + 1) {
for (var dx = -1; dx <= 1; dx = dx + 1) {
let neighborCell = gridCoord + vec3<i32>(dx, dy, dz);
// Compute hash of neighbor cell, with clamping to valid range inside getHash()
let neighborHash = getHash(neighborCell, cellCount);
let startIndex = startIndices[neighborHash];
let endIndex = endIndices[neighborHash];
for (var i = startIndex; i < endIndex; i = i + 1u) {
let otherIndex = hashSortedIndices[i];
if (otherIndex == particleIndex) {
continue;
}
let otherPosition = positions[otherIndex];
let offset = currentPosition - otherPosition;
let distSquared = dot(offset, offset);
if (distSquared < collisionRadiusSquared && distSquared > 0.00001) {
let distance = sqrt(distSquared);
let direction = offset / distance;
let overlap = collisionRadius - distance;
push += direction * overlap;
count += 1u;
}
}
}
}
}
if ( count > 0u ) {
let averagePush = push / f32(count);
currentPosition += averagePush * .9;
currentVelocity += averagePush * 3.0;
let pushDir = normalize(averagePush);
// Project current velocity onto push direction
let velAlongPush = dot(currentVelocity, pushDir);
// Damping factor (energy loss on collision)
let dampingFactor = 0.25;
// Reduce velocity along push direction
let velAlongPushDamped = velAlongPush * dampingFactor;
// Velocity perpendicular to push direction remains unchanged
let velPerp = currentVelocity - velAlongPush * pushDir;
// Combine damped velocity components
currentVelocity = velPerp + velAlongPushDamped * pushDir;
}
let deltaTimeClamped = min( deltaTimeSeconds, 0.01 );
let gridExtent = vec3<f32>(f32(cellCount)) * cellSize;
//let gridMax = gridMin + gridExtent;
// Enforce hardcoded bounding box from -1 to +1 on all axes
if (currentPosition.x < gridMin.x) {
currentPosition.x = gridMin.x;
currentVelocity.x = abs(currentVelocity.x) * 0.2;
} else if (currentPosition.x > gridMax.x) {
currentPosition.x = gridMax.x;
currentVelocity.x = -abs(currentVelocity.x) * 0.2;
}
if (currentPosition.y < gridMin.y) {
currentPosition.y = gridMin.y;
currentVelocity.y = abs(currentVelocity.y) * 0.2;
} else if (currentPosition.y > gridMax.y) {
currentPosition.y = gridMax.y;
currentVelocity.y = -abs(currentVelocity.y) * 0.2;
}
if (currentPosition.z < gridMin.z) {
currentPosition.z = gridMin.z;
currentVelocity.z = abs(currentVelocity.z) * 0.2;
} else if (currentPosition.z > gridMax.z) {
currentPosition.z = gridMax.z;
currentVelocity.z = -abs(currentVelocity.z) * 0.2;
}
if (currentPosition.y < -1.0) {
currentPosition.y = -1.0;
currentVelocity.y *= -0.2;
}
currentPosition += currentVelocity * deltaTimeClamped;
positions[ particleIndex ] = currentPosition;
velocities[ particleIndex ] = currentVelocity;
}

14
shaders/copyBuffer.wgsl Normal file
View File

@@ -0,0 +1,14 @@
@group(0) @binding(0)
var<storage, read> indices: array<u32>;
@group(0) @binding(1)
var<storage, read_write> sortedIndices: array<u32>;
@compute @workgroup_size(64)
fn computeMain(@builtin(global_invocation_id) globalInvocationId: vec3<u32>) {
let particleIndex = globalInvocationId.x;
sortedIndices[particleIndex] = indices[particleIndex];
}

49
shaders/findGridHash.wgsl Normal file
View File

@@ -0,0 +1,49 @@
@group(0) @binding(0) var<storage, read_write> positions: array<vec3<f32>>;
@group(0) @binding(1) var<storage, read_write> gridHashes: array<u32>;
@group(0) @binding(2) var<storage, read_write> indices: array<u32>;
@group(0) @binding(3) var<uniform> cellCount: u32;
@group(0) @binding(4) var<uniform> gridMin: vec3<f32>;
@group(0) @binding(5) var<uniform> gridMax: vec3<f32>;
fn getHash(gridCoordinate: vec3<i32>, cellCount: u32) -> u32 {
let maxIndex = i32(cellCount) - 1;
let x = max(0, min(gridCoordinate.x, maxIndex));
let y = max(0, min(gridCoordinate.y, maxIndex));
let z = max(0, min(gridCoordinate.z, maxIndex));
return u32(x + y * i32(cellCount) + z * i32(cellCount) * i32(cellCount));
}
@compute @workgroup_size(256)
fn computeMain(@builtin(global_invocation_id) globalInvocationId: vec3<u32>) {
let particleIndex = globalInvocationId.x;
if ( particleIndex >= arrayLength(&positions) ) {
return;
}
var currentPosition = positions[particleIndex];
let cellSize = (gridMax - gridMin) / f32(cellCount); // 2.0 / 16 = 0.125
let relativePos = currentPosition - gridMin; // currentPosition + 1
let gridCoord = vec3<i32>(floor(relativePos / cellSize)); // relativePos divided by cellSize, then floored
let hash = getHash(gridCoord, cellCount);
gridHashes[ particleIndex ] = hash;
indices[ particleIndex ] = particleIndex;
}

View File

@@ -0,0 +1,40 @@
@group(0) @binding(0)
var<storage, read> gridHashes: array<u32>;
@group(0) @binding(1)
var<storage, read> indices: array<u32>;
@group(0) @binding(2)
var<storage, read_write> startIndices: array<u32>;
@group(0) @binding(3)
var<storage, read_write> endIndices: array<u32>;
@group(0) @binding(4)
var<uniform> totalCount: u32;
@compute @workgroup_size(256)
fn findStartEndIndices(@builtin(global_invocation_id) globalId: vec3<u32>) {
let i = globalId.x;
if (i >= totalCount) {
return;
}
let currentHash = gridHashes[i];
if (i == 0u || gridHashes[i - 1u] != currentHash) {
startIndices[currentHash] = i;
}
if (i == totalCount - 1u || gridHashes[i + 1u] != currentHash) {
endIndices[currentHash] = i;
}
}

69
shaders/gravity.wgsl Normal file
View File

@@ -0,0 +1,69 @@
@group(0) @binding(0)
var<storage, read_write> positions: array<vec3<f32>>;
@group(0) @binding(1)
var<storage, read_write> velocities: array<vec3<f32>>;
@group(0) @binding(2)
var<storage, read_write> distances: array<f32>;
@group(0) @binding(3)
var<storage, read_write> indices: array<u32>;
@group(0) @binding(4)
var<uniform> deltaTimeSeconds: f32;
@group(0) @binding(5)
var<uniform> cameraPosition: vec3<f32>;
@group(0) @binding(6)
var<uniform> updateDistancesAndIndices: u32;
@group(0) @binding(7)
var<uniform> cellCount: u32;
@group(0) @binding(8)
var<uniform> gravity: f32;
@compute @workgroup_size(64)
fn computeMain(@builtin(global_invocation_id) globalInvocationId: vec3<u32>) {
let particleIndex = globalInvocationId.x;
if (particleIndex >= arrayLength(&positions)) {
return;
}
let gravityAcceleration = vec3<f32>(0.0, gravity, 0.0);
var currentPosition = positions[particleIndex];
var currentVelocity = velocities[particleIndex];
let deltaTimeClamped = min(deltaTimeSeconds, 0.01);
currentVelocity += gravityAcceleration * deltaTimeClamped;
currentPosition += currentVelocity * deltaTimeClamped;
let friction = 0.98;
currentVelocity *= friction;
positions[particleIndex] = currentPosition;
velocities[particleIndex] = currentVelocity;
if ( updateDistancesAndIndices == 1u ) {
let diff = currentPosition - cameraPosition;
let dist = length(diff);
distances[ particleIndex ] = dist;
indices[ particleIndex ] = particleIndex;
positions[ particleIndex ] = currentPosition;
}
}

View File

@@ -0,0 +1,18 @@
@group(0) @binding(0)
var<storage, read> initiationPositions : array<vec4<f32>>;
@group(0) @binding(1)
var<storage, read_write> positions : array<vec4<f32>>;
@group(0) @binding(2)
var<storage, read_write> velocities : array<vec2<f32>>;
@compute @workgroup_size(64)
fn initialize(@builtin(global_invocation_id) id : vec3<u32>) {
let i = id.x;
positions[i] = initiationPositions[i];
velocities[i] = vec2<f32>(0.0, 0.0);
}

88
shaders/localSort.wgsl Normal file
View File

@@ -0,0 +1,88 @@
@group(0) @binding(0)
var<storage, read_write> gridHashes: array<u32>;
@group(0) @binding(1)
var<storage, read_write> indices: array<u32>;
@group(0) @binding(2)
var<uniform> totalCount: u32;
var<workgroup> sharedData: array<u32, 256>;
var<workgroup> sharedIndices: array<u32, 256>;
@compute @workgroup_size(256)
fn main(@builtin(local_invocation_id) local_id : vec3<u32>,
@builtin(global_invocation_id) global_id : vec3<u32>) {
let localIndex = local_id.x;
let globalIndex = global_id.x;
// Load element from global memory into shared memory if in range
if (globalIndex < totalCount) {
sharedData[localIndex] = gridHashes[globalIndex];
sharedIndices[localIndex] = indices[globalIndex];
} else {
sharedData[localIndex] = 0xffffffffu; // Max uint to push invalid values to the end
sharedIndices[localIndex] = 0xffffffffu; // or some invalid index
}
workgroupBarrier();
// Bitonic sort in shared memory on 256 elements
var size = 2u;
while (size <= 256u) {
var stride = size >> 1u;
var j = stride;
while (j > 0u) {
let ixj = localIndex ^ j;
if (ixj > localIndex) {
let ascending = ((localIndex & size) == 0u);
let valLocal = sharedData[localIndex];
let valIxj = sharedData[ixj];
var swap = false;
if ( ascending ) {
if ( valLocal > valIxj ) {
swap = true;
}
} else {
if (valLocal < valIxj) {
swap = true;
}
}
if (swap) {
// Swap values
sharedData[localIndex] = valIxj;
sharedData[ixj] = valLocal;
// Swap indices as well
let idxLocal = sharedIndices[localIndex];
let idxIxj = sharedIndices[ixj];
sharedIndices[localIndex] = idxIxj;
sharedIndices[ixj] = idxLocal;
}
}
workgroupBarrier();
j = j >> 1u;
}
size = size << 1u;
}
// Write sorted results back to global memory
if (globalIndex < totalCount) {
gridHashes[globalIndex] = sharedData[localIndex];
indices[globalIndex] = sharedIndices[localIndex];
}
}

View File

@@ -0,0 +1,60 @@
@group(0) @binding(0)
var<storage, read> positions : array<vec4<f32>>;
@group(0) @binding(1)
var<storage, read> colors : array<vec4<f32>>;
@group(0) @binding(2)
var<uniform> viewProjectionMatrix : mat4x4<f32>;
@group(0) @binding(3)
var<uniform> aspectRatio : f32;
@group(0) @binding(4)
var<uniform> mousePos : vec2<f32>;
@group(0) @binding(5)
var<uniform> hoverRadius : f32;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) color : vec4<f32>,
};
fn smoothStep(edge0: f32, edge1: f32, x: f32) -> f32 {
let t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}
@vertex
fn vertex_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
var output : VertexOutput;
let pos = positions[vertexIndex];
var color = colors[vertexIndex];
let correctedPosition = vec4<f32>(
pos.x * aspectRatio,
pos.y,
pos.z,
pos.w
);
// Change color if near mousePos:
// mousePos is passed as uniform vec2<f32> in clip space (-aspectRatio..aspectRatio, -1..1)
let dist = distance(vec2<f32>(pos.x* aspectRatio, pos.y), mousePos);
if (dist < hoverRadius) {
color = vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red highlight
}
output.Position = viewProjectionMatrix * correctedPosition;
output.color = color;
return output;
}
@fragment
fn fragment_main(input : VertexOutput) -> @location(0) vec4<f32> {
return input.color;
}

73
shaders/points.wgsl Normal file
View File

@@ -0,0 +1,73 @@
struct Point {
pos: vec3<f32>,
_pad: f32,
};
struct BillboardAxis {
vector : vec3<f32>,
_pad : f32,
};
struct VSOut {
@builtin(position) Position : vec4<f32>,
@location(0) uv : vec2<f32>,
@location(1) color : vec3<f32>,
};
@group(0) @binding(0) var<storage, read> positions: array<Point>;
@group(0) @binding(1) var<storage, read> sortedIndices: array<u32>; // New binding for sorted indices
@group(0) @binding(2) var<uniform> viewProjectionMatrix: mat4x4<f32>;
@group(0) @binding(3) var<uniform> cameraRight : BillboardAxis;
@group(0) @binding(4) var<uniform> cameraUp : BillboardAxis;
@vertex
fn vertexEntryPoint(
@builtin(vertex_index) vertexIndex: u32,
@builtin(instance_index) instanceIndex: u32,
@location(0) quadOffset: vec2<f32>
) -> VSOut {
var output: VSOut;
// Use the sorted index to get the actual particle index
let actualIndex = sortedIndices[instanceIndex];
let point = positions[actualIndex];
let center = point.pos;
let radius = 0.03;
let rightOffset = cameraRight.vector * quadOffset.x * radius;
let upOffset = cameraUp.vector * quadOffset.y * radius;
let worldPos = vec4<f32>(center + rightOffset + upOffset, 1.0);
output.Position = viewProjectionMatrix * worldPos;
output.uv = quadOffset;
output.color = (center + vec3<f32>(1.0, 1.0, 1.0)) * 0.5;
return output;
}
@fragment
fn fragmentEntryPoint(
@location(0) uv: vec2<f32>,
@location(1) color: vec3<f32>
) -> @location(0) vec4<f32> {
let dist = length(uv);
if (dist > 1.0) {
discard;
}
let z = sqrt(1.0 - dist * dist);
let normal = normalize(vec3<f32>(uv.x, uv.y, z));
let light = normalize(vec3<f32>(1.0, 1.0, 1.0));
let diffuse = max(dot(normal, light), 0.0);
return vec4<f32>(color * diffuse, 1.0);
}

View File

@@ -0,0 +1,72 @@
@group(0) @binding(0)
var<storage, read_write> positions : array<vec4<f32>>;
@group(0) @binding(1)
var<storage, read_write> velocities : array<vec4<f32>>;
@group(0) @binding(2)
var<uniform> deltaTime : f32;
@group(0) @binding(3)
var<uniform> aspectRatio : f32;
@group(0) @binding(4)
var<uniform> mousePos : vec2<f32>;
@group(0) @binding(5)
var<uniform> hoverRadius : f32;
@group(0) @binding(6)
var<storage, read_write> states : array<u32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id : vec3<u32>) {
let index = id.x;
let gravity = vec3<f32>(0.0, -29.61, 0.0);
var pos = positions[index];
var vel = velocities[index];
// Apply gravity to velocity
// Integrate position
let dist = distance(vec2<f32>(pos.x*aspectRatio, pos.y), mousePos);
var newPos = pos.xyz;
if (dist < hoverRadius) {
states[index] = 1;
}
if ( states[index] == 1 ) {
let newVel = vel.xyz + gravity * deltaTime;
newPos = pos.xyz + newVel * 0.001;
velocities[index] = vec4<f32>(newVel, vel.w);
positions[index] = vec4<f32>(newPos, pos.w);
} else {
let newVel = vel.xyz;
newPos = pos.xyz + newVel * 0.001;
velocities[index] = vec4<f32>(newVel, vel.w);
positions[index] = vec4<f32>(newPos, pos.w);
}
// Store updated values
}

View File

@@ -0,0 +1,95 @@
@group(0) @binding(0)
var<uniform> viewProjectionMatrix : mat4x4<f32>;
@group(0) @binding(1)
var<storage, read> instancePositions : array<vec4<f32>>;
@group(0) @binding(2)
var<uniform> cameraPosition : vec3<f32>;
@group(0) @binding(3)
var myTextureArray: texture_2d_array<f32>;
@group(0) @binding(4)
var mySampler : sampler;
struct VertexOutput {
@builtin(position) position : vec4<f32>,
@location(0) worldPosition : vec3<f32>,
@location(1) worldNormal : vec3<f32>,
@location(2) uv : vec2<f32>,
};
@vertex
fn vertexEntryPoint(
@location(0) position : vec3<f32>,
@location(1) normal : vec3<f32>,
@location(2) uv : vec2<f32>,
@builtin(instance_index) instanceIndex : u32
) -> VertexOutput {
var output : VertexOutput;
let instanceOffset = instancePositions[instanceIndex].xyz;
let worldPosition = position + instanceOffset;
output.worldPosition = worldPosition;
output.worldNormal = normalize(normal);
output.position = viewProjectionMatrix * vec4<f32>(worldPosition, 1.0);
output.uv = uv;
return output;
}
@fragment
fn fragmentEntryPoint(
@location(0) worldPosition : vec3<f32>,
@location(1) worldNormal : vec3<f32>,
@location(2) uv : vec2<f32>
) -> @location(0) vec4<f32> {
// For test: encode UV as color (no texture sampling)
//let baseColor = vec3<f32>(uv, 0.0);
let baseColor = textureSampleLevel(myTextureArray, mySampler, uv, 0, 0).rgb;
let pi = 3.14159265;
let invPi = 0.318309886;
let N = normalize(worldNormal);
let V = normalize(cameraPosition - worldPosition);
let L = normalize(vec3<f32>(0.5, 1.0, 0.3));
let H = normalize(V + L);
let metallic = 0.2;
let roughness = 0.4;
let rough2 = roughness * roughness;
let lightColor = vec3<f32>(1.0);
let NdotV = max(dot(N, V), 0.001);
let NdotL = max(dot(N, L), 0.001);
let NdotH = max(dot(N, H), 0.001);
let HdotV = max(dot(H, V), 0.001);
let F0 = mix(vec3<f32>(0.04), baseColor, metallic);
let f = pow(1.0 - HdotV, 5.0);
let F = F0 + (1.0 - F0) * f;
let a2 = rough2 * rough2;
let NdotH2 = NdotH * NdotH;
let denom = NdotH2 * (a2 - 1.0) + 1.0;
let NDF = a2 / (pi * denom * denom);
let k = (roughness + 1.0);
let k2 = (k * k) / 8.0;
let Gv = NdotV / (NdotV * (1.0 - k2) + k2);
let Gl = NdotL / (NdotL * (1.0 - k2) + k2);
let G = Gv * Gl;
let spec = (NDF * G * F) / (4.0 * NdotV * NdotL + 0.001);
let kd = (vec3<f32>(1.0) - F) * (1.0 - metallic);
let diff = kd * baseColor * invPi;
let color = (diff + spec) * lightColor * NdotL + vec3<f32>(0.03) * baseColor;
return vec4<f32>(pow(color, vec3<f32>(1.0 / 2.2)), 1.0);
}

View File

@@ -0,0 +1,127 @@
@group(0) @binding(0)
var<uniform> viewProjectionMatrix : mat4x4<f32>;
@group(0) @binding(1)
var<storage, read> instancePositions : array<vec4<f32>>;
@group(0) @binding(2)
var<uniform> cameraPosition : vec3<f32>;
@group(0) @binding(3)
var myTexture : texture_2d<f32>;
@group(0) @binding(4)
var mySampler : sampler;
@group(0) @binding(5)
var normalMapTexture : texture_2d<f32>;
struct VertexOutput {
@builtin(position) position : vec4<f32>,
@location(0) worldPosition : vec3<f32>,
@location(1) worldNormal : vec3<f32>,
@location(2) worldBitangent : vec3<f32>,
@location(3) uv : vec2<f32>,
@location(4) meshIndex : f32
};
@vertex
fn vertexEntryPoint(
@location(0) position : vec3<f32>,
@location(1) normal : vec3<f32>,
@location(2) bitangent : vec3<f32>,
@location(3) uv : vec2<f32>,
@builtin(instance_index) instanceIndex : u32
) -> VertexOutput {
var output : VertexOutput;
let instanceOffset = instancePositions[instanceIndex].xyz;
let worldPosition = position + instanceOffset;
output.worldPosition = worldPosition;
output.worldNormal = normalize(normal);
output.worldBitangent = normalize(bitangent);
output.position = viewProjectionMatrix * vec4<f32>(worldPosition, 1.0);
output.uv = uv;
output.meshIndex = f32( instanceIndex );
return output;
}
@fragment
fn fragmentEntryPoint(
@location(0) worldPosition : vec3<f32>,
@location(1) worldNormal : vec3<f32>,
@location(2) worldBitangent : vec3<f32>,
@location(3) uv : vec2<f32>,
@location(4) meshIndex : f32
) -> @location(0) vec4<f32> {
// For test: encode UV as color (no texture sampling)
//let baseColor = vec3<f32>(uv, 0.0);
let baseColor = textureSample(myTexture, mySampler, uv).rgb;
// Sample normal map and decode
let normalMapSample = textureSample(normalMapTexture, mySampler, uv).rgb;
let tangentSpaceNormal = normalMapSample * 2.0 - 1.0; // Convert [0,1] to [-1,1]
// Construct TBN matrix
let n = normalize(worldNormal);
let B = normalize(worldBitangent);
let T = normalize(cross(B, n));
let TBN = mat3x3<f32>(T, B, n);
// Transform normal to world space
let mappedNormal = normalize(TBN * tangentSpaceNormal);
let pi = 3.14159265;
let invPi = 0.318309886;
let N = normalize(mappedNormal);
let V = normalize(cameraPosition - worldPosition);
let L = normalize(vec3<f32>(0.5, 1.0, 0.3));
let H = normalize(V + L);
let roughnessIndex = meshIndex / 30.0 % 1.0;
let metallic = 0.2;
let roughness = roughnessIndex;
let rough2 = roughness * roughness;
let lightColor = vec3<f32>(1.0);
let NdotV = max(dot(N, V), 0.001);
let NdotL = max(dot(N, L), 0.001);
let NdotH = max(dot(N, H), 0.001);
let HdotV = max(dot(H, V), 0.001);
let F0 = mix(vec3<f32>(0.04), baseColor, metallic);
let f = pow(1.0 - HdotV, 5.0);
let F = F0 + (1.0 - F0) * f;
let a2 = rough2 * rough2;
let NdotH2 = NdotH * NdotH;
let denom = NdotH2 * (a2 - 1.0) + 1.0;
let NDF = a2 / (pi * denom * denom);
let k = (roughness + 1.0);
let k2 = (k * k) / 8.0;
let Gv = NdotV / (NdotV * (1.0 - k2) + k2);
let Gl = NdotL / (NdotL * (1.0 - k2) + k2);
let G = Gv * Gl;
let spec = (NDF * G * F) / (4.0 * NdotV * NdotL + 0.001);
let kd = (vec3<f32>(1.0) - F) * (1.0 - metallic);
let diff = kd * baseColor * invPi;
let color = (diff + spec) * lightColor * NdotL + vec3<f32>(0.03) * baseColor;
return vec4<f32>(pow(color, vec3<f32>(1.0 / 2.2)), 1.0);
}

View File

@@ -0,0 +1,93 @@
@group(0) @binding(0)
var<uniform> viewProjectionMatrix : mat4x4<f32>;
@group(0) @binding(1)
var<storage, read> instancePositions : array<vec4<f32>>;
@group(0) @binding(2)
var<uniform> cameraPosition : vec3<f32>;
@group(0) @binding(3)
var myTexture : texture_2d<f32>;
@group(0) @binding(4)
var mySampler : sampler;
struct VertexOutput {
@builtin(position) position : vec4<f32>,
@location(0) worldPosition : vec3<f32>,
@location(1) worldNormal : vec3<f32>,
@location(2) uv : vec2<f32>,
};
@vertex
fn vertexEntryPoint(
@location(0) position : vec3<f32>,
@location(1) normal : vec3<f32>,
@location(2) uv : vec2<f32>,
@builtin(instance_index) instanceIndex : u32
) -> VertexOutput {
var output : VertexOutput;
let instanceOffset = instancePositions[instanceIndex].xyz;
let worldPosition = position + instanceOffset;
output.worldPosition = worldPosition;
output.worldNormal = normalize(normal);
output.position = viewProjectionMatrix * vec4<f32>(worldPosition, 1.0);
output.uv = uv;
return output;
}
@fragment
fn fragmentEntryPoint(
@location(0) worldPosition : vec3<f32>,
@location(1) worldNormal : vec3<f32>,
@location(2) uv : vec2<f32>
) -> @location(0) vec4<f32> {
// For test: encode UV as color (no texture sampling)
//let baseColor = vec3<f32>(uv, 0.0);
let baseColor = textureSample(myTexture, mySampler, uv).rgb;
let pi = 3.14159265;
let invPi = 0.318309886;
let N = normalize(worldNormal);
let V = normalize(cameraPosition - worldPosition);
let L = normalize(vec3<f32>(0.5, 1.0, 0.3));
let H = normalize(V + L);
let metallic = 0.2;
let roughness = 0.4;
let rough2 = roughness * roughness;
let lightColor = vec3<f32>(1.0);
let NdotV = max(dot(N, V), 0.001);
let NdotL = max(dot(N, L), 0.001);
let NdotH = max(dot(N, H), 0.001);
let HdotV = max(dot(H, V), 0.001);
let F0 = mix(vec3<f32>(0.04), baseColor, metallic);
let f = pow(1.0 - HdotV, 5.0);
let F = F0 + (1.0 - F0) * f;
let a2 = rough2 * rough2;
let NdotH2 = NdotH * NdotH;
let denom = NdotH2 * (a2 - 1.0) + 1.0;
let NDF = a2 / (pi * denom * denom);
let k = (roughness + 1.0);
let k2 = (k * k) / 8.0;
let Gv = NdotV / (NdotV * (1.0 - k2) + k2);
let Gl = NdotL / (NdotL * (1.0 - k2) + k2);
let G = Gv * Gl;
let spec = (NDF * G * F) / (4.0 * NdotV * NdotL + 0.001);
let kd = (vec3<f32>(1.0) - F) * (1.0 - metallic);
let diff = kd * baseColor * invPi;
let color = (diff + spec) * lightColor * NdotL + vec3<f32>(0.03) * baseColor;
return vec4<f32>(pow(color, vec3<f32>(1.0 / 2.2)), 1.0);
}

View File

@@ -0,0 +1,78 @@
@group(0) @binding(0)
var<uniform> viewProjectionMatrix : mat4x4<f32>;
@group(0) @binding(1)
var<storage, read> instancePositions : array<vec4<f32>>;
@group(0) @binding(2)
var<uniform> cameraPosition : vec3<f32>;
struct VertexOutput {
@builtin(position) position : vec4<f32>,
@location(0) worldPosition : vec3<f32>,
@location(1) worldNormal : vec3<f32>,
};
@vertex
fn vertexEntryPoint(
@location(0) position : vec3<f32>,
@location(1) normal : vec3<f32>,
@builtin(instance_index) instanceIndex : u32
) -> VertexOutput {
var output : VertexOutput;
let instanceOffset = instancePositions[instanceIndex].xyz;
let worldPosition = position + instanceOffset;
output.worldPosition = worldPosition;
output.worldNormal = normalize(normal);
output.position = viewProjectionMatrix * vec4<f32>(worldPosition, 1.0);
return output;
}
@fragment
fn fragmentEntryPoint(
@location(0) worldPosition : vec3<f32>,
@location(1) worldNormal : vec3<f32>
) -> @location(0) vec4<f32> {
let pi = 3.14159265;
let invPi = 0.318309886;
let N = normalize(worldNormal);
let V = normalize(cameraPosition - worldPosition);
let L = normalize(vec3<f32>(0.5, 1.0, 0.3));
let H = normalize(V + L);
let baseColor = vec3<f32>(1.0);
let metallic = 0.2;
let roughness = 0.4;
let rough2 = roughness * roughness;
let lightColor = vec3<f32>(1.0);
let NdotV = max(dot(N, V), 0.001);
let NdotL = max(dot(N, L), 0.001);
let NdotH = max(dot(N, H), 0.001);
let HdotV = max(dot(H, V), 0.001);
let F0 = mix(vec3<f32>(0.04), baseColor, metallic);
let f = pow(1.0 - HdotV, 5.0);
let F = F0 + (1.0 - F0) * f;
let a2 = rough2 * rough2;
let NdotH2 = NdotH * NdotH;
let denom = NdotH2 * (a2 - 1.0) + 1.0;
let NDF = a2 / (pi * denom * denom);
let k = (roughness + 1.0);
let k2 = (k * k) / 8.0;
let Gv = NdotV / (NdotV * (1.0 - k2) + k2);
let Gl = NdotL / (NdotL * (1.0 - k2) + k2);
let G = Gv * Gl;
let spec = (NDF * G * F) / (4.0 * NdotV * NdotL + 0.001);
let kd = (vec3<f32>(1.0) - F) * (1.0 - metallic);
let diff = kd * baseColor * invPi;
let color = (diff + spec) * lightColor * NdotL + vec3<f32>(0.03) * baseColor;
return vec4<f32>(pow(color, vec3<f32>(1.0 / 2.2)), 1.0);
}