Creating custom shaders by modifying existing shaders

Hello!

I’ve just done a lot of reading, and it seems some people are suggesting chunk replacement as a way of modifying existing shaders (rather than having to re-implement shadows, etc). Is this a fairly sane approach as a solution? At the moment I’m considering starting with this and then moving toward exporting the resultant shader as dedicated fragment and vertex shaders for futureproofing.

Hi @hearsepileup,

I’d say that for most model/material based shaders overriding/extending shader chunks is the way to go.

You will get a great boilerplate of uniforms to use (uv coordinates, model matrix, view/world position etc), and as you said support for lighting/shadows etc.

I don’t see these two ways of developing shaders (custom shader vs modifying shader chunks) as much different, since even with the shader chunk approach you have great control over the resulting shader. You can quickly modify the base chunks and get a very custom shader.

2 Likes

Great to hear it! Yeah I’m doing a couple of interating things like screenspace shading and an attempt at a Return of the Obra Dinn style dither (for a couple of minor effects). It’d be nice to just lay these on top of the groundwork already here rather than trying to re-implement. (I’m still a GLSL n00b, so really it would be trying and failing to implement hahaha).

I’m going to play around and see if I can find the most apropriate chunks to modify. Thanks for your help!

2 Likes

Where would be best to look for reference for these? I know they’re semi undocumented, but I’m having trouble getting access to these variables. What I’m really after is screen space uvs, though I can see if I set pixel snap I can get the screen size, so at worst I can just do gl_FragCoord.xy/uScreenSize.xy.

I’d say start your study on the chunks repo here, especially the base/start VS/PS programs:

At any point in the console you can see the final compiled shader of any material using the following notation:

console.log(material.shader.definitions);

Take note though, that is for model/material based shaders. If you are writing a post process effect there you are authoring a custom shader from scratch and you will be using the default WebGL attributes.

The following site is a great learning resource to get comfortable with how WebGL works:

3 Likes

Thanks for all your help - I did it!

Here’s a sample of the important code for anyone else reading this:

// Grab all materials in project
var assets = this.app.assets.filter(function (asset) {
    return asset.type === 'material';
});

// Grab custom fragment shader functions - these just make sure I can grab only the bits I need for each shader
var luma = this.app.assets.find("getluma.fs").resource;
var dither = this.app.assets.find("dither8x8.fs").resource;

// We are replacing the default diffuse light function in order to insert the new shader functions. This shader fragment is literally a copy paste of the code at https://github.com/playcanvas/engine/blob/137f6cb19fdb01caf2f936029bd76ae2fe0ce2a9/src/graphics/program-lib/chunks/lightDiffuseLambert.frag
var lightDifuse = this.app.assets.find("lightdiffuse.fs").resource;

// Concat all the functions together
var functions = luma + '\n' + dither + '\n' + lightDifuse + '\n';

// Grab the custom shader manipulation
var setDither = '\n' + this.app.assets.find("distancedither.fs").resource + '\n';

// Copy the very end of the default shader implementation (combineColour and getEmission) to correctly set your fragment colour, then drop in the custom function. As you can see I set a 'scale' float; this is because I'm doing some pixellation in the dither function, but I wanted to keep the scale easily manipulatable.
var endPS = 'gl_FragColor.rgb = combineColor(); \n\
        gl_FragColor.rgb += getEmission(); \n\
        float scale = 2.0; \n' + 
        setDither;

// Loop over all materials
assets.forEach( asset => {
    const material = asset.resource;
    // Replace the diffuse lambert pixel shader with a new one + our custom functions
    material.chunks.lightDiffuseLambertPS = functions;
    // Ensure that the screen dimensions are available globally (I only need this because of my pixellation shader)
    material.pixelSnap = true;
    // Add your function calls to set the fragment
    material.chunks.endPS = endPS;
});
1 Like

Oh, and because I simply can’t help myself, here’s a preview of the results - really happy that I got it working!

3 Likes

Thanks for sharing @hearsepileup, that looks awesome!

1 Like