WebGL: External GLSL Files

David Banks
2 min readMay 2, 2023

--

Introduction

Tutorials I see on WebGl seem to have the GLSL either in script tags:

<script src="fragment.glsl" type="x-shader/x-fragment">
precision mediump float;

void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
</script>

<script src="vertex.glsl" type="x-shader/x-vertex">
precision mediump float;

attribute vec2 vertPosition;

void main() {
gl_Position = vec4(vertPosition, 0.0, 1.0);
}
</script>

Or (even worse) in constants/variables embedding in the JS:

const vertexShaderText = [
'precision mediump float;',

'attribute vec2 vertPosition;',

'void main() {',
' gl_Position = vec4(vertPosition, 0.0, 1.0);',
'}',
].join('\n');

const fragmentShaderText = [
'precision mediump float;',

'void main()',
'{',
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);',
'}',
].join('\n');

These are horrible to work with.

In order to maximise the use of your IDEs abilities these need to be in separate *.glsl files (or whatever extension you’ve set up in the IDE to mean GLSL). Getting them is a bit of a faff, but I’ve written a nice function to do it for you.

Code

/**
* Fetch the fragment and vertex shader text from external files.
* @param vertexShaderPath
* @param fragmentShaderPath
* @returns {Promise<{vertexShaderText: string | null, fragmentShaderText: string | null}>}
*/
export async function fetchShaderTexts(vertexShaderPath, fragmentShaderPath) {
const results = {
vertexShaderText: null,
fragmentShaderText: null,
};

let errors = [];
await Promise.all([
fetch(vertexShaderPath)
.catch((e) => {
errors.push(e);
})
.then(async (response) => {
if (response.status === 200) {
results.vertexShaderText = await response.text();
} else {
errors.push(
`Non-200 response for ${vertexShaderPath}. ${response.status}: ${response.statusText}`
);
}
}),

fetch(fragmentShaderPath)
.catch((e) => errors.push(e))
.then(async (response) => {
if (response.status === 200) {
results.fragmentShaderText = await response.text();
} else {
errors.push(
`Non-200 response for ${fragmentShaderPath}. ${response.status}: ${response.statusText}`
);
}
}),
]);

if (errors.length !== 0) {
throw new Error(
`Failed to fetch shader(s):\n${JSON.stringify(errors, (key, value) => {
if (value?.constructor.name === 'Error') {
return {
name: value.name,
message: value.message,
stack: value.stack,
cause: value.cause,
};
}
return value;
}, 2)}`
);
}
return results;
}

If you’re wondering what’s the deal with the function being passed into the JSON.stringify() function then you should read my article on Serializing the Error Object in JavaScript.

This function accepts a path for each of the vertex and shader files, and fetches them for you.

Conclusion

I was finding this so annoying, it;s pretty trivial to write a function to handle fetching the shader text without having to resort to nasty non-solutions that just confuse readers who are trying to learn an already difficult technology.

If you’re using WebStorm then there’s a great plugin for syntax highlighting, code completion and some basic error checking too:

GLSL — IntelliJ IDEs Plugin | Marketplace (jetbrains.com)

I recommend installing it.

--

--