Merged the vertex and fragment shader WGSL files, because their bindings can't collide anyhow. The rain render shader now accepts a bind group of time uniforms, which change on every frame, as well as MSDF uniforms, along with an MSDF sampler and texture that's loaded in from the PNG. The shader currently renders the correct grid of quads for volumetric mode, displays the first glyph raw in each one, and every sixty frames, turns on and off the blue channel.

This commit is contained in:
Rezmason
2021-10-27 18:16:07 -07:00
parent 8c62146884
commit c716d30808
4 changed files with 166 additions and 63 deletions

View File

@@ -3,6 +3,32 @@ const getCanvasSize = (canvas) => {
return [canvas.clientWidth * devicePixelRatio, canvas.clientHeight * devicePixelRatio]; return [canvas.clientWidth * devicePixelRatio, canvas.clientHeight * devicePixelRatio];
}; };
const loadTexture = async (device, url) => {
const image = new Image();
image.crossOrigin = "anonymous";
image.src = url;
await image.decode();
const imageBitmap = await createImageBitmap(image);
const texture = device.createTexture({
size: [imageBitmap.width, imageBitmap.height, 1],
format: "rgba8unorm",
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, // Which of these are necessary?
});
device.queue.copyExternalImageToTexture(
{
source: imageBitmap,
},
{
texture: texture,
},
[imageBitmap.width, imageBitmap.height]
);
return texture;
};
export default async (canvas, config) => { export default async (canvas, config) => {
console.log(config); console.log(config);
@@ -37,9 +63,11 @@ export default async (canvas, config) => {
], ],
}; };
// TODO: create buffers, uniforms, textures, samplers const sampler = device.createSampler();
const uniformBufferSize = 4 * (1 + 1); const msdfTexture = await loadTexture(device, config.glyphTexURL);
const uniformBufferSize = 4 * (1 * 1 + 1 * 1);
const uniformBuffer = device.createBuffer({ const uniformBuffer = device.createBuffer({
size: uniformBufferSize, size: uniformBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, // Which of these are necessary? usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, // Which of these are necessary?
@@ -48,9 +76,24 @@ export default async (canvas, config) => {
new Int32Array(uniformBuffer.getMappedRange()).set([numColumns, numRows]); new Int32Array(uniformBuffer.getMappedRange()).set([numColumns, numRows]);
uniformBuffer.unmap(); uniformBuffer.unmap();
// TODO: create pipelines, bind groups, shaders const msdfUniformBufferSize = 4 * (1 * 1);
const msdfUniformBuffer = device.createBuffer({
size: msdfUniformBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.FRAGMENT | GPUBufferUsage.COPY_DST, // Which of these are necessary?
mappedAtCreation: true,
});
new Int32Array(msdfUniformBuffer.getMappedRange()).set([config.glyphTextureColumns]);
msdfUniformBuffer.unmap();
const [vert, frag] = await Promise.all(["shaders/rainPass.vert.wgsl", "shaders/rainPass.frag.wgsl"].map(async (path) => (await fetch(path)).text())); const timeBufferSize = 4 * (1 * 1 + 1 * 1);
const timeBuffer = device.createBuffer({
size: uniformBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.VERTEX | GPUBufferUsage.FRAGMENT | GPUBufferUsage.COMPUTE | GPUBufferUsage.COPY_DST, // Which of these are necessary?
});
const [rainRenderShader] = await Promise.all(["shaders/rainRenderPass.wgsl"].map(async (path) => (await fetch(path)).text()));
const rainRenderShaderModule = device.createShaderModule({ code: rainRenderShader });
const additiveBlendComponent = { const additiveBlendComponent = {
operation: "add", operation: "add",
@@ -65,16 +108,12 @@ export default async (canvas, config) => {
const rainRenderPipeline = device.createRenderPipeline({ const rainRenderPipeline = device.createRenderPipeline({
vertex: { vertex: {
module: device.createShaderModule({ module: rainRenderShaderModule,
code: vert, entryPoint: "vertMain",
}),
entryPoint: "main",
}, },
fragment: { fragment: {
module: device.createShaderModule({ module: rainRenderShaderModule,
code: frag, entryPoint: "fragMain",
}),
entryPoint: "main",
targets: [ targets: [
{ {
format: presentationFormat, format: presentationFormat,
@@ -85,7 +124,7 @@ export default async (canvas, config) => {
primitive: { primitive: {
// What happens if this isn't here? // What happens if this isn't here?
topology: "triangle-list", // What happens if this isn't here? topology: "triangle-list", // What happens if this isn't here?
cullMode: "none", // What happens if this isn't here? cullMode: "back", // What happens if this isn't here?
}, },
}); });
@@ -101,18 +140,55 @@ export default async (canvas, config) => {
], ],
}); });
const msdfBindGroup = device.createBindGroup({
layout: rainRenderPipeline.getBindGroupLayout(1),
entries: [
{
binding: 0,
resource: {
buffer: msdfUniformBuffer,
},
},
{
binding: 1,
resource: sampler,
},
{
binding: 2,
resource: msdfTexture.createView(),
},
],
});
const timeBindGroup = device.createBindGroup({
layout: rainRenderPipeline.getBindGroupLayout(2),
entries: [
{
binding: 0,
resource: {
buffer: timeBuffer,
},
},
],
});
const rainRenderPipelineBindGroups = [uniformBindGroup, msdfBindGroup, timeBindGroup];
const bundleEncoder = device.createRenderBundleEncoder({ const bundleEncoder = device.createRenderBundleEncoder({
colorFormats: [presentationFormat], colorFormats: [presentationFormat],
}); });
bundleEncoder.setPipeline(rainRenderPipeline); bundleEncoder.setPipeline(rainRenderPipeline);
bundleEncoder.setBindGroup(0, uniformBindGroup); rainRenderPipelineBindGroups.forEach((bindGroup, index) => {
bundleEncoder.draw(6 * numColumns * numRows, 1, 0, 0); bundleEncoder.setBindGroup(index, bindGroup);
});
const numQuads = numColumns * numRows;
bundleEncoder.draw(6 * numQuads, 1, 0, 0);
const renderBundles = [bundleEncoder.finish()]; const renderBundles = [bundleEncoder.finish()];
// queue.writeBuffer(uniformBuffer, 0, new Int32Array([numColumns, numRows])); let frame = 0;
const frame = (now) => { const renderLoop = (now) => {
const canvasSize = getCanvasSize(canvas); const canvasSize = getCanvasSize(canvas);
if (canvasSize[0] !== canvasConfig.size[0] || canvasSize[1] !== canvasConfig.size[1]) { if (canvasSize[0] !== canvasConfig.size[0] || canvasSize[1] !== canvasConfig.size[1]) {
canvasConfig.size = canvasSize; canvasConfig.size = canvasSize;
@@ -123,9 +199,9 @@ export default async (canvas, config) => {
// TODO: update camera matrix, screen size, write to queue // TODO: update camera matrix, screen size, write to queue
} }
// TODO: update the uniforms that change, write to queue queue.writeBuffer(timeBuffer, 0, new Int32Array([now, frame]));
frame++;
renderPassConfig.colorAttachments[0].loadValue.r = Math.sin((now / 1000) * 2) / 2 + 0.5;
renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView(); renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView();
const encoder = device.createCommandEncoder(); const encoder = device.createCommandEncoder();
@@ -135,8 +211,8 @@ export default async (canvas, config) => {
const commandBuffer = encoder.finish(); const commandBuffer = encoder.finish();
queue.submit([commandBuffer]); queue.submit([commandBuffer]);
requestAnimationFrame(frame); requestAnimationFrame(renderLoop);
}; };
requestAnimationFrame(frame); requestAnimationFrame(renderLoop);
}; };

View File

@@ -1,3 +0,0 @@
[[stage(fragment)]] fn main([[location(0)]] UV : vec2<f32>) -> [[location(0)]] vec4<f32> {
return vec4<f32>(0.0, UV, 1.0);
}

View File

@@ -1,39 +0,0 @@
[[block]] struct Uniforms {
numColumns: i32;
numRows: i32;
};
[[binding(0), group(0)]] var<uniform> uniforms : Uniforms;
struct VertexOutput {
[[builtin(position)]] Position : vec4<f32>;
[[location(0)]] UV : vec2<f32>;
};
[[stage(vertex)]] fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOutput {
var i = i32(VertexIndex);
var quadIndex = i / 6;
var cornerPosition = vec2<f32>(
f32(i % 2),
f32(((i + 1) % 6 / 3))
);
var x = uniforms.numColumns;
var position = cornerPosition;
position = position + vec2<f32>(
f32(quadIndex % uniforms.numColumns),
f32(quadIndex / uniforms.numColumns)
);
position = position / vec2<f32>(
f32(uniforms.numColumns),
f32(uniforms.numRows)
);
position = position * 2.0 - 1.0;
return VertexOutput(
vec4<f32>(position, 1.0, 1.0),
cornerPosition
);
}

View File

@@ -0,0 +1,69 @@
let PI:f32 = 3.14159265359;
let TWO_PI:f32 = 6.28318530718;
[[block]] struct Uniforms {
numColumns: i32;
numRows: i32;
};
[[group(0), binding(0)]] var<uniform> uniforms:Uniforms;
[[block]] struct MSDFUniforms {
numColumns: i32;
};
[[group(1), binding(0)]] var<uniform> msdfUniforms:MSDFUniforms;
[[group(1), binding(1)]] var msdfSampler: sampler;
[[group(1), binding(2)]] var msdfTexture: texture_2d<f32>;
[[block]] struct TimeUniforms {
time: i32;
frame: i32;
};
[[group(2), binding(0)]] var<uniform> timeUniforms:TimeUniforms;
// Vertex shader
struct VertexOutput {
[[builtin(position)]] Position:vec4<f32>;
[[location(0)]] UV:vec2<f32>;
};
[[stage(vertex)]] fn vertMain([[builtin(vertex_index)]] VertexIndex:u32) -> VertexOutput {
var i = i32(VertexIndex);
var quadIndex = i / 6;
var cornerPosition = vec2<f32>(
f32(i % 2),
f32(((i + 1) % 6 / 3))
);
var x = uniforms.numColumns;
var position = cornerPosition;
position = position + vec2<f32>(
f32(quadIndex % uniforms.numColumns),
f32(quadIndex / uniforms.numColumns)
);
position = position / vec2<f32>(
f32(uniforms.numColumns),
f32(uniforms.numRows)
);
position = 1.0 - position * 2.0;
// position.x = position.x + f32(quadIndex) * 0.01;
return VertexOutput(
vec4<f32>(position, 1.0, 1.0),
cornerPosition
);
}
// Fragment shader
[[stage(fragment)]] fn fragMain([[location(0)]] UV:vec2<f32>) -> [[location(0)]] vec4<f32> {
var msdf:vec4<f32> = textureSample(msdfTexture, msdfSampler, UV / f32(msdfUniforms.numColumns));
// msdf.b = msdf.b * (sin(f32(timeUniforms.time) / 1000.0 * TWO_PI) * 0.5 + 0.5);
msdf.b = msdf.b * f32(timeUniforms.frame / 60 % 2);
var time = timeUniforms.time;
return msdf;
}