├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── app └── index.html ├── images ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png └── triangle.png ├── lib ├── createApp.js ├── createBunnyGeometry.js ├── index.js ├── reference │ ├── shader1.frag │ ├── shader1.vert │ ├── shader2.frag │ ├── shader2.vert │ ├── shader3.frag │ ├── shader3.vert │ ├── shader4.frag │ ├── shader4.vert │ ├── shader5.frag │ └── shader5.vert ├── shader.frag └── shader.vert └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: [ "es2015" ] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Jam3 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### [jam3-lesson](https://github.com/Jam3/jam3-lesson) » webgl » shader-threejs 2 | 3 | --- 4 | 5 | # WebGL Lessons — ThreeJS Shaders 6 | 7 | In this lesson, we'll learn how to apply Fragment and Vertex shaders to a [ThreeJS](http://threejs.org/) mesh in WebGL. Make sure you read [webgl-shader-intro](https://github.com/Jam3/jam3-lesson-webgl-shader-intro) first to understand the basics of GLSL. 8 | 9 | ## Contents 10 | 11 | - [Why ThreeJS?](#why-threejs) 12 | - [Setup](#setup) 13 | - [Code Overview](#code-overview) 14 | - [Mesh & Shader Materials](#mesh--shader-materials) 15 | - [Step 1: Your First Shader](#step-1-your-first-shader) 16 | - [Attributes](#attributes) 17 | - [Uniforms](#uniforms) 18 | - [Projecting the Vertex](#projecting-the-vertex) 19 | - [Step 2: Distance from Center](#step-2-distance-from-center) 20 | - [Step 3: Visualizing Normals](#step-3-visualizing-normals) 21 | - [Step 4: Exploding Triangles](#step-4-exploding-triangles) 22 | - [Step 5: Animation](#step-5-animation) 23 | - [Appendix: ShaderMaterial vs RawShaderMaterial](#appendix-shadermaterial-vs-rawshadermaterial) 24 | 25 | ## Why ThreeJS? 26 | 27 | Writing raw WebGL involves a lot of boilerplate and adds a great deal of complexity. Instead, we will use ThreeJS to provide a layer of convenience and abstraction, and also to ease ourselves into more advanced ThreeJS lessons. 28 | 29 | This lesson will be easier if you already have some basic experience with ThreeJS Scene, Camera, WebGLRenderer etc. 30 | 31 | ## Setup 32 | 33 | You can download [ThreeJS](http://threejs.org/) directly from the site, but for our purposes we will use [npm](https://www.npmjs.com/) and [budo](https://github.com/mattdesl/budo/) for a faster development workflow. 34 | 35 | > :bulb: If you haven't used npm or budo, see our lesson [Modules for Frontend JavaScript](https://github.com/Jam3/jam3-lesson-module-basics) for more details. 36 | 37 | First, you should clone this repo and install the dependencies: 38 | 39 | ```sh 40 | git clone https://github.com/Jam3/jam3-lesson-webgl-shader-threejs.git 41 | 42 | # move into directory 43 | cd jam3-lesson-webgl-shader-threejs 44 | 45 | # install dependencies 46 | npm install 47 | 48 | # start dev server 49 | npm run start 50 | ``` 51 | 52 | Now you should be able to open [http://localhost:9966/](http://localhost:9966/) to see a white bunny. 53 | 54 | 55 | 56 | This project uses Babel through [babelify](https://github.com/babel/babelify) (a browserify transform). It also uses [brfs](https://github.com/substack/brfs), which can statically inline files like shaders. 57 | 58 | ## Code Overview 59 | 60 | The code is split into a few different files: 61 | 62 | - **[`./lib/index.js`](./lib/index.js)** 63 | This holds the "guts" of our demo, creating our application, geometry, mesh, and render loop. Since it is the root file, we use this to ensure the `THREE` global can be accessed from other files: 64 | ```js 65 | global.THREE = require('three'); 66 | ``` 67 | - **[`./lib/createApp.js`](./lib/createApp.js)** 68 | This boilerplate creates a basic ThreeJS application using a helper module, [orbit-controls](https://github.com/Jam3/orbit-controls), for 3D camera movement. 69 | 70 | You can review the code in this file, but it won't be essential to this lesson. 71 | 72 | - **[`./lib/createBunnyGeometry.js`](./lib/createBunnyGeometry.js)** 73 | This helper method creates a ThreeJS geometry from a *mesh primitive*, in this case a 3D bunny. 74 | 75 | For the purpose of this lesson, you can skip over this file. 76 | - **[`./lib/shader.frag`](./lib/shader.frag)** 77 | This is the fragment shader which you will be editing in this lesson. 78 | 79 | - **[`./lib/shader.vert`](./lib/shader.vert)** 80 | This is the fragment shader which you will be editing in this lesson. 81 | 82 | - **[`./lib/reference/`](./lib/reference/)** 83 | Here you can find some shader references for each step in this lesson. 84 | 85 | ## Mesh & Shader Materials 86 | 87 | In ThreeJS, a `Mesh` is the basic building block for rendering 3D shapes. It is made up of a `Geometry` (often re-used for memory) and a `Material`. For example, this code adds a red cube to the scene: 88 | 89 | ```js 90 | const geometry = new THREE.BoxGeometry(1, 1, 1); 91 | const material = new THREE.MeshBasicMaterial({ color: 'red' }); 92 | const mesh = new THREE.Mesh(geometry, material); 93 | scene.add(mesh); 94 | ``` 95 | 96 | In our [./lib/index.js](./lib/index.js), we have some similar code that sets up a 3D bunny geometry: 97 | 98 | ```js 99 | // Get a nicely prepared geometry 100 | const geometry = createBunnyGeometry({ flat: true }); 101 | ``` 102 | 103 | Next, we create a material for the bunny. 104 | 105 | We use a `RawShaderMaterial`, which is a special type of material that accepts `vertexShader` and `fragmentShader` strings. We are using [brfs](https://github.com/substack/brfs), a source transform, so that we can keep our shaders as separate files and inline them at bundle-time. 106 | 107 | ```js 108 | const path = require('path'); 109 | const fs = require('fs'); 110 | 111 | // Create our vertex/fragment shaders 112 | const material = new THREE.RawShaderMaterial({ 113 | vertexShader: fs.readFileSync(path.join(__dirname, 'shader.vert'), 'utf8'), 114 | fragmentShader: fs.readFileSync(path.join(__dirname, 'shader.frag'), 'utf8') 115 | }); 116 | ``` 117 | 118 | > :bulb: Later, you may wish to explore [glslify](https://github.com/stackgl/glslify) instead of `brfs`. 119 | 120 | Our bunny mesh is then created the same was as our box: 121 | 122 | ```js 123 | // Setup our mesh 124 | const mesh = new THREE.Mesh(geometry, material); 125 | scene.add(mesh); 126 | ``` 127 | 128 | ### Step 1: Your First Shader 129 | 130 | Reference: [shader.vert](./lib/shader.vert), [shader.frag](./lib/shader.frag) 131 | 132 | 133 | 134 | The initial shader provided with this repo just renders the 3D mesh with a flat white material. It's a good idea to always start with a basic shader before you dive into other effects. 135 | 136 | #### Vertex Shader 137 | 138 | The vertex shader at [`./lib/shader.vert`](./lib/shader.vert) looks like this: 139 | 140 | ```glsl 141 | attribute vec3 position; 142 | 143 | uniform mat4 projectionMatrix; 144 | uniform mat4 modelViewMatrix; 145 | 146 | void main () { 147 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 148 | } 149 | ``` 150 | 151 | Fragment and vertex shaders require a `main()` function — it gets called by WebGL for each vertex (or fragment) in our mesh. 152 | 153 | This vertex shader introduces two new concepts: **attributes** and **uniforms**. 154 | 155 | ##### Attributes 156 | 157 | A vertex shader runs on each *vertex* in a geometry. A vertex can contain various *attributes*, such as Position (XYZ), UV coordinates (XY), Normal (XYZ), etc. The attributes are set up in JavaScript, and end up as read-only inputs to our vertex shader. 158 | 159 | For example, the triangle below has 3 *vertices*, each vertex has one *attribute* that holds the XYZ position. For this triangle, the vertex shader would be executed 3 times by the GPU. 160 | 161 | 162 | 163 | Our bunny vertex shader only has one attribute for the vertex position. 164 | 165 | ```glsl 166 | attribute vec3 position; 167 | ``` 168 | 169 | ##### Uniforms 170 | 171 | Vertex and fragment shaders can also have *uniforms*, which is a constant value across all triangles (or fragments) in a render call. This value, set from JavaScript, is read-only in the vertex and fragment shaders. 172 | 173 | For example, we could use a `uniform` to define a constant RGB color for our mesh. 174 | 175 | ThreeJS provides a few built-in uniforms for shaders, such as the following 4x4 matrices, as `mat4` data types: 176 | 177 | - `projectionMatrix` — used to convert 3D world units into 2D screen-space. 178 | - `viewMatrix` — an inverse of our `PerspectiveCamera`'s world matrix 179 | - `modelMatrix` — the model-space matrix of our `Mesh` 180 | - `modelViewMatrix` — a combination of the view and model matrix 181 | 182 | > :bulb: If you aren't familiar with matrices, don't fret! We plan to cover the basics of vector math in a later lesson. 183 | 184 | If you use one of these, you will need to define it like so: 185 | 186 | ```glsl 187 | uniform mat4 projectionMatrix; 188 | uniform mat4 modelViewMatrix; 189 | ``` 190 | 191 | #### Projecting The Vertex 192 | 193 | The role of the vertex shader is to turn our 3D data (e.g. position) into features that WebGL's rasterizer can use to fill our shapes on a 2D screen. This is known as "projecting" 3D world coordinates into 2D screen-space coordinates. 194 | 195 | To do this, we use the following pattern: 196 | 197 | ```glsl 198 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 199 | ``` 200 | 201 | > :bulb: It's not yet necessary to understand why `1.0` is used as the `w` component in position. If you are curious, you can read more [here](http://www.tomdalling.com/blog/modern-opengl/explaining-homogenous-coordinates-and-projective-geometry/). 202 | 203 | The above could also be written a little differently, by specifying `vec4` for the attribute (WebGL will expand vectors with `w=1.0` by default) and multiplying each matrix independently. 204 | 205 | ```glsl 206 | attribute vec4 position; 207 | 208 | uniform mat4 projectionMatrix; 209 | uniform mat4 modelMatrix; 210 | uniform mat4 viewMatrix; 211 | 212 | void main () { 213 | gl_Position = projectionMatrix * viewMatrix * modelMatrix * position; 214 | } 215 | ``` 216 | 217 | #### Fragment Shader 218 | 219 | If you open up [`./lib/shader.frag`](./lib/shader.frag), you'll see the fragment shader: 220 | 221 | ```glsl 222 | precision highp float; 223 | 224 | void main () { 225 | gl_FragColor = vec4(1.0); 226 | } 227 | ``` 228 | 229 | The first line, with `precision`, defines the floating point precision the GPU should use by default for all floats. When using `RawShaderMaterial`, you will need to specify this at the top of your fragment shaders. You can use `lowp`, `mediump`, or `highp`, but typically `highp` is recommended. 230 | 231 | > :bulb: The default precision for vertex shaders is `highp`, so we didn't need to define it earlier. 232 | 233 | Then, we have `gl_FragColor`, which is a builtin write-only `vec4` for the output color. You should always write all 4 channels to this in your fragment shader. In our case, we are using pure white: `(1.0, 1.0, 1.0, 1.0)`. This is similar to [ShaderToy's](http://shadertoy.com/) `fragColor` variable. 234 | 235 | ## Step 2: Distance from Center 236 | 237 | Reference: [shader2.vert](./lib/reference/shader2.vert), [shader2.frag](./lib/reference/shader2.frag) 238 | 239 | 240 | 241 | Next, we'll apply a radial gradient to our mesh by coloring each pixel based on its XYZ distance from world center, `(0, 0, 0)`. 242 | 243 | You can edit your `shader.vert` and `shader.frag` to see the new changes locally. 244 | 245 | ### Vertex Shader 246 | 247 | In this step, our vertex shader looks like this: 248 | 249 | ```glsl 250 | attribute vec4 position; 251 | 252 | uniform mat4 projectionMatrix; 253 | uniform mat4 modelViewMatrix; 254 | 255 | varying float distFromCenter; 256 | 257 | void main () { 258 | distFromCenter = length(position.xyz); 259 | gl_Position = projectionMatrix * modelViewMatrix * position; 260 | } 261 | ``` 262 | 263 | This shader introduces a new concept: **varyings**. This is a write-only value that will get passed down the pipeline to the fragment shader. Here, we take the magnitude of the `position` vector, i.e. compute its distance from origin `vec3(0.0)`. 264 | 265 | > :bulb: You can also use the `distance` built-in, like so: 266 | ```glsl 267 | distFromCenter = distance(position.xyz, vec3(0.0)); 268 | ``` 269 | 270 | ### Fragment Shader 271 | 272 | ```glsl 273 | precision highp float; 274 | 275 | varying float distFromCenter; 276 | void main () { 277 | gl_FragColor = vec4(vec3(distFromCenter), 1.0); 278 | } 279 | ``` 280 | 281 | In a fragment shader, varyings are read-only. They are inputs from the vertex shader. Their values are interpolated between vertices, so if you have a `0.0` float coming from one vertex, and `1.0` coming from another, each fragment will end up with some values in-between. 282 | 283 | In our case, the fragment RGB color is set to the `distFromCenter`, which gives us values that are black near the world origin, and more white as the vertices move away from this point. 284 | 285 | 286 | ## Step 3: Visualizing Normals 287 | 288 | Reference: [shader3.vert](./lib/reference/shader3.vert), [shader3.frag](./lib/reference/shader3.frag) 289 | 290 | 291 | 292 | In this step, we visualize the *normals* of the mesh. This is a new attribute, already specified in `createBunnyGeometry.js`, that defines the way each triangle points away from the center. Normals are often used in surface lighting and other effects. 293 | 294 | A normal is a unit vector, which means its components are normalized to the range -1.0 to 1.0. 295 | 296 | ### Vertex Shader 297 | 298 | ```glsl 299 | attribute vec4 position; 300 | attribute vec3 normal; 301 | 302 | uniform mat4 projectionMatrix; 303 | uniform mat4 modelViewMatrix; 304 | 305 | varying vec3 vNormal; 306 | 307 | void main () { 308 | vNormal = normal; 309 | gl_Position = projectionMatrix * modelViewMatrix * position; 310 | } 311 | ``` 312 | 313 | In this shader, we include a new attribute, `normal`, and pass it along to the fragment shader with the `vNormal` varying. 314 | 315 | ### Fragment Shader 316 | 317 | ```glsl 318 | precision highp float; 319 | 320 | varying vec3 vNormal; 321 | 322 | void main () { 323 | gl_FragColor = vec4(vNormal, 1.0); 324 | } 325 | ``` 326 | 327 | Here, we simply render the passed `vNormal` for each fragment. It ends up looking nice, even though some values will be clamped because they are less than 0.0. 328 | 329 | > :bulb: The geometry in `index.js` is created with `{ flat: true }`, which means the normals should be separated. You can try toggling that to see how it looks with combined (smooth) normals. 330 | 331 | ## Step 4: Exploding Triangles 332 | 333 | Reference: [shader4.vert](./lib/reference/shader4.vert), [shader4.frag](./lib/reference/shader4.frag) 334 | 335 | 336 | 337 | Now, let's push each triangle along its face normal to create an explosion effect! :fire: 338 | 339 | ### Vertex Shader 340 | 341 | ```glsl 342 | attribute vec4 position; 343 | attribute vec3 normal; 344 | 345 | uniform mat4 projectionMatrix; 346 | uniform mat4 modelViewMatrix; 347 | 348 | varying vec3 vNormal; 349 | 350 | void main () { 351 | vNormal = normal; 352 | 353 | vec4 offset = position; 354 | float dist = 0.25; 355 | offset.xyz += normal * dist; 356 | gl_Position = projectionMatrix * modelViewMatrix * offset; 357 | } 358 | ``` 359 | 360 | In this shader, we offset each vertex position by a vector. In this case, we use the `normal` to know which direction the position should be offset, and a `dist` scalar to determine how far away the triangles should be pushed. 361 | 362 | We can leave the fragment shader unchanged. 363 | 364 | ## Step 5: Animation 365 | 366 | Reference: [shader5.vert](./lib/reference/shader5.vert), [shader5.frag](./lib/reference/shader5.frag) 367 | 368 | 369 | 370 | Lastly, we'll animate the explosion by adding a uniform to the shader material. 371 | 372 | This needs to be set up from JavaScript, so make sure our `index.js` defines a `uniforms` option in the material: 373 | 374 | ```js 375 | // Create our vertex/fragment shaders 376 | const material = new THREE.RawShaderMaterial({ 377 | vertexShader: fs.readFileSync(path.join(__dirname, 'shader.vert'), 'utf8'), 378 | fragmentShader: fs.readFileSync(path.join(__dirname, 'shader.frag'), 'utf8'), 379 | uniforms: { 380 | time: { type: 'f', value: 0 } 381 | } 382 | }); 383 | ``` 384 | 385 | Above, we define a `time` uniform (which will be accessed by that name in the shader), give it a float type (ThreeJS uses `'f'` — see [here](https://github.com/mrdoob/three.js/wiki/Uniforms-types) for others), and provide a default value. 386 | 387 | Then, our render loop increments the value every frame, and changes the `value` field of our `time` uniform. 388 | 389 | ```js 390 | // Time since beginning 391 | let time = 0; 392 | 393 | // Start our render loop 394 | createLoop((dt) => { 395 | // update time 396 | time += dt / 1000; 397 | 398 | // set value 399 | material.uniforms.time.value = time; 400 | 401 | // render 402 | ... 403 | }).start(); 404 | ``` 405 | 406 | ### Vertex Shader 407 | 408 | ```glsl 409 | attribute vec4 position; 410 | attribute vec3 normal; 411 | 412 | uniform mat4 projectionMatrix; 413 | uniform mat4 modelViewMatrix; 414 | 415 | uniform float time; 416 | 417 | varying vec3 vNormal; 418 | 419 | void main () { 420 | vNormal = normal; 421 | 422 | vec4 offset = position; 423 | 424 | // Animate between 0 and 1 425 | // sin(x) returns a value in [-1...1] range 426 | float dist = sin(time) * 0.5 + 0.5; 427 | 428 | offset.xyz += normal * dist; 429 | gl_Position = projectionMatrix * modelViewMatrix * offset; 430 | } 431 | ``` 432 | 433 | In this shader, we first define our uniform before our main function. Since it's a uniform, it will be the same value for all vertices in that render call. 434 | 435 | ```glsl 436 | uniform float time; 437 | ``` 438 | 439 | We can use the built-in `sin()` function (equivalent to `Math.sin` in JavaScript), which gives us back a value from -1.0 to 1.0. Then we normalize it into 0 to 1 range, so that our mesh is always exploding outward: 440 | 441 | ```glsl 442 | float dist = sin(time) * 0.5 + 0.5; 443 | ``` 444 | 445 | Voilà! We have an exploding bunny! :rabbit: 446 | 447 | ## Appendix: ShaderMaterial vs RawShaderMaterial 448 | 449 | At some point you may wonder why ThreeJS has both `ShaderMaterial` and `RawShaderMaterial`. Typically I suggest using `RawShaderMaterial` since it is less error-prone, but it means you have to be a bit more verbose and manually specify precision, extensions, etc. 450 | 451 | Instead, you can use `ShaderMaterial` and skip some definitions, such as ThreeJS's built-in attributes, uniforms and fragment shader precision: 452 | 453 | ```glsl 454 | void main () { 455 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xyz, 1.0); 456 | } 457 | ``` 458 | 459 | ## Next Steps 460 | 461 | Stay tuned for future lessons on ThreeJS shaders, BufferGeometry, custom attributes, and more! -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jam3-lesson-webgl-shader-threejs 7 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-webgl-shader-threejs/17c000b750392f678e4ccf1af0c086878a85ff52/images/1.png -------------------------------------------------------------------------------- /images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-webgl-shader-threejs/17c000b750392f678e4ccf1af0c086878a85ff52/images/2.png -------------------------------------------------------------------------------- /images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-webgl-shader-threejs/17c000b750392f678e4ccf1af0c086878a85ff52/images/3.png -------------------------------------------------------------------------------- /images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-webgl-shader-threejs/17c000b750392f678e4ccf1af0c086878a85ff52/images/4.png -------------------------------------------------------------------------------- /images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-webgl-shader-threejs/17c000b750392f678e4ccf1af0c086878a85ff52/images/5.png -------------------------------------------------------------------------------- /images/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-webgl-shader-threejs/17c000b750392f678e4ccf1af0c086878a85ff52/images/triangle.png -------------------------------------------------------------------------------- /lib/createApp.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is a generic "ThreeJS Application" 3 | helper which sets up a renderer and camera 4 | controls. 5 | */ 6 | 7 | const createControls = require('orbit-controls'); 8 | const assign = require('object-assign'); 9 | 10 | module.exports = createApp; 11 | function createApp (opt = {}) { 12 | // Scale for retina 13 | const dpr = window.devicePixelRatio; 14 | 15 | // Our WebGL renderer with alpha and device-scaled 16 | const renderer = new THREE.WebGLRenderer(assign({ 17 | antialias: true // default enabled 18 | }, opt)); 19 | renderer.setPixelRatio(dpr); 20 | 21 | // Add the to DOM body 22 | const canvas = renderer.domElement; 23 | document.body.appendChild(canvas); 24 | 25 | // perspective camera 26 | const near = 0.01; 27 | const far = 1000; 28 | const fieldOfView = 65; 29 | const camera = new THREE.PerspectiveCamera(fieldOfView, 1, near, far); 30 | const target = new THREE.Vector3(); 31 | 32 | // 3D scene 33 | const scene = new THREE.Scene(); 34 | 35 | // slick 3D orbit controller with damping 36 | const controls = createControls(assign({ 37 | canvas, 38 | distanceBounds: [ 1, 10 ], 39 | distance: 2.5, 40 | phi: 70 * Math.PI / 180 41 | }, opt)); 42 | 43 | // Update renderer size 44 | window.addEventListener('resize', resize); 45 | 46 | // Setup initial size & aspect ratio 47 | resize(); 48 | 49 | return { 50 | updateControls, 51 | camera, 52 | scene, 53 | renderer, 54 | controls, 55 | canvas 56 | }; 57 | 58 | function updateControls () { 59 | const width = window.innerWidth; 60 | const height = window.innerHeight; 61 | const aspect = width / height; 62 | 63 | // update camera controls 64 | controls.update(); 65 | camera.position.fromArray(controls.position); 66 | camera.up.fromArray(controls.up); 67 | target.fromArray(controls.direction).add(camera.position); 68 | camera.lookAt(target); 69 | 70 | // Update camera matrices 71 | camera.aspect = aspect; 72 | camera.updateProjectionMatrix(); 73 | } 74 | 75 | function resize () { 76 | renderer.setSize(window.innerWidth, window.innerHeight); 77 | updateControls(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/createBunnyGeometry.js: -------------------------------------------------------------------------------- 1 | // Simple 3D geometry 2 | const data = require('three-buffer-vertex-data'); 3 | const unindex = require('unindex-mesh'); 4 | 5 | // grab a simplicial complex 6 | const primitive = require('bunny'); 7 | 8 | module.exports = function (opt = {}) { 9 | // Set up our geometry 10 | const geometry = new THREE.BufferGeometry(); 11 | 12 | // Sharing normals gives us a smooth look, splitting 13 | // them gives us a faceted look 14 | if (opt.flat) { 15 | data.attr(geometry, 'position', unindex(primitive)); 16 | } else { 17 | data.index(geometry, primitive.cells); 18 | data.attr(geometry, 'position', primitive.positions); 19 | } 20 | 21 | // This is a ThreeJS utility to position the vertices 22 | // into world center [ 0, 0, 0 ] 23 | geometry.center(); 24 | 25 | // Another utility to scale all the vertices 26 | geometry.scale(0.2, 0.2, 0.2); 27 | 28 | // Now compute a normal for each vertex 29 | // This will add a new attribute called 'normal' 30 | geometry.computeVertexNormals(); 31 | 32 | return geometry; 33 | }; 34 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | global.THREE = require('three'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const createApp = require('./createApp'); 5 | const createBunnyGeometry = require('./createBunnyGeometry'); 6 | const createLoop = require('raf-loop'); 7 | 8 | // Create our basic ThreeJS application 9 | const { 10 | renderer, 11 | camera, 12 | scene, 13 | updateControls 14 | } = createApp(); 15 | 16 | // Get a nicely prepared geometry 17 | const geometry = createBunnyGeometry({ flat: true }); 18 | 19 | // Create our vertex/fragment shaders 20 | const material = new THREE.RawShaderMaterial({ 21 | vertexShader: fs.readFileSync(path.join(__dirname, 'shader.vert'), 'utf8'), 22 | fragmentShader: fs.readFileSync(path.join(__dirname, 'shader.frag'), 'utf8'), 23 | uniforms: { 24 | time: { type: 'f', value: 0 } 25 | } 26 | }); 27 | 28 | // Setup our mesh 29 | const mesh = new THREE.Mesh(geometry, material); 30 | scene.add(mesh); 31 | 32 | // Time since beginning 33 | let time = 0; 34 | 35 | // Start our render loop 36 | createLoop((dt) => { 37 | // update time 38 | time += dt / 1000; 39 | material.uniforms.time.value = time; 40 | 41 | // render 42 | updateControls(); 43 | renderer.render(scene, camera); 44 | }).start(); 45 | -------------------------------------------------------------------------------- /lib/reference/shader1.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | void main () { 4 | gl_FragColor = vec4(1.0); 5 | } -------------------------------------------------------------------------------- /lib/reference/shader1.vert: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | uniform mat4 projectionMatrix; 4 | uniform mat4 modelViewMatrix; 5 | 6 | void main () { 7 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 8 | } -------------------------------------------------------------------------------- /lib/reference/shader2.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying float distFromCenter; 4 | void main () { 5 | gl_FragColor = vec4(vec3(distFromCenter), 1.0); 6 | } -------------------------------------------------------------------------------- /lib/reference/shader2.vert: -------------------------------------------------------------------------------- 1 | attribute vec4 position; 2 | 3 | uniform mat4 projectionMatrix; 4 | uniform mat4 modelViewMatrix; 5 | 6 | varying float distFromCenter; 7 | 8 | void main () { 9 | distFromCenter = length(position.xyz); 10 | 11 | // Can also be written as: 12 | // distFromCenter = distance(position.xyz, vec3(0.0)); 13 | 14 | gl_Position = projectionMatrix * modelViewMatrix * position; 15 | } -------------------------------------------------------------------------------- /lib/reference/shader3.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying vec3 vNormal; 4 | 5 | void main () { 6 | gl_FragColor = vec4(vNormal, 1.0); 7 | } -------------------------------------------------------------------------------- /lib/reference/shader3.vert: -------------------------------------------------------------------------------- 1 | attribute vec4 position; 2 | attribute vec3 normal; 3 | 4 | uniform mat4 projectionMatrix; 5 | uniform mat4 modelViewMatrix; 6 | 7 | varying vec3 vNormal; 8 | 9 | void main () { 10 | vNormal = normal; 11 | gl_Position = projectionMatrix * modelViewMatrix * position; 12 | } -------------------------------------------------------------------------------- /lib/reference/shader4.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying vec3 vNormal; 4 | 5 | void main () { 6 | gl_FragColor = vec4(vNormal, 1.0); 7 | } -------------------------------------------------------------------------------- /lib/reference/shader4.vert: -------------------------------------------------------------------------------- 1 | attribute vec4 position; 2 | attribute vec3 normal; 3 | 4 | uniform mat4 projectionMatrix; 5 | uniform mat4 modelViewMatrix; 6 | 7 | varying vec3 vNormal; 8 | 9 | void main () { 10 | vNormal = normal; 11 | 12 | vec4 offset = position; 13 | float dist = 0.25; 14 | offset.xyz += normal * dist; 15 | gl_Position = projectionMatrix * modelViewMatrix * offset; 16 | } -------------------------------------------------------------------------------- /lib/reference/shader5.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying vec3 vNormal; 4 | 5 | void main () { 6 | gl_FragColor = vec4(vNormal, 1.0); 7 | } -------------------------------------------------------------------------------- /lib/reference/shader5.vert: -------------------------------------------------------------------------------- 1 | attribute vec4 position; 2 | attribute vec3 normal; 3 | 4 | uniform mat4 projectionMatrix; 5 | uniform mat4 modelViewMatrix; 6 | 7 | uniform float time; 8 | 9 | varying vec3 vNormal; 10 | 11 | void main () { 12 | vNormal = normal; 13 | 14 | vec4 offset = position; 15 | 16 | // Animate between 0 and 1 17 | // sin(x) returns a value in [-1...1] range 18 | float dist = sin(time) * 0.5 + 0.5; 19 | 20 | // CHEAT SHEET 21 | // ------------------- 22 | // [0..1] to [-1..1] a = (b * 2.0 - 1.0) 23 | // [-1..1] to [0..1] a = (b * 0.5 + 0.5) 24 | 25 | offset.xyz += normal * dist; 26 | gl_Position = projectionMatrix * modelViewMatrix * offset; 27 | } -------------------------------------------------------------------------------- /lib/shader.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | void main () { 4 | gl_FragColor = vec4(1.0); 5 | } -------------------------------------------------------------------------------- /lib/shader.vert: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | uniform mat4 projectionMatrix; 4 | uniform mat4 modelViewMatrix; 5 | 6 | void main () { 7 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jam3-lesson-webgl-shader-threejs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "author": { 8 | "name": "Matt DesLauriers", 9 | "email": "dave.des@gmail.com", 10 | "url": "https://github.com/mattdesl" 11 | }, 12 | "semistandard": { 13 | "globals": [ 14 | "THREE" 15 | ] 16 | }, 17 | "dependencies": { 18 | "bunny": "^1.0.1", 19 | "object-assign": "^4.1.0", 20 | "orbit-controls": "^1.1.1", 21 | "raf-loop": "^1.1.3", 22 | "three": "^0.81.2", 23 | "three-buffer-vertex-data": "^1.0.2", 24 | "unindex-mesh": "^2.0.0" 25 | }, 26 | "devDependencies": { 27 | "babel-preset-es2015": "^6.14.0", 28 | "babelify": "^7.3.0", 29 | "brfs": "^1.4.3", 30 | "budo": "^9.2.0" 31 | }, 32 | "scripts": { 33 | "test": "node test.js", 34 | "start": "budo lib/index.js:bundle.js --live --dir app -- -t babelify -t brfs" 35 | }, 36 | "keywords": [], 37 | "repository": { 38 | "type": "git", 39 | "url": "git://github.com/Jam3/jam3-lesson-webgl-shader-threejs.git" 40 | }, 41 | "homepage": "https://github.com/Jam3/jam3-lesson-webgl-shader-threejs", 42 | "bugs": { 43 | "url": "https://github.com/Jam3/jam3-lesson-webgl-shader-threejs/issues" 44 | } 45 | } 46 | --------------------------------------------------------------------------------