Skip to content

How to load shader from external file? THREE.FileLoader

I want to load shader from external file. This shader is correct, as it works when inserted in <script> tag. I do the following:

var loader = new THREE.FileLoader();
loader.load('shader.vert',function ( data ) {vShader =  data;},);
loader.load('shader.frag',function ( data ) {fShader =  data;},);

Later I use it as:

var material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: vShader,
    fragmentShader: fShader,

});

The problem is that when i reload the page I mave errors, that shaders are not defined. When I type vShader in console it prints me out what I want. When I start to debug (breakpoint inside init() function vShader is undefined.

Could You explain my mistake?

The whole code:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">

    <script src="https://threejs.org/build/three.min.js"></script>

</head>
<body>
<div class="WebGL-output" id="WebGL-output"></div>
<script id="vertexShader" type="x-shader/x-vertex" src="shader.vert">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform vec3 color;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(color,1.);
}
</script>

<script>
var renderer,uniforms,vShader,fShader,camera,scene;
var loader = new THREE.FileLoader();
init();
animate();
function init(){
renderer = new THREE.WebGLRenderer
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('WebGL-output').appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 5000;
scene = new THREE.Scene();

//******* DO NOT WORK
loader.load('plaid.frag',function ( data ) {fShader =  data;},);
loader.load('plaid.vert',function ( data ) {vShader =  data;},);
// ****** WORK
//fShader = document.getElementById('fragmentShader').text;
//vShader = document.getElementById('vertexShader').text;

// **************************
uniforms = {
    "color" : {
        type : "c",
        //value :new THREE.Color(0xf0f0f0)
        value :new THREE.Color(0x00ff00)
    },
};


var material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: vShader,
    fragmentShader: fShader,
});

// Create circles and add to scene.
var geometry = new THREE.CircleGeometry(100, 50);
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

var light = new THREE.AmbientLight(0x000000); // soft white light
scene.add(light);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
</script>
</body>
</html>

Answer

Your mistake is that loading files in browsers is asychronous. So first you have this code

loader.load('plaid.frag',function ( data ) {fShader =  data;},);
loader.load('plaid.vert',function ( data ) {vShader =  data;},);

Those functions you made that set fShader and vShader will not be called until some time later (how ever long it takes to download the files)

But, just a few lines down you use both fShader and vShader immediately even though they haven’t downloaded yet

var material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: vShader,
    fragmentShader: fShader,
});

If you really want to load from external files you need to structure your code to wait for them to download.

As one example

var renderer,uniforms,vShader,fShader,camera,scene;
var loader = new THREE.FileLoader();
init();

function init() {
  renderer = new THREE.WebGLRenderer
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.getElementById('WebGL-output').appendChild(renderer.domElement);
  camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
  camera.position.z = 5000;
  scene = new THREE.Scene();

  var numFilesLeft = 2;
  
  function runMoreIfDone() {
     --numFilesLeft;
     if (numFilesLeft === 0) {
       more();
     }
  }
  
  //******* DO NOT WORK
  loader.load('plaid.frag',function ( data ) {fShader =  data; runMoreIfDone(); },);
  loader.load('plaid.vert',function ( data ) {vShader =  data; runMoreIfDone(); },);
  // ****** WORK
  //fShader = document.getElementById('fragmentShader').text;
  //vShader = document.getElementById('vertexShader').text;
}

function more() {

  // **************************
  uniforms = {
      "color" : {
          type : "c",
          //value :new THREE.Color(0xf0f0f0)
          value :new THREE.Color(0x00ff00)
      },
  };


  var material = new THREE.ShaderMaterial({
      uniforms: uniforms,
      vertexShader: vShader,
      fragmentShader: fShader,
  });

  // Create circles and add to scene.
  var geometry = new THREE.CircleGeometry(100, 50);
  mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  var light = new THREE.AmbientLight(0x000000); // soft white light
  scene.add(light);
  
  animate();
}

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}