0

I'm starting to get to grips with writing shaders and it has been very convenient to use THREE.js to build an OpenGL scene in order to play around with shaders written in GLSL. Up to now I have been including the GLSL code in a script field within the main HTML file:

<html>
<head>
    <meta charset=uft-8>
    <title>Babby's first three.js app</title>
    <style>
        body { margin: 0; }
        canvas { width: 100%; height: 100% }
    </style>
</head>
<body>
    <script src="js/three.js"></script>

    <script id="fragShader" type="shader-code">
        // GLSL shader goes here!
        void main(){
            /* shader body */
        }
    </script>

    <script>
        // Three.js scene setup goes here

        // Get shader code from fragShader script element
        var shaderCode = document.getElementById("fragShader").innerHTML; 

        // Apply it to a material
        bufferMaterial = new THREE.ShaderMaterial({
            uniforms : {
                bufferTexture : {type : "t", value : textureA},
            },
            fragmentShader : shaderCode 
        });

        // Render everything
    </script>
</body> 

I'm working on a more complex shader project and would like to split the main html file up so that shader code can be kept seperate but I've found this surprisingly hard to do.

I've tried using jquery to read the shader code stored in a seperate text file (I'm running a local server):

<script src ="node_modules/jquery/dist/jquery.js"></script>
<script> 
    $(function(){
        $("#fragShader").load("mainShader.html"); // contains GLSL code
    });
</script> 

<script id="fragShader"></script>

But the shader cannot compile - fragment ERROR: Missing main().

Any help for this js novice is much appreciated!

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Makkasu
  • 166
  • 1
  • 1
  • 10

1 Answers1

1

I often create a separate js file which has an object of form:

var customShaderObject = {
  fragmentShader: /* code (see below) */
  vertexShader: /* code (see below) */
  ... etc.
}

Each of the shaders is an array of strings with each index representing one line which is joined at the end using a new line as the separator e.g.

fragmentShader: [
  'line 1',
  'line 2',
  'line 3',
  ' etc. '
].join('\r\n');

A significantly easier approach in terms of editing the code is to use the template literal syntax (back ticks `) instead of an array because then you can basically edit a single string like normal code over multiple lines.

This approach (both using arrays and template literals) also has the added advantage of allowing you to create the value as a function so that you can slot variables into parts of the shader at runtime e.g.

fragmentShader: function (bar) {
    return [
      'float foo = ' + bar.toString() + ';',
      'line 2',
      'line 3',
      ' etc. '
    ].join('\r\n');
  }

or with the template literal:

fragmentShader: function (bar) {
    return `
      float foo = ${bar.toString()};
      line 2
      line 3
      //etc.
    `;
  }

You can then call the shaders by referencing their parent object like you would for any other JS object.

Code_Frank
  • 381
  • 1
  • 6
  • 1
    Template literals have been available in every browser except IE since like 2009. They were part of ES5. There's zero reason not to use them. – gman Mar 18 '18 at 01:34
  • Ah, thanks for pointing that out. I've removed the reference to ES6. But yes, they should be used - I think it's a lot cleaner and a lot easier to manage if used like in these examples – Code_Frank Mar 18 '18 at 01:39
  • THREE.FileLoader can also be used to fetch shaders asynchronous. – pailhead Mar 18 '18 at 22:37