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

18,533

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

Solution 3

Shader language scripts are just text. The text can be grabbed or generated from anywhere (that you can read or generate text). Many tutorials just skip over the part where the magic happens and and the WebGL shader instances are created from the string obtained. There's no reason you couldn't refer to the scripts externally like you propose, but you would need additional JavaScript to load the contents, not the browser. Script tags are likely used in the tutorials primarily because if you give a script tag a type that the browser doesn't understand, the browser skips execution of the tag's contents or retrieval of the script's source, so the tag's contents and attributes can be used however you desire.

Edit: Well, I have to take some things back. I decided to go through four browsers (Chrome, Firefox, IE9, Opera), and see what happens when you have a line

<script type="x-shader/x-fragment" id="shader-fs" src="util/fs"></script>

in your html. Turns out, the browser does load the file in every browser I tried, so I was wrong. However, that doesn't mean the browser knows what to do with the file besides cache it. I don't know what you mean by "Why doesn't src="util/fs" work???". In every browser I've tried,

alert(document.getElementById('shader-fs').src);

alerts the full path to the file when given a partial path. (Maybe this is your problem? You're expecting a partial path when the browser gives you a full one?) Besides that, I'm not sure how to interpret your problem.

Share:
18,533

Related videos on Youtube

Dov
Author by

Dov

Updated on June 16, 2022

Comments

  • Dov
    Dov about 2 years

    I have seen the following question where someone asked how to remove shaders from html: WebGL - is there an alternative to embedding shaders in HTML?

    There are elaborate workarounds to load in a file containing the shader suggested in the answers to the question.

    In the tutorial I saw, the shader code is embedded directly in the html. The javascript code refers to it using getElementById. But it's ugly embedding the shader directly in the html for many reasons. Why can't I just refer to it externally using the src= attribute?

    <script type="x-shader/x-fragment" id="shader-fs" src="util/fs"></script>
    

    The above doesn't work, I just want to know why not. This is clearly something to do with limitations on script itself, but I don't get it.

    • wildpeaks
      wildpeaks over 10 years
      They don't have to be in HTML, it's just something many examples use, but you could also fetch the shader sources using ajax or load as module using require.js or any other way you can think of to get text.
    • gaitat
      gaitat almost 10 years
  • Dov
    Dov over 11 years
    This doesn't answer the question. I tried to rewrite the script to pull in the text of the script externally. Forget that there is no supported text type "x-shader/x-fragment", the guy is using it in his loader code. Why doesn't src="util/fs" work???
  • JayC
    JayC over 11 years
    Updated answer. It might help you to add to your question how you're pulling the external source if this answer doesn't help.
  • Jack
    Jack almost 11 years
    Voting this up because, though a workaround has been presented, it is useful to know why the original example does not work.
  • Michiel van der Blonk
    Michiel van der Blonk about 9 years
    It's voted down (I think) because it states what script tag doesn't do, instead of explaining what it does do.
  • Sophia Gold
    Sophia Gold almost 8 years
    But if I use the ES6 format can I make Babel transcompile it one of the string versions?
  • Sophia Gold
    Sophia Gold almost 8 years
    Sorry, I overlooked the part where you said every browser that supports WebGL also supports template strings. If that's the case I'm wondering why then it still seems more common to store GLSL as actual strings? Honestly, working with that formatting is just awful.
  • zwcloud
    zwcloud over 6 years
    Once any shader error happens, it's hard to determine the exact line number of the error in shader written inside html. Who knows where is line 235? How do you solve this problem? (I will create a new question if you don't want to reply as a comment here.)
  • gman
    gman over 6 years
    @zwcloud: I do this in 2 ways, In my editors there's a line number. If the shader starts on line 517 and the error message says 235 then 517+235 = bad shader line is at 752. Otherwise in my library, twgl, if I get an error I print the entire shader with line numbers added to the console.
  • gman
    gman over 6 years
    @Sophia Gold: As for why storing them in script tags is more common I have a feeling that when WebGL shipped the template literals were not common. Many people start new WebGL projects by copying old ones so the practice continues. Template literals are also not compatible with any version of IE so if you want your site to have some kind of fall back and you want to use template literals then you need to take other steps. Use a transpiler like babel to convert to old JS or check if no support don't load the WebGL scripts etc...
  • zwcloud
    zwcloud over 6 years
    @gman I hope Google Chrome will somehow integrate the second way into its dev-tool.