Why do shaders have to be in html file for webgl program?

Solution 1:

You don't have to use <script> tags at all to load a shader program. Most tutorials and examples just use them as a container to store a string in the DOM of the webpage. The script type "x-shader/x-fragment" is meaningless for web browsers, so they don't execute the script. They do, however, store the content of that tag as a string in the DOM which can then later be accessed by "real" scripts. This only works when the script content is in the HTML file. When you load the script via a src attribute, the content does not become a text childnode of the script tag and thus can not be accessed through the DOM tree.

You can just as well store the sourcecode for the shader as a string in a Javascript file:

// myVertextShader.glsl.js
var myVertexShaderSrc =         
        "attribute vec3 pos;"+      
        "void main() {"+        
        "   gl_Position = vec4(pos, 1.0);"+     
        "}"
    ;

You would then compile the shader like this:

var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, myVertexShaderSrc);
gl.compileShader(vertexShader);

gl.attachShader(program, vertexShader);

Solution 2:

Update 2018

In 2018 I'd suggest using multiline template literals as in surround the shader with backticks and it can cross multiple lines

const someShaderSource = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
  gl_Position = matrix * position;
}
`;

If you want to put shaders in separate files you can easily do this in 2018 using JavaScript modules. A shader file might 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 must be in a separate file. You can access the shader source by importing it

// main.js

import someShaderSource from './someshader.glsl.js';

// use someShadeSource

And you include it in your HTML with

<script src="main.js" type="module"></script>

Or you can use it from a script tag in the page itself like this

<script type="module">
import someShaderSource from './someshader.glsl.js';

// use someShadeSource
</script>

If you want to support older browsers you can use a tool like rollup which will read all the import statements and generate one large JavaScript file. This is what three.js does.

If you need to support IE11 you can use babel to convert the multiline templates. All other browsers have supported multiline templates for many years.

Original Answer

Why do shaders have to be in html file for webgl program?

They don't

You can put shaders in external javascript. For example

// --myshader.js--
var myFragmentShader = 
  "void main() {\n" +
  "  gl_FragColor = vec4(1,0,0,1);\n" +
  "}n\";

Or 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 use template literals

// --myshader.js--
var myFragmentShader = `
  void main() {
    gl_FragColor = vec4(1,0,0,1); 
  }
`;

Otherwise you can put them in text files and load them with XMLHTTPRequest

// --myshader.txt
  void main() {
    gl_FragColor = vec4(1,0,0,1); 
  }

Then in JavaScript do the following

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) {
  // use text...
});

The reason people put them in the HTML is because it's easy, efficient, and synchronous.

easy: unlike the JS file versions you don't have to surround every line with quotes and other punctuation. Although now with es6 that's no longer an issue. Every browser that supports WebGL supports es6 template strings.

efficient: unlike the text and js files there's only one request to the server. Of course some people might run a concatenator on their js files to fix some of that.

synchronous: unlike the text files their usages is synchronous. No need for callbacks or promises or otherwise dealing with asynchronous issues of downloading files.

As for why your example doesn't work I'm pretty sure the reason is it would allow cross origin resource access. The <script> tag was designed before people figured out cross origin access was a problem so they couldn't turn off cross origin scripts without breaking a bunch of sites. They could make everything else more strict.

XMLHttpRequest for example does not allow cross origin access unless the server you're contacting gives permission. If script tags let you access that content you could use script tags to work around that restriction. In other words instead of making a XMLHttpRequest and reading the request.responseText for the result you'd just programmatically make a script tag, set its src to the URL you want and then read its text field when it finished. To make sure you can not do that you're not allowed to read the text field of a script tag that had a src attribute