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.
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.
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!
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:
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;
});
is this still the best way to create a shader based on the default shader?
In my case I need to alpha mask a rotating model (sphere) in global space - meaning, the mask does not rotate with the model. The goal is to see inside the sphere but only where it is facing the camera. Doing this in a shader is trivial but I’m not sure what the best approach is to not lose the default shader features.
yeah I’d capture the shader without this feature using Spector JS.
Then inspect the code in order to figure out what needs to change.
When find that code in chunks engine/src/scene/shader-lib/chunks at main · playcanvas/engine · GitHub, copy those into your script, customize them and specify them on the material as an override.