Skip to content

Improved support for WGSL shaders. #7320

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions examples/src/examples/compute/particles.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -208,22 +208,11 @@ assetListLoader.load(() => {
// ------- Particle rendering -------

// material to render the particles using WGSL shader as GLSL does not have access to storage buffers
const shaderSource = files['shader-shared.wgsl'] + files['shader-rendering.wgsl'];
const material = new pc.ShaderMaterial({
uniqueName: 'ParticleRenderShader',
vertexCode: shaderSource,
fragmentCode: shaderSource,
shaderLanguage: pc.SHADERLANGUAGE_WGSL,

// For now WGSL shaders need to provide their own bind group formats as they aren't processed.
// This has to match the ub_mesh struct in the shader.
meshUniformBufferFormat: new pc.UniformBufferFormat(app.graphicsDevice, [
new pc.UniformFormat('matrix_model', pc.UNIFORMTYPE_MAT4)
]),
meshBindGroupFormat: new pc.BindGroupFormat(app.graphicsDevice, [
// particle storage buffer in read-only mode
new pc.BindStorageBufferFormat('particles', pc.SHADERSTAGE_VERTEX | pc.SHADERSTAGE_FRAGMENT, true)
])
vertexCode: files['shader-shared.wgsl'] + files['shader-rendering.vertex.wgsl'],
fragmentCode: files['shader-shared.wgsl'] + files['shader-rendering.fragment.wgsl'],
shaderLanguage: pc.SHADERLANGUAGE_WGSL
});

// index buffer - two triangles (6 indices) per particle using 4 vertices
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
varying color: vec4f;

@fragment
fn fragmentMain(input : FragmentInput) -> FragmentOutput {
var output: FragmentOutput;
output.color = input.color;
return output;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
uniform matrix_viewProjection : mat4x4f;

// particle storage buffer in read-only mode
var<storage, read> particles: array<Particle>;

// quad vertices - used to expand the particles into quads
var<private> pos : array<vec2f, 4> = array<vec2f, 4>(
vec2(-1.0, 1.0), vec2(1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, -1.0)
);

const particleSize = 0.04;

varying color: vec4f;

@vertex
fn vertexMain(input : VertexInput) -> VertexOutput {

// get particle position from the storage buffer
var particleIndex = input.vertexIndex / 4;
var particlePos = particles[particleIndex].position;

// extract camera left and up vectors from the view-projection matrix
var left = vec3f(uniform.matrix_viewProjection[0][0], uniform.matrix_viewProjection[1][0], uniform.matrix_viewProjection[2][0]);
var up = vec3f(uniform.matrix_viewProjection[0][1], uniform.matrix_viewProjection[1][1], uniform.matrix_viewProjection[2][1]);

// expand the particle into a quad
var quadVertexIndex = input.vertexIndex % 4;
var quadPos = vec3f(pos[quadVertexIndex] * particleSize, 0.0);
var expandedPos = quadPos.x * left + quadPos.y * up;

// projected position
var output : VertexOutput;
output.position = uniform.matrix_viewProjection * vec4(particlePos + expandedPos, 1.0);

// lerp between red and yellow based on the time since the particle collision
output.color = mix(vec4f(1.0, 1.0, 0.0, 1.0), vec4f(1.0, 0.0, 0.0, 1.0), particles[particleIndex].collisionTime / 7.0);
return output;
}
56 changes: 0 additions & 56 deletions examples/src/examples/compute/particles.shader-rendering.wgsl

This file was deleted.

110 changes: 58 additions & 52 deletions examples/src/examples/graphics/wgsl-shader.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import * as pc from 'playcanvas';
const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas'));
window.focus();

const assets = {
diffuse: new pc.Asset('color', 'texture', { url: `${rootPath}/static/assets/textures/playcanvas.png` })
};

// Even though we're using WGSL, we still need to provide glslang
// and twgsl to compile shaders used internally by the engine.
const gfxOptions = {
Expand All @@ -31,59 +35,61 @@ createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler];

const app = new pc.AppBase(canvas);
app.init(createOptions);
app.start();

// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

// Ensure canvas is resized when window changes size
const resize = () => app.resizeCanvas();
window.addEventListener('resize', resize);
app.on('destroy', () => {
window.removeEventListener('resize', resize);
});

const material = new pc.ShaderMaterial({
uniqueName: 'MyWGSLShader',
vertexCode: files['shader.vert.wgsl'],
fragmentCode: files['shader.frag.wgsl'],
shaderLanguage: pc.SHADERLANGUAGE_WGSL,

// For now WGSL shaders need to provide their own bind group formats as they aren't processed.
// This has to match the ub_mesh struct in the shader.
meshUniformBufferFormat: new pc.UniformBufferFormat(app.graphicsDevice, [
new pc.UniformFormat('matrix_model', pc.UNIFORMTYPE_MAT4),
new pc.UniformFormat('amount', pc.UNIFORMTYPE_FLOAT)
]),
meshBindGroupFormat: new pc.BindGroupFormat(app.graphicsDevice, [])
});

// create box entity
const box = new pc.Entity('cube');
box.addComponent('render', {
type: 'box',
material: material
});
app.root.addChild(box);

// create camera entity
const camera = new pc.Entity('camera');
camera.addComponent('camera', {
clearColor: new pc.Color(0.5, 0.6, 0.9)
});
app.root.addChild(camera);
camera.setPosition(0, 0, 3);

// Rotate the box according to the delta time since the last frame.
// Update the material's 'amount' parameter to animate the color.
let time = 0;
app.on('update', (/** @type {number} */ dt) => {
box.rotate(10 * dt, 20 * dt, 30 * dt);

time += dt;
// animate the amount as a sine wave varying from 0 to 1
material.setParameter('amount', (Math.sin(time * 4) + 1) * 0.5);
const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
assetListLoader.load(() => {
app.start();

// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

// Ensure canvas is resized when window changes size
const resize = () => app.resizeCanvas();
window.addEventListener('resize', resize);
app.on('destroy', () => {
window.removeEventListener('resize', resize);
});

const material = new pc.ShaderMaterial({
uniqueName: 'MyWGSLShader',
vertexCode: files['shader.vert.wgsl'],
fragmentCode: files['shader.frag.wgsl'],
shaderLanguage: pc.SHADERLANGUAGE_WGSL,
attributes: {
position: pc.SEMANTIC_POSITION,
texCoords: pc.SEMANTIC_TEXCOORD0
}
});

material.setParameter('diffuseTexture', assets.diffuse.resource);

// create box entity
const box = new pc.Entity('cube');
box.addComponent('render', {
type: 'box',
material: material
});
app.root.addChild(box);

// create camera entity
const camera = new pc.Entity('camera');
camera.addComponent('camera', {
clearColor: new pc.Color(0.5, 0.6, 0.9)
});
app.root.addChild(camera);
camera.setPosition(0, 0, 3);

// Rotate the box according to the delta time since the last frame.
// Update the material's 'amount' parameter to animate the color.
let time = 0;
app.on('update', (/** @type {number} */ dt) => {
box.rotate(10 * dt, 20 * dt, 30 * dt);

time += dt;
// animate the amount as a sine wave varying from 0 to 1
material.setParameter('amount', (Math.sin(time * 4) + 1) * 0.5);
});
});

export { app };
31 changes: 13 additions & 18 deletions examples/src/examples/graphics/wgsl-shader.shader.frag.wgsl
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
struct ub_mesh {
matrix_model : mat4x4f,
amount : f32,
}

struct ub_view {
matrix_viewProjection : mat4x4f,
}
varying fragPosition: vec4f;
varying texCoord: vec2f;

struct VertexOutput {
@builtin(position) position : vec4f,
@location(0) fragPosition: vec4f,
}

@group(2) @binding(0) var<uniform> uvMesh : ub_mesh;
@group(0) @binding(0) var<uniform> ubView : ub_view;
uniform amount : f32;
var diffuseTexture : texture_2d<f32>;
var diffuseSampler : sampler;

@fragment
fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
fn fragmentMain(input : FragmentInput) -> FragmentOutput {

var color : vec3f = input.fragPosition.rgb;
var roloc : vec3f = vec3f(1.0) - color;
return vec4f(mix(color, roloc, uvMesh.amount), 1.0);
var roloc : vec3f = vec3f(uniform.amount) + color;
var diffuseColor : vec4f = textureSample(diffuseTexture, diffuseSampler, input.texCoord);

var output: FragmentOutput;
output.color = vec4f(diffuseColor.xyz * roloc, 1.0);
return output;
}
27 changes: 8 additions & 19 deletions examples/src/examples/graphics/wgsl-shader.shader.vert.wgsl
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
struct ub_mesh {
matrix_model : mat4x4f,
amount : f32,
}

struct ub_view {
matrix_viewProjection : mat4x4f,
}
attribute position: vec4f;
attribute texCoords: vec2f;

struct VertexOutput {
@builtin(position) position : vec4f,
@location(0) fragPosition: vec4f,
}

@group(2) @binding(0) var<uniform> uvMesh : ub_mesh;
@group(0) @binding(0) var<uniform> ubView : ub_view;
varying fragPosition: vec4f;
varying texCoord: vec2f;

struct VertexInput {
@location(0) position : vec4f,
}
uniform matrix_model : mat4x4f;
uniform matrix_viewProjection : mat4x4f;

@vertex
fn vertexMain(input : VertexInput) -> VertexOutput {
var output : VertexOutput;
output.position = ubView.matrix_viewProjection * (uvMesh.matrix_model * input.position);
output.position = uniform.matrix_viewProjection * (uniform.matrix_model * input.position);
output.fragPosition = 0.5 * (input.position + vec4(1.0));
output.texCoord = input.texCoords;
return output;
}
14 changes: 13 additions & 1 deletion src/platform/graphics/bind-group-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ class BindUniformBufferFormat extends BindBaseFormat {
* @category Graphics
*/
class BindStorageBufferFormat extends BindBaseFormat {
/**
* Format, extracted from vertex and fragment shader.
*
* @type {string}
* @ignore
*/
format = '';

/**
* Create a new instance.
*
Expand Down Expand Up @@ -134,8 +142,9 @@ class BindTextureFormat extends BindBaseFormat {
* @param {boolean} [hasSampler] - True if the sampler for the texture is needed. Note that if the
* sampler is used, it will take up an additional slot, directly following the texture slot.
* Defaults to true.
* @param {string|null} [samplerName] - Optional name of the sampler. Defaults to null.
*/
constructor(name, visibility, textureDimension = TEXTUREDIMENSION_2D, sampleType = SAMPLETYPE_FLOAT, hasSampler = true) {
constructor(name, visibility, textureDimension = TEXTUREDIMENSION_2D, sampleType = SAMPLETYPE_FLOAT, hasSampler = true, samplerName = null) {
super(name, visibility);

// TEXTUREDIMENSION_***
Expand All @@ -146,6 +155,9 @@ class BindTextureFormat extends BindBaseFormat {

// whether to use a sampler with this texture
this.hasSampler = hasSampler;

// optional name of the sampler (its automatically generated if not provided)
this.samplerName = samplerName ?? `${name}_sampler`;
}
}

Expand Down
Loading