Setting Up Your First WebGL Project With CurtainsJS - Four Part Setup
Tags: javascript, webdev, beginners, programming
Published: 2022-06-14 19:23:41

Today we want to create a basic template using CurtainsJS with a div to hold your canvas and a div to hold your images.

## Getting started
Go ahead and initialise our new project using the [CodePen playground](https://codepen.io/martinlaxenaire/full/pVQYoG) or setup your own project on [Visual Studio Code](https://visualstudio.microsoft.com/) with the following file structure under your src folder.

- index.html
- style.css
- scripts.js

## Part 1: HTML
The HTML is really basic. Edit your `index.html` and replace it with the following code.

```html
<!-- div that will hold our WebGL canvas -->
		<div id="canvas"></div>
		<!-- div used to create our plane -->
		<div class="plane">
<!-- image that will be used as a texture by our plane -->
 <img src="https://images.unsplash.com/photo-1462331940025-496dfbfc7564?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8OHx8Z2FsYXh5fGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=600&q=60" alt="Photo by Simon Zhu on Unsplash" crossorigin />
		</div>
```
## Part 2: CSS
Next step is to add the following styles and complete our `style.css` file. Just make sure the div that will wrap the canvas fits the document, and apply any size you want to your plane div element.

```css
body {
  /* make the body fits our viewport */
  position: relative;
  width: 100%;
  height: 100vh;
  margin: 0;
  
  /* hide scrollbars */
  overflow: hidden;
  background-color: black;
}

#canvas {
  /* make the canvas wrapper fits the window */
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
}

.plane {
  /* define the size of your plane */
  width: 80%;
  max-width: 1400px;
  height: 80vh;
  position: relative;
  top: 10vh;
  margin: 0 auto;
  overflow: hidden;
}

.plane img {
  /* hide the img element */
  display: none;
}

/*** in case of error show the image ***/

.no-curtains .plane {
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.no-curtains .plane img {
  display: block;
  max-width: 100%;
  object-fit: cover;
}
```
## Part 3: JavaScript
Now we can implement our JavaScript logic to our setup like so.

```javascript
window.onload = function() {
  // set up our WebGL context and append the canvas to our wrapper
  var webGLCurtain = new Curtains({
    container: "canvas"
  });
  
  // if there's any error during init, we're going to catch it here
  webGLCurtain.onError(function() {
    // we will add a class to the document body to display original images
    document.body.classList.add("no-curtains");
  });

  // get our plane element
  var planeElement = document.getElementsByClassName("plane")[0];

  // set our initial parameters (basic uniforms)
  var params = {
    vertexShaderID: "plane-vs", // our vertex shader ID
    fragmentShaderID: "plane-fs", // our framgent shader ID
    //crossOrigin: "", // codepen specific
    uniforms: {
      time: {
        name: "uTime", // uniform name that will be passed to our shaders
        type: "1f", // this means our uniform is a float
        value: 0,
      },
    }
  }

  // create our plane mesh
  var plane = webGLCurtain.addPlane(planeElement, params);

  // if our plane has been successfully created
  // we use the onRender method of our plane fired at each requestAnimationFrame call
  plane && plane.onRender(function() {
    plane.uniforms.time.value++; // update our time uniform value
  });

}
```

## Part 4: Shaders
Cool! We can now implement some basic vertex and fragment shaders. Just put it inside your body tag, right before you include the library.

```javascript
<script id="plane-vs" type="x-shader/x-vertex">
			#ifdef GL_ES
			precision mediump float;
			#endif

			// those are the mandatory attributes that the lib sets
			attribute vec3 aVertexPosition;
			attribute vec2 aTextureCoord;

			// those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
			uniform mat4 uMVMatrix;
			uniform mat4 uPMatrix;
          
      // our texture matrix uniform (this is the lib default name, but it could be changed)
      uniform mat4 uTextureMatrix0;

			// if you want to pass your vertex and texture coords to the fragment shader
			varying vec3 vVertexPosition;
			varying vec2 vTextureCoord;

			void main() {
				vec3 vertexPosition = aVertexPosition;

				gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);

				// set the varyings
        // thanks to the texture matrix we will be able to calculate accurate texture coords
        // so that our texture will always fit our plane without being distorted
				vTextureCoord = (uTextureMatrix0 * vec4(aTextureCoord, 0.0, 1.0)).xy;
				vVertexPosition = vertexPosition;
			}
        </script>
        <script id="plane-fs" type="x-shader/x-fragment">
			#ifdef GL_ES
			precision mediump float;
			#endif

			// get our varyings
			varying vec3 vVertexPosition;
			varying vec2 vTextureCoord;

			// the uniform we declared inside our javascript
			uniform float uTime;

			// our texture sampler (default name, to use a different name please refer to the documentation)
			uniform sampler2D uSampler0;

			void main() {
        // get our texture coords
				vec2 textureCoord = vTextureCoord;

				// displace our pixels along both axis based on our time uniform and texture UVs
				// this will create a kind of water surface effect
				// try to comment a line or change the constants to see how it changes the effect
				// reminder : textures coords are ranging from 0.0 to 1.0 on both axis
				const float PI = 3.141592;

				textureCoord.x += (
					sin(textureCoord.x * 10.0 + ((uTime * (PI / 3.0)) * 0.031))
					+ sin(textureCoord.y * 10.0 + ((uTime * (PI / 2.489)) * 0.017))
					) * 0.0075;

				textureCoord.y += (
					sin(textureCoord.y * 20.0 + ((uTime * (PI / 2.023)) * 0.023))
					+ sin(textureCoord.x * 20.0 + ((uTime * (PI / 3.1254)) * 0.037))
					) * 0.0125;

				gl_FragColor = texture2D(uSampler0, textureCoord);
			}
        </script>
```

## Recap
If you followed along then you should have completed the project and finished off your basic template.

Now if you made it this far, then I am linking the code to my [Sandbox](https://codepen.io/hr21don/full/QWQYYdJ) for you to fork or clone and then the job's done.

## Useful resources

https://www.curtainsjs.com/