Is it ok to use ES6 template syntax for shaders?

I see that in the threejs shaders, there are include statements such as:

#include <common>

uniform float scale;


I’m not trying to be too esoteric here, but I was wondering if anybody could think of a reason for me not to use es6 template strings?
Just a personal preference (I like that I have intellisense for the possible includes), but thought I’d ask in case there were unforeseen problems with this.



uniform float scale;


Nope, shouldn’t be a problem with that, considering that’s kinda what three does under the hood :sweat_smile:

(In general, regarding templates, keep in mind that you still have to use uniforms for any dynamic values though. Changing variable value in JS won’t change it on the GPU unless you recompile the shader.)

Ok Cool. I wasn’t sure if there were special tools people were using for glsl editing that somehow understood those import directives.

:slight_smile: Even if I thought that the shaders recompiled magically, template strings don’t actually change the string value if the variable changes after the string is constructed.

let v = 3; let str = `${v}`; v = 4; str; // "3"

i wish three did this, it would help tree shaking and create explicit contracts between shaders and their parts. i believe currently our bundles always carry the full shader lib, even if you effectively use 1% of it, everything is pulled in. (last time i checked that was the case at least). also makes understanding shaders/reading code really hard because the material classes are just empty shells that are magically filled in later.


Hm, it doesn’t sound like the most complex or breaking change / PR. @Mugen87, do you think there’d be anything preventing it :thinking: (besides taking some time ofc) ?

1 Like

If a user writes a custom material by including built-in shader chunks and the material should run in older browsers like IE11, it’s not possible to use ES6 syntax. So the engine needs a backwards compatible way to resolve shader chunks.


So the engine needs a backwards compatible way to resolve shader chunks.

Babel? :grin:

I personally don’t care that much about the tree shaking problem – in most cases I’m going to use threejs from cdn as you’ll get a decent percent of users getting from cache. three weighs in at less ~160kb which is amazing for what it does.

The current policy is that the build files three.js, three.min.js and the code from examples/js should run in older browsers so users don’t have to worry about transpiling.

this wouldn’t be a concern: three.js/rollup.config.js at 464efc85ecfda5c03d786d15d8f8eff20d70f256 · mrdoob/three.js · GitHub

all your dist files are bablified. you already control to which extend by giving it a target, currently

// the supported browsers of the three.js browser bundle
targets: '>0.3%, not dead',

you can check your targets in babels repl: Babel · The compiler for next generation JavaScript

it definitively transpiles string templates and they could be readily used in threes code.

1 Like

This isn’t true anymore AFAIK since browsers no longer share the cache between sites.

1 Like

still quite big compared to things like ogl (around 20kb). instead of cdns and script tags professional front end web dev revolves entirely around modules (node modules), they mostly expect things to work on slow internet connections which has gotten a much bigger issue with folks coming from countries where fast internet isn’t guaranteed.

it definitively transpiles string templates and they could be readily used in threes code.

This would be a nice change to make. GLSL in src was converted to template strings quite recently, so maybe we could move forward to convert #include statements as well.

To examine a simple example, src\renderers\shaders\ShaderLib\background_frag.glsl.js would look like this:

import tonemapping_fragment from '../ShaderChunk/tonemapping_fragment.glsl.js';
import encodings_fragment from '../ShaderChunk/encodings_fragment.glsl.js';

export default /* glsl */`
uniform sampler2D t2D;

varying vec2 vUv;

void main() {

	vec4 texColor = texture2D( t2D, vUv );

	gl_FragColor = mapTexelToLinear( texColor );



In three.module.js this would become:

var background_frag = "uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t${tonemapping_fragment}\n\t${encodings_fragment}\n}";

While in three.js it would become:

var background_frag = "\nuniform sampler2D t2D;\n\nvarying vec2 vUv;\n\nvoid main() {\n\n\tvec4 texColor = texture2D( t2D, vUv );\n\n\tgl_FragColor = mapTexelToLinear( texColor );\n\n\t".concat(tonemapping_fragment, "\n\t").concat(encodings_fragment, "\n\n}\n");

Or with “loose” mode in babel:

var background_frag = "\nuniform sampler2D t2D;\n\nvarying vec2 vUv;\n\nvoid main() {\n\n\tvec4 texColor = texture2D( t2D, vUv );\n\n\tgl_FragColor = mapTexelToLinear( texColor );\n\n\t" + tonemapping_fragment + "\n\t" + encodings_fragment + "\n\n}\n";

This seems like a fairly straightforward change. Besides tree-shaking improvements we could also remove the functions from WebGLProgram.js that @mjurczyk linked above. @Mugen87 you are more familiar with this part of the code, are there any potential issues here?

I was not referring to what three.js internally does. I’m talking about the dev who wants to use three.js for creating a custom shader material that should run with IE11.

This developer has to use the current #include syntax and three.js has to resolve it.

I’m afraid this is not possible for the above reason.

1 Like

Yeah… can’t see an easy way around that. Oh well, back to waiting for three.js and three.min.js to be removed :neutral_face:

Now I’m wondering - wouldn’t this then break all the helpers / shaders / modifiers that depend on replacing #include <chunk>? Fyrestar’s extendMaterial, built-in CurveModifier etc.)


Yes, but in the long run, there might be better ways of doing that than using .replace. Seems like a very fragile and unwieldy approach. Would it be possible to have a way to redefine includes like beginnormal_vertex instead?

On the other hand, my attitude towards anything like this in the current material system has just been “wait for node materials to replace it”. So I’m not gonna expend too much effort here.

1 Like

wouldn’t they construct a shader in the exact same way as three would, only without string templates? if the argument is that three doesnt want to break “# import”, ever, then that’s the way it is - but i dont understand the internet explorer thing. it’s not about templates, even if three concetenates strings - that would be good enough. the problem is the “import” thing, which forces it to bundle all shader code.

import { shadows, lights } from “three/shaderlib”

const myShader = shadows + lights + “gl_frag = whatever”

or the same without import statements and IIEFs. please forgive my asking, it just could be something that would make three less fragile, reduce the bundle, help beginners understand shader code.

1 Like

Node materials are bad for performance, memory and to maintain, the cost of this is way higher than using .replace and is rather suited for beginners or non-programmers, asides of the work with this increasing drastically compared to simple code. It’s kinda the THREE.Geometry of shader, which is why i not understand why this would be preferable. As a optional feature it makes sense but really not as the default approach.

Patching works perfectly fine, the real issue is rather the missing consistency across all the standard materials, this would be doable to add a more consistent structure using comments asides of the includes, but so far it is already quite consistent, considering the most needed entry points.

The way THREE currently basically uses “includes” is much better than pre-assembling just everything as it makes patching of features that should be separated not possible anymore (asides of blowing up the files with redundant codeblocks). The string of a built-in material is very small currently thanks to these includes what makes patching easy and fast.