Upgrade 2018
For the year 2018, my recommendation is to utilize multiline template literals by wrapping the shader with backticks to span multiple lines.
const someShaderSource = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
If you prefer to store shaders in separate files, JavaScript modules provide an easy solution in 2018. A shader file could look like this:
// someshader.glsl.js
export default `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
To use JavaScript modules, your main JavaScript code must be in a separate file. You can import the shader source for usage:
// main.js
import someShaderSource from './someshader.glsl.js';
// utilize someShadeSource
Include it in your HTML using:
<script src="main.js" type="module"></script>
Alternatively, you can use it directly within a script tag on the page itself like this:
<script type="module">
import someShaderSource from './someshader.glsl.js';
// utilize someShadeSource
</script>
If you need to cater to older browsers, tools like Rollup can be used to bundle all imported statements into one large JavaScript file. This approach is adopted by three.js.
For IE11 compatibility, you can utilize Babel for converting multiline templates. Most other browsers have supported multiline templates for many years now.
Original Response
Why do shaders need to be in the html file for WebGL programs?
They don't have to be
You can place shaders in external JavaScript files. For example:
// --myshader.js--
var myFragmentShader =
"void main() {\n" +
" gl_FragColor = vec4(1,0,0,1);\n" +
"}n\";
Or in another common format:
// --myshader.js--
var myFragmentShader = [
"void main() {",
" gl_FragColor = vec4(1,0,0,1);",
"}",
].join("\n");
In all browsers that support WebGL, you can make use of template literals
// --myshader.js--
var myFragmentShader = `
void main() {
gl_FragColor = vec4(1,0,0,1);
}
`;
Otherwise, shaders can be kept in text files and loaded through XMLHTTPRequest
// --myshader.txt
void main() {
gl_FragColor = vec4(1,0,0,1);
}
Then, in JavaScript, execute the following steps:
function loadTextFile(url, callback) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.addEventListener('load', function() {
callback(request.responseText);
});
request.send();
}
loadTextFile("myshader.txt", function(text) {
// utilize text...
});
The reason why shaders are often placed in HTML files is due to its simplicity, efficiency, and synchronous nature.
Simple: Unlike versions stored in JS files, there's no need to add quotes and extra punctuation. With ES6, this is even more streamlined. Every WebGL-supported browser also supports ES6 template strings.
Efficient: Having shaders within the HTML means only one server request. While some users may concatenate JS files for better performance.
Synchronous: Text and JS files require dealing with callbacks or promises for asynchronous loading whereas HTML offers a more straightforward approach.
If your example doesn't work, it might be related to cross-origin resource access restrictions. The <script>
tag was designed before these concerns became prominent, hence limitations were not initially enforced. Technologies like XMLHttpRequest now require permissions for cross-origin access to prevent security risks.