Initial commit
This commit is contained in:
64
shaders/bitonicSort.wgsl
Normal file
64
shaders/bitonicSort.wgsl
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
54
shaders/bitonicSortUInt.wgsl
Normal file
54
shaders/bitonicSortUInt.wgsl
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
shaders/bitonicSortUIntMultiPass.wgsl
Normal file
67
shaders/bitonicSortUIntMultiPass.wgsl
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
187
shaders/collisionDetection.wgsl
Normal file
187
shaders/collisionDetection.wgsl
Normal 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
14
shaders/copyBuffer.wgsl
Normal 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
49
shaders/findGridHash.wgsl
Normal 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;
|
||||
|
||||
}
|
||||
40
shaders/findGridHashRanges.wgsl
Normal file
40
shaders/findGridHashRanges.wgsl
Normal 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
69
shaders/gravity.wgsl
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
18
shaders/initiateParticles.wgsl
Normal file
18
shaders/initiateParticles.wgsl
Normal 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
88
shaders/localSort.wgsl
Normal 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];
|
||||
|
||||
}
|
||||
}
|
||||
60
shaders/particle-header.wgsl
Normal file
60
shaders/particle-header.wgsl
Normal 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
73
shaders/points.wgsl
Normal 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);
|
||||
}
|
||||
72
shaders/simpleGravity.wgsl
Normal file
72
shaders/simpleGravity.wgsl
Normal 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
|
||||
|
||||
|
||||
}
|
||||
95
shaders/triangle-list-texture-array.wgsl
Normal file
95
shaders/triangle-list-texture-array.wgsl
Normal 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);
|
||||
}
|
||||
127
shaders/triangle-list-texture-normal.wgsl
Normal file
127
shaders/triangle-list-texture-normal.wgsl
Normal 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);
|
||||
}
|
||||
93
shaders/triangle-list-texture.wgsl
Normal file
93
shaders/triangle-list-texture.wgsl
Normal 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);
|
||||
}
|
||||
78
shaders/triangle-list.wgsl
Normal file
78
shaders/triangle-list.wgsl
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user