├── .gitignore
├── LICENSE
├── README.md
├── exercises.json
├── exercises
├── _gpgpu-1
│ ├── README.md
│ ├── files
│ │ └── render.frag
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── display.frag
│ │ ├── expected.frag
│ │ └── triangle.vert
├── _gpgpu-3
│ ├── README.md
│ ├── files
│ │ ├── logic.frag
│ │ └── render.frag
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── logic_solution.frag
│ │ ├── render_solution.frag
│ │ └── triangle.vert
├── _lesson-template
│ ├── README.md
│ ├── files
│ │ └── triangle.frag
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── triangle.frag
│ │ └── triangle.vert
├── _macros
│ └── README.md
├── _point-1
│ ├── README.md
│ ├── files
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── common.js
├── frag-1
│ ├── README.md
│ ├── files
│ │ └── fragment.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── frag-2
│ ├── README.md
│ ├── files
│ │ └── fragment.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── frag-3
│ ├── README.md
│ ├── files
│ │ └── fragment.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── geom-1
│ ├── README.md
│ ├── files
│ │ └── transforms.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── geom-2
│ ├── README.md
│ ├── files
│ │ └── translate.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── expected.glsl
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── geom-3
│ ├── README.md
│ ├── files
│ │ └── scale.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── expected.glsl
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── geom-4
│ ├── README.md
│ ├── files
│ │ └── reflect.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── expected.glsl
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── geom-5
│ ├── README.md
│ ├── files
│ │ └── rotate.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── expected.glsl
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── gpgpu-1
│ ├── README.md
│ ├── files
│ │ └── life.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── logic_solution.frag
│ │ ├── render.frag
│ │ └── triangle.vert
├── gpgpu-2
│ ├── README.md
│ ├── files
│ │ └── heat.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── pass-thru.glsl
│ │ ├── point-fragment.glsl
│ │ ├── point-vertex.glsl
│ │ ├── render.glsl
│ │ └── update.glsl
├── gpgpu-3
│ ├── README.md
│ ├── files
│ │ └── wave.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── pass-thru.glsl
│ │ ├── point-fragment.glsl
│ │ ├── point-vertex.glsl
│ │ ├── render.glsl
│ │ └── update.glsl
├── intro-0
│ ├── README.md
│ ├── files
│ │ └── hello.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── intro-1
│ ├── README.md
│ ├── files
│ │ └── hello.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── intro-2
│ ├── README.md
│ ├── files
│ │ └── sides.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── expected.glsl
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── intro-3
│ ├── README.md
│ ├── files
│ │ └── vectors.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── expected.glsl
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── intro-4
│ ├── README.md
│ ├── files
│ │ └── box.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── intro-5
│ ├── README.md
│ ├── files
│ │ └── mandelbrot.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── intro-6
│ ├── README.md
│ ├── files
│ │ └── mpow.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── expected.glsl
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── lesson-plan.md
├── light-1
│ ├── README.md
│ ├── files
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── light-2
│ ├── README.md
│ ├── files
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── light-3
│ ├── README.md
│ ├── files
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── light-4
│ ├── README.md
│ ├── files
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ ├── point-fragment.glsl
│ │ ├── point-vertex.glsl
│ │ └── vertex.glsl
├── light-5
│ ├── README.md
│ ├── files
│ │ ├── fragment.glsl
│ │ ├── light.glsl
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ ├── light.glsl
│ │ ├── point-fragment.glsl
│ │ ├── point-vertex.glsl
│ │ └── vertex.glsl
├── npr-1
│ ├── README.md
│ ├── files
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── npr-2
│ ├── README.md
│ ├── files
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── playground-flocking
│ ├── README.md
│ ├── files
│ │ ├── position.glsl
│ │ ├── render.frag
│ │ ├── render.vert
│ │ ├── speed.glsl
│ │ └── triangle.glsl
│ ├── index.html
│ ├── index.js
│ └── server.js
├── playground-gpgpu
│ ├── README.md
│ ├── files
│ │ ├── render.glsl
│ │ └── update.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ └── pass-thru.glsl
├── prims-1
│ ├── README.md
│ ├── files
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── prims-2
│ ├── README.md
│ ├── files
│ │ └── fragment.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
├── vert-1
│ ├── README.md
│ ├── files
│ │ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
└── vert-2
│ ├── README.md
│ ├── files
│ ├── fragment.glsl
│ └── vertex.glsl
│ ├── index.html
│ ├── index.js
│ ├── server.js
│ └── shaders
│ ├── fragment.glsl
│ └── vertex.glsl
├── index.js
├── intro.txt
├── lib
├── close-window.html
├── create-answers.js
├── description.js
├── diff-ui.html
├── diff-ui.js
├── exercise-map.js
├── match-fbo.js
└── progress.js
├── menu
├── index.html
└── index.js
├── package.json
├── postpack.js
├── prepack.js
├── start.js
└── style
├── index.css
├── index.js
└── reset.css
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | answers
3 | npm-debug.log
4 | workshop.tar.gz
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2013 Mikola Lysenko, Hugh Kennedy and Chris Dickinson
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
24 | The following icons included directly within the project are kindly licensed
25 | under Creative Commons Attribution:
26 |
27 | * Home by Edward Boatman from The Noun Project
28 | * Folder by Sergio Calcara from The Noun Project
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shader School
2 |
3 | [](https://nodei.co/npm/shader-school/)
4 | [](https://nodei.co/npm/shader-school/)
5 |
6 | **An introduction to GLSL shaders and graphics programming that runs in your web browser.**
7 |
8 | 
9 |
10 | ## Running this thing
11 |
12 | First, you need to [get a browser with WebGL](http://get.webgl.org/), as well
13 | as a copy of [node.js](http://nodejs.org/) and [git](http://git-scm.com/). Once you have
14 | all of that set up, you can install the workshop using [npm](http://npmjs.org/), which
15 | is included with node:
16 |
17 | ```
18 | npm install -g shader-school
19 | ```
20 |
21 | After that completes, you should be able to run the workshopper with the command:
22 |
23 | ```
24 | shader-school
25 | ```
26 |
27 | The script will ask you if you want to create an answer directory, press `y` to
28 | accept. This will populate your current directory with shader files for you to
29 | edit for each lesson – hopefully, it should also automatically open your web
30 | browser but if it doesn't you can find the workshop menu on
31 | [http://localhost:12492/](http://localhost:12492/).
32 |
33 | ## Stuck?
34 |
35 | Feedback and criticism is welcome, please log your troubles in
36 | [issues](https://github.com/gl-modules/shader-school/issues). The workshop is
37 | still being worked on but is very close to being complete!
38 |
39 | Full curriculum reviews
40 | [like this one](https://github.com/timoxley/functional-javascript-workshop/issues/7)
41 | are very helpful. More feedback like this please!
42 |
43 | ## Contributors
44 |
45 |
50 |
51 | ## Color Scheme
52 |
53 | 
54 |
55 | From left to right:
56 |
57 | * `#34363B` `vec3(0.2039, 0.2117, 0.2313)` (black)
58 | * `#A9B0C3` `vec3(0.5372, 0.6901, 0.7647)` (dark grey)
59 | * `#DEE7FF` `vec3(0.8705, 0.9058, 1.0)` (light grey)
60 | * `#FFFFFF` `vec3(1.0, 1.0, 1.0)` (white)
61 | * `#58FF8B` `vec3(0.3451, 1.0, 0.5450)` (green)
62 | * `#FF6E57` `vec3(1.0, 0.4313, 0.3411)` (red)
63 | * `#FFE25F` `vec3(1.0, 0.8862, 0.3725)` (yellow)
64 | * `#61C3FF` `vec3(0.3804, 0.7647, 1.0)` (blue)
65 |
66 | ## Screenshots
67 |
68 | 
69 |
70 | 
71 |
72 | 
73 |
74 | 
75 |
76 | 
77 |
--------------------------------------------------------------------------------
/exercises.json:
--------------------------------------------------------------------------------
1 | {
2 | "» GETTING STARTED: HELLO, GLSL": "intro-0",
3 | "» GETTING STARTED: GLSL SYNTAX": "intro-1",
4 | "» GETTING STARTED: QUALIFIERS AND BUILTINS": "intro-2",
5 | "» GETTING STARTED: VECTORS": "intro-3",
6 | "» GETTING STARTED: BRANCHING": "intro-4",
7 | "» GETTING STARTED: LOOPS": "intro-5",
8 | "» GETTING STARTED: MATRICES": "intro-6",
9 | "» FRAGMENT SHADERS: THE BASICS": "frag-1",
10 | "» FRAGMENT SHADERS: DISCARD": "frag-2",
11 | "» FRAGMENT SHADERS: UNIFORMS AND TEXTURES": "frag-3",
12 | "» VERTEX SHADERS: THE BASICS": "vert-1",
13 | "» VERTEX SHADERS: VARYING VARIABLES": "vert-2",
14 | "» GEOMETRY: CLIP COORDINATES": "geom-1",
15 | "» GEOMETRY: TRANSLATIONS": "geom-2",
16 | "» GEOMETRY: SCALING": "geom-3",
17 | "» GEOMETRY: REFLECTIONS": "geom-4",
18 | "» GEOMETRY: ROTATIONS": "geom-5",
19 | "» LIGHTING: FLAT SHADING": "light-1",
20 | "» LIGHTING: DIFFUSE SHADING": "light-2",
21 | "» LIGHTING: PHONG SHADING": "light-3",
22 | "» LIGHTING: POINT LIGHTS": "light-4",
23 | "» LIGHTING: MULTIPLE LIGHTS": "light-5",
24 | "» NON-PHOTOREALISTIC RENDERING: CEL SHADING": "npr-1",
25 | "» NON-PHOTOREALISTIC RENDERING: GOOCH SHADING": "npr-2",
26 | "» GPGPU: GAME OF LIFE": "gpgpu-1",
27 | "» GPGPU: HEAT EQUATION": "gpgpu-2",
28 | "» GPGPU: WAVE EQUATION": "gpgpu-3",
29 | "» PRIMITIVES: POINT SPRITES": "prims-1",
30 | "» PRIMITIVES: TRIANGLES": "prims-2",
31 | "» PLAYGROUND: FLOCKING": "playground-flocking",
32 | "» PLAYGROUND: GPGPU": "playground-gpgpu"
33 | }
34 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-1/README.md:
--------------------------------------------------------------------------------
1 | # GPGPU: Texture Feedback
2 |
3 | *in progress*
4 |
5 | Your graphics card might have been designed for handling images, but it's
6 | useful for solving certain problems *really* quickly too. Shaders on the GPU
7 | run in *parallel*, and it's considerably better at handling thousands of small
8 | independent tasks when pitted against a CPU. So if you're willing to work
9 | around the specific restrictions of parallel computing, you can use the GPU
10 | for general computation too.
11 |
12 | This approach has been generalised to standards and platforms such as
13 | [OpenCL](https://www.khronos.org/opencl/) and
14 | [CUDA](http://www.nvidia.com/object/cuda_home_new.html). Unfortunately WebGL
15 | doesn't have its own equivalent [(yet)](http://en.wikipedia.org/wiki/WebCL), so
16 | for now we'll be using the classic
17 | [ping pong technique](http://www.seas.upenn.edu/~cis565/fbo.htm#feedback2),
18 | or texture feedback loop.
19 |
20 | ## FBO Ping-Pong
21 |
22 | To create a texture feedback loop, we simply need to draw each frame using
23 | the previous frame as input.
24 |
25 | One restriction we have to work with is that when we're drawing to a framebuffer
26 | we can't use that same framebuffer as input to the texture. To work around this
27 | issue, we use two FBOs – one for rendering to, and one for reading from – and
28 | switch them every frame.
29 |
30 |
31 |
32 | A simple example would be:
33 |
34 | ``` javascript
35 | var shader = require('./shaders/feedback-loop')
36 | var frame1 = createFBO(gl)
37 | var frame2 = createFBO(gl)
38 |
39 | function render() {
40 | // Bind to our first FBO, and use the second one as
41 | // an input texture to the shader:
42 | frame1.bind()
43 | shader.uniforms.previous = frame2.color[0].bind()
44 |
45 | // Draw the scene to frame1:
46 | drawFrame()
47 |
48 | // Switch frame1 and frame2:
49 | var tmp = frame2
50 | frame2 = frame1
51 | frame1 = tmp
52 | }
53 | ```
54 |
55 | The end result is equivalent to creating a new framebuffer to draw to every
56 | frame, and retaining the previous one for input. However this way we can avoid
57 | the overhead in creating a new FBO every frame, which would be very expensive.
58 |
59 | ## A Simple Example
60 |
61 | In this [exercise's directory](/open/gpgpu-1) you'll find a file called
62 | `render.frag` that handles the general setup of an FBO ping pong shader. Update
63 | it according to the instructions there to generate the expected output.
64 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-1/files/render.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D uTexture;
4 | uniform vec2 uMouse;
5 | varying vec2 vUv;
6 |
7 | // Each frame, draw a white circle around the mouse that
8 | // has a radius of 20 pixels. Everywhere else, fade
9 | // to black as slowly as possible.
10 | vec3 update(vec3 color) {
11 | vec2 pixelPosition = gl_FragCoord.xy;
12 | vec2 mousePosition = uMouse;
13 |
14 | return color;
15 | }
16 |
17 | void main() {
18 | vec3 original = texture2D(uTexture, vUv).rgb;
19 |
20 | gl_FragColor.rgb = update(original);
21 | gl_FragColor.a = 1.0;
22 | }
23 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: lesson template
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-1/index.js:
--------------------------------------------------------------------------------
1 | var mouse = require('mouse-position')()
2 | var triangle = require('a-big-triangle')
3 | var throttle = require('frame-debounce')
4 | var fit = require('canvas-fit')
5 | var getContext = require('gl-context')
6 | var compare = require('gl-compare')
7 | var createShader = require('glslify')
8 | var createFBO = require('gl-fbo')
9 | var fs = require('fs')
10 |
11 | var container = document.getElementById('container')
12 | var canvas = container.appendChild(document.createElement('canvas'))
13 | var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
14 | var gl = getContext(canvas, render)
15 | var comparison = compare(gl
16 | , createLoop('actual')
17 | , createLoop('expected')
18 | )
19 |
20 | comparison.mode = 'slide'
21 | comparison.amount = 0.5
22 |
23 | require('../common')({
24 | description: readme
25 | , compare: comparison
26 | , canvas: canvas
27 | , dirname: process.env.dirname
28 | })
29 |
30 | window.addEventListener('resize', fit(canvas), false)
31 |
32 | function render() {
33 | comparison.run()
34 | comparison.render()
35 | }
36 |
37 | var shaders = {
38 | actual: createShader({
39 | frag: process.env.file_render_frag
40 | , vert: './shaders/triangle.vert'
41 | })(gl),
42 | expected: createShader({
43 | frag: './shaders/expected.frag'
44 | , vert: './shaders/triangle.vert'
45 | })(gl),
46 | display: createShader({
47 | frag: './shaders/display.frag'
48 | , vert: './shaders/triangle.vert'
49 | })(gl)
50 | }
51 |
52 | var outputs = {
53 | actual: createFBO(gl, [512, 512])
54 | , expected: createFBO(gl, [512, 512])
55 | }
56 |
57 | var inputs = {
58 | actual: createFBO(gl, [512, 512])
59 | , expected: createFBO(gl, [512, 512])
60 | }
61 |
62 | function createLoop(key) {
63 | return function render(fbo) {
64 | outputs[key].shape = [canvas.height, canvas.width]
65 | outputs[key].bind()
66 | shaders[key].bind()
67 | shaders[key].uniforms.uTexture = inputs[key].color[0].bind(0)
68 | shaders[key].uniforms.uMouse = [mouse.x, canvas.height - mouse.y]
69 | triangle(gl)
70 |
71 | fbo.shape = [canvas.height, canvas.width]
72 | fbo.bind()
73 | shaders.display.bind()
74 | shaders.display.uniforms.uTexture = outputs[key].color[0].bind(0)
75 | triangle(gl)
76 |
77 | var tmp = inputs[key]
78 | inputs[key] = outputs[key]
79 | outputs[key] = tmp
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-1/server.js:
--------------------------------------------------------------------------------
1 |
2 | var path = require('path')
3 |
4 | module.exports = function(sourceFiles) {
5 | var glslify = ['-t', require.resolve('glslify')]
6 | var live = ['-t', require.resolve('glslify-live')]
7 | var brfs = ['-t', require.resolve('brfs')]
8 | var envify = ['-t', '[', require.resolve('envify')]
9 |
10 | envify.push('--dirname', path.basename(__dirname))
11 | sourceFiles.forEach(function(file) {
12 | var base = path.basename(file).replace(/\./g, '_')
13 | envify.push('--file_' + base)
14 | envify.push(file)
15 | })
16 |
17 | envify.push(']')
18 |
19 | return require('beefy')({
20 | cwd: __dirname
21 | , entries: ['index.js']
22 | , quiet: false
23 | , live: false
24 | , debug: false
25 | , watchify: false
26 | , bundlerFlags: []
27 | .concat(envify)
28 | .concat(live)
29 | .concat(glslify)
30 | .concat(brfs)
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-1/shaders/display.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D uTexture;
4 | varying vec2 vUv;
5 |
6 | void main() {
7 | gl_FragColor = texture2D(uTexture, vUv);
8 | }
9 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-1/shaders/expected.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D uTexture;
4 | uniform vec2 uMouse;
5 | varying vec2 vUv;
6 |
7 | // Each frame, draw a white circle around the mouse that
8 | // has a radius of 20 pixels. Everywhere else, fade to black
9 | // as slowly as possible.
10 | vec3 update(vec3 color) {
11 | vec2 pixelPosition = gl_FragCoord.xy;
12 | vec2 mousePosition = uMouse;
13 | float dist = distance(mousePosition, pixelPosition);
14 |
15 | if (dist <= 20.0) {
16 | color = vec3(1.0);
17 | } else {
18 | color.rgb -= 1.0/255.0;
19 | }
20 |
21 | return color;
22 | }
23 |
24 | void main() {
25 | vec3 original = texture2D(uTexture, vUv).rgb;
26 |
27 | gl_FragColor.rgb = update(original);
28 | gl_FragColor.a = 1.0;
29 | }
30 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-1/shaders/triangle.vert:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec2 position;
4 | varying vec2 vUv;
5 |
6 | void main() {
7 | vUv = (position.xy + 1.0) * 0.5;
8 | gl_Position = vec4(position.xy, 1.0, 1.0);
9 | }
10 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-3/README.md:
--------------------------------------------------------------------------------
1 | # GPGPU: Separating Form and Function
2 |
3 | Now that you've got a handle on manipulating data in a texture, it's time to
4 | start slowly moving out of the constraints of that black-and-white image.
5 |
6 | To keep things simple, we'll stick to the grid but simply map the colors to
7 | different values.
8 |
9 | In this [exercise's directory](/open/gpgpu-3) you'll find two shaders:
10 |
11 | * `logic.frag` handles the cell update logic – you'll recognise this from the
12 | previous lesson.
13 | * `render.frag` is new: it handles actually drawing the cells to the screen.
14 |
15 | Fix the pair of shaders such that:
16 |
17 | * Cells that are alive are colored `ALIVE`.
18 | * Cells that are dead are colored `DEAD`.
19 | * Cells that have been born this frame are colored `BIRTH`.
20 | * Cells that have died this frame are colored `DEATH`.
21 |
22 | To do this properly, you'll need to edit both shaders.
23 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-3/files/logic.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D uTexture;
4 | uniform vec2 uUnitSize;
5 | varying vec2 vUv;
6 |
7 | void main() {
8 | float s = texture2D(uTexture, vUv).r;
9 | float n = 0.0;
10 |
11 | for (int i = -1; i <= 1; i++)
12 | for (int j = -1; j <= 1; j++) {
13 | vec2 coord = fract(vUv + vec2(i, j) / uUnitSize);
14 | n += texture2D(uTexture, coord).r;
15 | }
16 |
17 | float alive = n > 3.0+s || n < 3.0 ? 0.0 : 1.0;
18 |
19 | gl_FragColor.rgb = vec3(alive);
20 | gl_FragColor.a = 1.0;
21 | }
22 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-3/files/render.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D uTexture;
4 | varying vec2 vUv;
5 |
6 | #define BIRTH vec4(1.0, 0.8862, 0.3725, 1.0)
7 | #define DEATH vec4(1.0, 0.4313, 0.3411, 1.0)
8 | #define ALIVE vec4(0.3804, 0.7647, 1.0, 1.0)
9 | #define DEAD vec4(0.2039, 0.2117, 0.2313, 1.0)
10 |
11 | void main() {
12 | gl_FragColor = texture2D(uTexture, vUv);
13 | }
14 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: lesson template
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-3/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | // .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-3/shaders/logic_solution.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D uTexture;
4 | uniform vec2 uUnitSize;
5 | varying vec2 vUv;
6 |
7 | void main() {
8 | float s = texture2D(uTexture, vUv).r;
9 | float n = 0.0;
10 |
11 | for (int i = -1; i <= 1; i++)
12 | for (int j = -1; j <= 1; j++) {
13 | vec2 coord = fract(vUv + vec2(i, j) / uUnitSize);
14 | n += texture2D(uTexture, coord).r;
15 | }
16 |
17 | float alive = n > 3.0+s || n < 3.0 ? 0.0 : 1.0;
18 |
19 | gl_FragColor.r = alive;
20 | gl_FragColor.g = alive == 1.0 && s != 1.0 ? 1.0 : 0.0;
21 | gl_FragColor.b = alive != 1.0 && s == 1.0 ? 1.0 : 0.0;
22 | gl_FragColor.a = 1.0;
23 | }
24 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-3/shaders/render_solution.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D uTexture;
4 | varying vec2 vUv;
5 |
6 | #define BIRTH vec4(1.0, 0.8862, 0.3725, 1.0)
7 | #define DEATH vec4(1.0, 0.4313, 0.3411, 1.0)
8 | #define ALIVE vec4(0.3804, 0.7647, 1.0, 1.0)
9 | #define DEAD vec4(0.2039, 0.2117, 0.2313, 1.0)
10 |
11 | void main() {
12 | vec3 data = texture2D(uTexture, vUv).rgb;
13 | bool birth = data.g > 0.5;
14 | bool death = data.b > 0.5;
15 | bool alive = data.r > 0.5;
16 |
17 | if (birth) {
18 | gl_FragColor = BIRTH;
19 | } else
20 | if (death) {
21 | gl_FragColor = DEATH;
22 | } else
23 | if (alive) {
24 | gl_FragColor = ALIVE;
25 | } else {
26 | gl_FragColor = DEAD;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/exercises/_gpgpu-3/shaders/triangle.vert:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec2 position;
4 | varying vec2 vUv;
5 |
6 | void main() {
7 | vUv = (position.xy + 1.0) * 0.5;
8 | gl_Position = vec4(position.xy, 0.0, 1.0);
9 | }
10 |
--------------------------------------------------------------------------------
/exercises/_lesson-template/README.md:
--------------------------------------------------------------------------------
1 | # demo lesson
2 |
3 | This lesson is just an example.
4 |
5 | The "expected" shader is the checkerboard of blue/red squares, and the "actual"
6 | shader is the grid with a slight gradient.
7 |
8 | All the shaders have had [glslify-live](http://github.com/hughsk/glslify-live)
9 | applied, so you can modify their file contents and they'll update without
10 | needing to refresh the page. Unfortunately it's not resistant to syntax errors
11 | yet so right now will crash the workshop if a shader is ever invalid on save.
12 |
13 | `exercises/lesson-1/files/triangle.frag` will be copied to
14 | `lesson-1/triangle.frag` for the student to modify themselves, and the rest
15 | will be kept hidden away. If time permits we might want to also copy over a
16 | dummy project which runs the shader standalone on `npm start`?
17 |
--------------------------------------------------------------------------------
/exercises/_lesson-template/files/triangle.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | void main() {
4 | float bx = mod(gl_FragCoord.x / 20.0, 4.0);
5 | float by = mod(gl_FragCoord.y / 20.0, 4.0);
6 | float brightness = (
7 | bx > 1.0 && by > 1.0 ||
8 | bx < 1.0 && by < 1.0
9 | ) ? 1.0 : 0.0;
10 |
11 | gl_FragColor = vec4(vec3(brightness) / gl_FragCoord.y * 100.0, 1.0);
12 | }
13 |
--------------------------------------------------------------------------------
/exercises/_lesson-template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: lesson template
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/_lesson-template/index.js:
--------------------------------------------------------------------------------
1 | var triangle = require('a-big-triangle')
2 | var throttle = require('frame-debounce')
3 | var fit = require('canvas-fit')
4 | var getContext = require('gl-context')
5 | var compare = require('gl-compare')
6 | var createShader = require('glslify')
7 | var fs = require('fs')
8 |
9 | var container = document.getElementById('container')
10 | var canvas = container.appendChild(document.createElement('canvas'))
11 | var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
12 | var gl = getContext(canvas, render)
13 | var comparison = compare(gl, actual, expected)
14 |
15 | comparison.mode = 'slide'
16 | comparison.amount = 0.5
17 |
18 | require('../common')({
19 | description: readme
20 | , compare: comparison
21 | , canvas: canvas
22 | })
23 |
24 | window.addEventListener('resize', fit(canvas), false)
25 |
26 | var actualShader = createShader({
27 | frag: process.env.file_triangle_frag
28 | , vert: './shaders/triangle.vert'
29 | })(gl)
30 |
31 | var expectedShader = createShader({
32 | frag: './shaders/triangle.frag'
33 | , vert: './shaders/triangle.vert'
34 | })(gl)
35 |
36 | function render() {
37 | comparison.run()
38 | comparison.render()
39 | }
40 |
41 | function actual(fbo) {
42 | fbo.shape = [canvas.height, canvas.width]
43 | fbo.bind()
44 | actualShader.bind()
45 | triangle(gl)
46 | }
47 |
48 | function expected(fbo) {
49 | fbo.shape = [canvas.height, canvas.width]
50 | fbo.bind()
51 | expectedShader.bind()
52 | triangle(gl)
53 | }
54 |
--------------------------------------------------------------------------------
/exercises/_lesson-template/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | sourceFiles.forEach(function(file) {
10 | var base = path.basename(file).replace(/\./g, '_')
11 | envify.push('--file_' + base)
12 | envify.push(file)
13 | })
14 |
15 | envify.push(']')
16 |
17 | return require('beefy')({
18 | cwd: __dirname
19 | , entries: ['index.js']
20 | , quiet: false
21 | , live: false
22 | , debug: false
23 | , watchify: false
24 | , bundlerFlags: []
25 | .concat(envify)
26 | .concat(live)
27 | .concat(glslify)
28 | .concat(brfs)
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/exercises/_lesson-template/shaders/triangle.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | void main() {
4 | float bx = mod(gl_FragCoord.x / 20.0, 2.0);
5 | float by = mod(gl_FragCoord.y / 20.0, 2.0);
6 | float brightness = (
7 | bx > 1.0 && by > 1.0 ||
8 | bx < 1.0 && by < 1.0
9 | ) ? 1.0 : 0.0;
10 |
11 | gl_FragColor = vec4(vec3(brightness, 0.2, 0.5), 1.0);
12 | }
13 |
--------------------------------------------------------------------------------
/exercises/_lesson-template/shaders/triangle.vert:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec2 position;
4 |
5 | void main() {
6 | gl_Position = vec4(position.xy, 1.0, 1.0);
7 | }
8 |
--------------------------------------------------------------------------------
/exercises/_macros/README.md:
--------------------------------------------------------------------------------
1 | ## Preprocessor
2 |
3 | GLSL also has a C-like preprocessor. All preprocessor commands are prefixed with a `#` symbol and are executed at compile time.
4 |
5 | ### Macros
6 |
7 | One of the most useful preprocessor features are macros. Macros are like functions, but they are expanded at compile time. This makes them useful for generating code or complex functions in shaders that would be too cumbersome to write by hand. Macros are also useful for defining constant values in shaders and for conditional compilation. To declare a macro, you can use the `#define` command. For example, here is a simple use of macros:
8 |
9 | ```glsl
10 | #define MAX_LIGHTS 10
11 |
12 | vec3 lightPositions[MAX_LIGHTS];
13 | ```
14 |
15 | When compiled, this code will expand into:
16 |
17 | ```glsl
18 | vec3 lightPositions[10];
19 | ```
20 |
21 | Macros can also take parameters, for example:
22 |
23 | ```glsl
24 | #define PLUS_ONE(x) (x+1.0)
25 |
26 | float y = 0.0;
27 | float z = PLUS_ONE(y);
28 | ```
29 |
30 | Which expands to:
31 |
32 | ```glsl
33 | float y = 0.0;
34 | float z = (y+1.0);
35 | ```
36 |
37 | ### Conditional compilation
38 |
39 | The GLSL preprocessor also supports basic logic for conditional compilation using the `#if`, `#else` and `#endif` commands:
40 |
41 | ```glsl
42 | #define USE_FAST_RANDOM 1
43 |
44 |
45 | #if USE_FAST_RANDOM
46 |
47 | float random(float seed) {
48 | return 2.0; //Rolled using a fair die
49 | }
50 |
51 | #else
52 |
53 | float random(float seed) {
54 | // ...
55 | }
56 |
57 | #endif
58 | ```
59 |
60 | ### Capabilities
61 |
62 | Conditional compilation is especially helpful when combined with knowledge of device capabilities. For example, if a WebGL implementation does not support some extension then the shader can detect this with an appropriate preprocessor command and select some fallback.
63 |
--------------------------------------------------------------------------------
/exercises/_point-1/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | void main() {
4 | gl_FragColor = vec4(1,1,1,1);
5 | }
--------------------------------------------------------------------------------
/exercises/_point-1/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec3 position;
4 | attribute float size;
5 |
6 | void main() {
7 | gl_Position = vec4(0,0,0,0);
8 | }
--------------------------------------------------------------------------------
/exercises/_point-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: clip coordinates and transformations
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/_point-1/index.js:
--------------------------------------------------------------------------------
1 | var matchFBO = require('../../lib/match-fbo')
2 | var conway = require('conway-hart')
3 | var throttle = require('frame-debounce')
4 | var fit = require('canvas-fit')
5 | var getContext = require('gl-context')
6 | var compare = require('gl-compare')
7 | var createBuffer = require('gl-buffer')
8 | var createVAO = require('gl-vao')
9 | var createShader = require('glslify')
10 | var fs = require('fs')
11 | var now = require('right-now')
12 | var glm = require('gl-matrix')
13 | var mat4 = glm.mat4
14 |
15 | var container = document.getElementById('container')
16 | var canvas = container.appendChild(document.createElement('canvas'))
17 | var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
18 | var gl = getContext(canvas, render)
19 | var comparison = compare(gl, actual, expected)
20 |
21 | comparison.mode = 'slide'
22 | comparison.amount = 0.5
23 |
24 | require('../common')({
25 | description: readme
26 | , compare: comparison
27 | , canvas: canvas
28 | , test: matchFBO(comparison, 0.99)
29 | , dirname: process.env.dirname
30 | })
31 |
32 | window.addEventListener('resize', fit(canvas), false)
33 |
34 |
35 |
36 |
37 |
38 | var actualShader = createShader({
39 | frag: './shaders/fragment.glsl'
40 | , vert: process.env.file_transforms_glsl
41 | })(gl)
42 |
43 | var expectedShader = createShader({
44 | frag: './shaders/fragment.glsl'
45 | , vert: './shaders/vertex.glsl'
46 | })(gl)
47 |
48 |
49 | function getCamera() {
50 | var projection = mat4.perspective(
51 | mat4.create(),
52 | Math.PI/4.0,
53 | canvas.width/canvas.height,
54 | 0.0001,
55 | 1000.0)
56 |
57 | var t = now() * 0.0001
58 | var view = mat4.lookAt(
59 | mat4.create(),
60 | [4*Math.cos(t), 4.5, 4*Math.sin(t)],
61 | [0,0,0],
62 | [0,1,0])
63 |
64 | var model = mat4.create()
65 |
66 | return {
67 | view: view,
68 | projection: projection,
69 | model: model
70 | }
71 | }
72 |
73 | var camera
74 | function render() {
75 | camera = getCamera()
76 | comparison.run()
77 | comparison.render()
78 | }
79 |
80 | function actual(fbo) {
81 | fbo.shape = [canvas.height, canvas.width]
82 | fbo.bind()
83 | gl.clear(gl.COLOR_BUFFER_BIT)
84 |
85 | actualShader.bind()
86 | actualShader.uniforms = camera
87 |
88 | vertexArray.bind()
89 | vertexArray.draw(gl.POINTS, vertexData.length / 3)
90 | }
91 |
92 | function expected(fbo) {
93 | fbo.shape = [canvas.height, canvas.width]
94 | fbo.bind()
95 | gl.clear(gl.COLOR_BUFFER_BIT)
96 |
97 | expectedShader.bind()
98 | expectedShader.uniforms = camera
99 |
100 | vertexArray.bind()
101 | vertexArray.draw(gl.POINTS, vertexData.length / 3)
102 | }
--------------------------------------------------------------------------------
/exercises/_point-1/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/_point-1/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1,1,1,1);
3 | }
--------------------------------------------------------------------------------
/exercises/_point-1/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | attribute vec3 position;
3 | uniform mat4 model, view, projection;
4 |
5 | void main() {
6 | gl_Position = projection * view * vec4(position, 1);
7 | }
--------------------------------------------------------------------------------
/exercises/frag-1/README.md:
--------------------------------------------------------------------------------
1 | # Introduction to Fragment shaders
2 |
3 | ## Exercise
4 |
5 | Write a fragment shader which draws a disk of radius `128` centered at the point `vec2(256,256)` in device coordinates. The points inside the disk should be colored with `CIRCLE_COLOR` and the points outside should be colored with `OUTSIDE_COLOR`. A file called `fragment.glsl` has been created in this project's directory to help get you started.
6 |
7 | ***
8 |
9 | ### Fragments vs. pixels
10 |
11 | A *fragment* is the color of some fraction of a pixel. In the simplest case, there is a 1:1 relation between fragments and pixels, though with anti-aliasing this ratio could be much higher. Fragment shaders are programs which are run on the GPU and determine the color of each fragment.
12 |
13 | ### Basics of writing fragment shaders
14 |
15 | The entry point for each fragment shader is a special procedure called `main()`, which takes no arguments and has no return value. The output from a fragment shader is one or more RGBA color values. These values are the components of a special builtin array called `gl_FragData[]`. Here is a simple fragment shader that outputs the color red:
16 |
17 | ```glsl
18 | void main() {
19 | //Colors are represented by 4 vectors in RGBA order
20 | gl_FragData[0] = vec4(1, 0, 0, 1);
21 | }
22 | ```
23 |
24 | The n-th entry in the `gl_FragData[n]` array represents the color that will be written to the color attachment at position `n`. When we are rendering to the drawing buffer, which has only one color attachment, only the first component of `gl_FragData` will be used. For these situations, GLSL defines a helpful macro which aliases `gl_FragData[0]` to the variable `gl_FragColor`. Therefore we could rewrite the above shader as:
25 |
26 | ```glsl
27 | void main() {
28 | gl_FragColor = vec4(1, 0, 0, 1);
29 | }
30 | ```
31 |
32 | ### gl_FragCoord
33 |
34 | Every fragment shader also receives a special input variable called `gl_FragCoord`. `gl_FragCoord` is a `vec4` which returns the coordinate of the fragment in device coordinates. Specifically:
35 |
36 | * `gl_FragCoord.xy` is the coordinate of the fragment in units relative to the top-left of the buffer. The y-component is the row of the fragment, and the x-coordinate is the column.
37 | * `gl_FragCoord.z` is the depth value of the fragment, in units between `[0,1]`, where `0` represents the closest possible z value and `1` represents the farthest possible z-value.
38 | * `gl_FragCoord.w` is the reciprocal of the homogeneous part of the fragment's position in clip coordinates
39 |
--------------------------------------------------------------------------------
/exercises/frag-1/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | #define CIRCLE_COLOR vec4(1.0, 0.4313, 0.3411, 1.0)
4 | #define OUTSIDE_COLOR vec4(0.3804, 0.7647, 1.0, 1.0)
5 |
6 | void main() {
7 |
8 | //TODO: Replace this with a function that draws a circle at (256.5,256.5) with radius 128
9 |
10 | if(gl_FragCoord.y > 256.0) {
11 | gl_FragColor = CIRCLE_COLOR;
12 | } else {
13 | gl_FragColor = OUTSIDE_COLOR;
14 | }
15 | }
--------------------------------------------------------------------------------
/exercises/frag-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: introduction to fragment shaders
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/frag-1/index.js:
--------------------------------------------------------------------------------
1 | var matchFBO = require('../../lib/match-fbo')
2 | var triangle = require('a-big-triangle')
3 | var throttle = require('frame-debounce')
4 | var fit = require('canvas-fit')
5 | var getContext = require('gl-context')
6 | var compare = require('gl-compare')
7 | var createShader = require('glslify')
8 | var fs = require('fs')
9 |
10 | var container = document.getElementById('container')
11 | var canvas = container.appendChild(document.createElement('canvas'))
12 | var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
13 | var gl = getContext(canvas, render)
14 | var comparison = compare(gl, actual, expected)
15 |
16 | comparison.mode = 'slide'
17 | comparison.amount = 0.5
18 |
19 | require('../common')({
20 | description: readme
21 | , compare: comparison
22 | , canvas: canvas
23 | , dirname: process.env.dirname
24 | , test: matchFBO(comparison, 0.99)
25 | })
26 |
27 | window.addEventListener('resize', fit(canvas), false)
28 |
29 | var actualShader = createShader({
30 | frag: process.env.file_fragment_glsl
31 | , vert: './shaders/vertex.glsl'
32 | })(gl)
33 |
34 | var expectedShader = createShader({
35 | frag: './shaders/fragment.glsl'
36 | , vert: './shaders/vertex.glsl'
37 | })(gl)
38 |
39 | function render() {
40 | comparison.run()
41 | comparison.render()
42 | }
43 |
44 | function actual(fbo) {
45 | fbo.shape = [512, 512]
46 | fbo.bind()
47 | actualShader.bind()
48 | triangle(gl)
49 | }
50 |
51 | function expected(fbo) {
52 | fbo.shape = [512, 512]
53 | fbo.bind()
54 | expectedShader.bind()
55 | triangle(gl)
56 | }
--------------------------------------------------------------------------------
/exercises/frag-1/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/frag-1/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | #define CIRCLE_COLOR vec4(1.0, 0.4313, 0.3411, 1.0)
4 | #define OUTSIDE_COLOR vec4(0.3804, 0.7647, 1.0, 1.0)
5 |
6 | void main() {
7 | float d2 = distance(gl_FragCoord.xy, vec2(256, 256));
8 | if(d2 >= 128.0) {
9 | gl_FragData[0] = OUTSIDE_COLOR;
10 | } else {
11 | gl_FragData[0] = CIRCLE_COLOR;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/exercises/frag-1/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec2 position;
4 |
5 | void main() {
6 | gl_Position = vec4(position.xy, 1.0, 1.0);
7 | }
8 |
--------------------------------------------------------------------------------
/exercises/frag-2/README.md:
--------------------------------------------------------------------------------
1 | # The `discard` keyword
2 |
3 | ## Exercise
4 |
5 | Create a shader which renders a checkerboard with 16x16 pixel tiles. Tiles with even parity should be discarded, while tiles with odd parity should be drawn white. A template file called `fragment.glsl` has been created in this project's directory to help get you started.
6 |
7 | #### Hint
8 |
9 | To create a periodic 1D grid of tiles, you can use the fract and step functions:
10 |
11 | ```glsl
12 | bool inTile(vec2 p, float tileSize) {
13 | vec2 ptile = step(0.5, fract(0.5 * p / tileSize));
14 | return ptile.x == ptile.y;
15 | }
16 | ```
17 |
18 | ***
19 |
20 | ## discard;
21 |
22 | In GLSL it is possible to skip rendering a fragment with the `discard` statement. If a pixel is discarded in the fragment shader, execution terminates immediately and nothing is written to the frame buffer.
23 |
--------------------------------------------------------------------------------
/exercises/frag-2/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | void main() {
4 |
5 | //TODO: Replace this with a function that draws a checkerboard
6 |
7 | discard;
8 | }
--------------------------------------------------------------------------------
/exercises/frag-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: discard
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/frag-2/index.js:
--------------------------------------------------------------------------------
1 | var matchFBO = require('../../lib/match-fbo')
2 | var triangle = require('a-big-triangle')
3 | var throttle = require('frame-debounce')
4 | var fit = require('canvas-fit')
5 | var getContext = require('gl-context')
6 | var compare = require('gl-compare')
7 | var createShader = require('glslify')
8 | var fs = require('fs')
9 | var now = require('right-now')
10 |
11 | var container = document.getElementById('container')
12 | var canvas = container.appendChild(document.createElement('canvas'))
13 | var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
14 | var gl = getContext(canvas, render)
15 | var comparison = compare(gl, actual, expected)
16 |
17 | comparison.mode = 'slide'
18 | comparison.amount = 0.5
19 |
20 | require('../common')({
21 | description: readme
22 | , compare: comparison
23 | , canvas: canvas
24 | , dirname: process.env.dirname
25 | , test: matchFBO(comparison, 0.99)
26 | })
27 |
28 | window.addEventListener('resize', fit(canvas), false)
29 |
30 | var actualShader = createShader({
31 | frag: process.env.file_fragment_glsl
32 | , vert: './shaders/vertex.glsl'
33 | })(gl)
34 |
35 | var expectedShader = createShader({
36 | frag: './shaders/fragment.glsl'
37 | , vert: './shaders/vertex.glsl'
38 | })(gl)
39 |
40 | var clearColor = [0,0,0,0]
41 |
42 | function render() {
43 | var t = 0.0001 * now()
44 | clearColor[0] = 0.5 + 0.5 * Math.cos(t)
45 | clearColor[1] = 0.5 + 0.5 * Math.cos(2.3*t + 1.8)
46 | clearColor[2] = 0.5 + 0.5 * Math.cos(0.7*t + 3)
47 | clearColor[3] = 1.0
48 |
49 | comparison.run()
50 | comparison.render()
51 | }
52 |
53 | function actual(fbo) {
54 | fbo.shape = [512, 512]
55 | fbo.bind()
56 | gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3])
57 | gl.clear(gl.COLOR_BUFFER_BIT)
58 | actualShader.bind()
59 | triangle(gl)
60 | }
61 |
62 | function expected(fbo) {
63 | fbo.shape = [512, 512]
64 | fbo.bind()
65 | gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3])
66 | gl.clear(gl.COLOR_BUFFER_BIT)
67 | expectedShader.bind()
68 | triangle(gl)
69 | }
--------------------------------------------------------------------------------
/exercises/frag-2/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/frag-2/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | void main() {
4 | vec2 cell = step(0.5, fract(gl_FragCoord.xy/32.0));
5 | if(cell.x + cell.y == 1.0) {
6 | discard;
7 | } else {
8 | gl_FragColor = vec4(1,1,1,1);
9 | }
10 | }
--------------------------------------------------------------------------------
/exercises/frag-2/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec2 position;
4 |
5 | void main() {
6 | gl_Position = vec4(position.xy, 1.0, 1.0);
7 | }
8 |
--------------------------------------------------------------------------------
/exercises/frag-3/README.md:
--------------------------------------------------------------------------------
1 | # Uniform variables and textures
2 |
3 | ## Exercise
4 |
5 | Write a program which swaps the red and blue color channels of a texture image. To do this, you should modify the `fragment.glsl` shader in this project's directory. You will be given the following uniforms to help compute this quantity:
6 |
7 | * `screenSize` which should be applied to scale the coordinates of `gl_FragCoord` to an acceptable range.
8 | * `texture` which is a `sampler2D` containing the texture to draw.
9 |
10 | ***
11 |
12 | ## Uniform variables
13 |
14 | One way to send data to shaders is with `uniform` variables. `uniform` variables can be set from within JavaScript and are broadcast to all executions of a shader. Here is how we could declare a fragment shader which takes as input some 4-vector called `foo` and assigns it to the fragment color:
15 |
16 | ```glsl
17 | precision highp float;
18 |
19 | uniform vec4 foo;
20 |
21 | void main() {
22 | gl_FragColor = foo;
23 | }
24 | ```
25 |
26 | ## Textures
27 |
28 | Uniforms are a great way to send small, frequently changing information to shaders. However, sometimes we might want to send a larger, static data to fragment shaders. This can be done using *textures*. Textures in WebGL are 2D arrays of vectors. Textures are declared using the `sampler2D` data type, and accessed using the built in function `texture2D()`:
29 |
30 | ```glsl
31 | precision highp float;
32 |
33 | uniform sampler2D image;
34 | uniform vec2 screenSize;
35 |
36 | void main() {
37 |
38 | //Draws the texture to the screen
39 | vec2 uv = gl_FragCoord.xy / screenSize;
40 | gl_FragColor = texture2D(image, uv);
41 | }
42 | ```
43 |
44 | The built-in function `texture2D()` has the following signature:
45 |
46 | ```glsl
47 | vec4 texture2D(
48 | in sampler2D texture,
49 | in vec2 coordinate,
50 | in float bias = 0.0
51 | );
52 | ```
53 |
54 | Where:
55 |
56 | * `texture` is a sampler variable.
57 | * `coordinate` controls which data is read out from the texture. Coordinates in the texture range from `0` to `1`.
58 | * `bias` is an optional parameter that changes the filtering of the texture.
59 |
--------------------------------------------------------------------------------
/exercises/frag-3/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform sampler2D texture;
4 | uniform vec2 screenSize;
5 |
6 | void main() {
7 | vec2 coord = gl_FragCoord.xy / screenSize;
8 |
9 | //TODO: Swap red and blue color channels of image
10 |
11 | gl_FragColor = texture2D(texture, coord);
12 | }
13 |
--------------------------------------------------------------------------------
/exercises/frag-3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: uniforms and textures
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/frag-3/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/frag-3/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform sampler2D texture;
4 | uniform vec2 screenSize;
5 |
6 | void main() {
7 | vec2 coord = gl_FragCoord.xy / screenSize;
8 | gl_FragColor = texture2D(texture, coord).bgra;
9 | }
10 |
--------------------------------------------------------------------------------
/exercises/frag-3/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec2 position;
4 |
5 | void main() {
6 | gl_Position = vec4(position.xy, 1.0, 1.0);
7 | }
8 |
--------------------------------------------------------------------------------
/exercises/geom-1/files/transforms.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec3 position;
4 |
5 | uniform mat4 model, view, projection;
6 |
7 | void main() {
8 |
9 | //TODO: Apply the model-view-projection matrix to `position`
10 |
11 | gl_Position = vec4(position, 1);
12 | }
--------------------------------------------------------------------------------
/exercises/geom-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: clip coordinates and transformations
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/geom-1/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/geom-1/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1,1,1,1);
3 | }
--------------------------------------------------------------------------------
/exercises/geom-1/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | attribute vec3 position;
3 | uniform mat4 model, view, projection;
4 |
5 | void main() {
6 | gl_Position = projection * view * model * vec4(position, 1);
7 | }
8 |
--------------------------------------------------------------------------------
/exercises/geom-2/README.md:
--------------------------------------------------------------------------------
1 | # Translations
2 |
3 | ## Exercise
4 |
5 | One of the most basic perspective transformations are translations. A translation of a vector `v` moving the point `o` to the origin is a function:
6 |
7 | ```glsl
8 | vec3 translatePoint(vec3 v, vec3 o) {
9 | return v - o;
10 | }
11 | ```
12 |
13 | Translations are not linear in affine coordinates, however in projective homogeneous coordinates they are. As a result, they can be written as a matrix.
14 |
15 | For this exercise, you will work out how to do this yourself. That is, you should implement a GLSL function which constructs a 4x4 matrix representing a translation moving a point `p` to the origin. To get started modify the file `translate.glsl` in the directory for this lesson.
16 |
17 | Note that GLSL matrices are stored in *column major* order, not *row major* as matrices are commonly written. This means that the entries of the matrix are transposed with respect to the usual notation.
18 |
19 | ***
20 |
21 | ## See Also
22 |
23 | * Translation (Wikipedia)
24 |
--------------------------------------------------------------------------------
/exercises/geom-2/files/translate.glsl:
--------------------------------------------------------------------------------
1 | highp mat4 translate(highp vec3 p) {
2 |
3 | //TODO: Construct a matrix, m, which translates all points so that p is at the origin.
4 |
5 | return mat4(1, 0, 0, 0,
6 | 0, 1, 0, 0,
7 | 0, 0, 1, 0,
8 | 0, 0, 0, 1);
9 | }
10 |
11 | //Do not remove this line
12 | #pragma glslify: export(translate)
--------------------------------------------------------------------------------
/exercises/geom-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: translations
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/geom-2/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/geom-2/shaders/expected.glsl:
--------------------------------------------------------------------------------
1 | highp mat4 translate(vec3 p) {
2 | return mat4(1, 0, 0, 0,
3 | 0, 1, 0, 0,
4 | 0, 0, 1, 0,
5 | -p.x, -p.y, -p.z, 1);
6 | }
7 |
8 | #pragma glslify: export(translate)
--------------------------------------------------------------------------------
/exercises/geom-2/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1,1,1,1);
3 | }
--------------------------------------------------------------------------------
/exercises/geom-2/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | attribute vec3 position;
3 |
4 | uniform mat4 view, projection;
5 | uniform vec3 shiftVec;
6 |
7 | #pragma glslify: translation=require(./expected.glsl)
8 |
9 | void main() {
10 | gl_Position = projection * view * translation(shiftVec) * vec4(position, 1);
11 | }
--------------------------------------------------------------------------------
/exercises/geom-3/README.md:
--------------------------------------------------------------------------------
1 | # Scaling
2 |
3 | ## Exercise
4 |
5 | Scaling transformations stretch or squish the coordinate axes. Specifically, scaling a vector `v` by a factor of `s` along each axis can be defined using the following function:
6 |
7 | ```glsl
8 | vec3 scaleVector(vec3 v, vec3 s) {
9 | return s * v;
10 | }
11 | ```
12 |
13 | Like translation, scaling transformations are also linear in projective geometry. For this exercise, write a shader that computes a matrix representation of the scaling function above. To get started, a file called `scale.glsl` has been created in this lesson's directory.
14 |
15 | ***
16 |
17 | ## See Also
18 |
19 | * Scaling (Wikipedia)
20 |
--------------------------------------------------------------------------------
/exercises/geom-3/files/scale.glsl:
--------------------------------------------------------------------------------
1 | highp mat4 scale(highp vec3 p) {
2 |
3 | //TODO: Return a matrix that scales each axis about the origin by a factor of p.x/p.y/p.z
4 |
5 | return mat4(1, 0, 0, 0,
6 | 0, 1, 0, 0,
7 | 0, 0, 1, 0,
8 | 0, 0, 0, 1);
9 | }
10 |
11 | #pragma glslify: export(scale)
--------------------------------------------------------------------------------
/exercises/geom-3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: scaling
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/geom-3/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/geom-3/shaders/expected.glsl:
--------------------------------------------------------------------------------
1 | highp mat4 scale(vec3 p) {
2 | return mat4(p.x, 0, 0, 0,
3 | 0, p.y, 0, 0,
4 | 0, 0, p.z, 0,
5 | 0, 0, 0, 1);
6 | }
7 |
8 | #pragma glslify: export(scale)
--------------------------------------------------------------------------------
/exercises/geom-3/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1,1,1,1);
3 | }
--------------------------------------------------------------------------------
/exercises/geom-3/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | attribute vec3 position;
3 |
4 | uniform mat4 view, projection;
5 | uniform vec3 scaleVec;
6 |
7 | #pragma glslify: transform=require(./expected.glsl)
8 |
9 | void main() {
10 | gl_Position = projection * view * transform(scaleVec) * vec4(position, 1);
11 | }
--------------------------------------------------------------------------------
/exercises/geom-4/README.md:
--------------------------------------------------------------------------------
1 | # Reflections
2 |
3 | ## Exercise
4 |
5 | In this exercise, you will figure out how to represent a reflection as a matrix transformation. Reflections flip a vector along a given axis. Specifically, the reflection of a point p along the axis n is defined to be:
6 |
7 | ```glsl
8 | vec3 reflectPoint(vec3 p, vec3 n) {
9 | return p - 2.0 * dot(n, p) * n / dot(n, n);
10 | }
11 | ```
12 |
13 | The above function is again linear in `p` so it can be translated into a matrix. Do this now by modifying the file `reflect.glsl` and writing a GLSL function to compute this quantity.
14 |
--------------------------------------------------------------------------------
/exercises/geom-4/files/reflect.glsl:
--------------------------------------------------------------------------------
1 | highp mat4 reflection(highp vec3 n) {
2 |
3 | //TODO: Return a matrix that reflects all points about the plane passing through the origin with normal n
4 |
5 | return mat4(1, 0, 0, 0,
6 | 0, 1, 0, 0,
7 | 0, 0, 1, 0,
8 | 0, 0, 0, 1);
9 | }
10 |
11 | #pragma glslify: export(reflection)
--------------------------------------------------------------------------------
/exercises/geom-4/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: reflections
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/geom-4/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/geom-4/shaders/expected.glsl:
--------------------------------------------------------------------------------
1 | highp mat4 reflection(highp vec3 n) {
2 | n = normalize(n);
3 | return mat4(1.0-2.0*n.x*n.x, -2.0*n.y*n.x, -2.0*n.z*n.x, 0,
4 | -2.0*n.y*n.x, 1.0-2.0*n.y*n.y, -2.0*n.z*n.y, 0,
5 | -2.0*n.z*n.x, -2.0*n.y*n.z, 1.0-2.0*n.z*n.z, 0,
6 | 0, 0, 0, 1);
7 | }
8 |
9 | #pragma glslify: export(reflection)
--------------------------------------------------------------------------------
/exercises/geom-4/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1,1,1,1);
3 | }
--------------------------------------------------------------------------------
/exercises/geom-4/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | attribute vec3 position;
3 |
4 | uniform mat4 view, projection;
5 | uniform vec3 planeVec;
6 |
7 | void main() {
8 | gl_Position = projection * view * vec4(reflect(position, normalize(planeVec)), 1);
9 | }
--------------------------------------------------------------------------------
/exercises/geom-5/README.md:
--------------------------------------------------------------------------------
1 | # Rotations
2 |
3 | ## Exercise
4 |
5 | Again, rotations are linear perspective transformations and so they can be written as matrices. For this exercise, you should translate the `rotatePoint` function below into a matrix. For the solution, modify the file `rotate.glsl` in this lesson's directory.
6 |
7 | ***
8 |
9 | A *rotation* is the composition of an even number of reflections. In higher dimensions, rotations can be very complicated structures. However, it turns out that 3D geometry is rather unique as it admits a number of special ways to describe rotations that turn out to be very useful in practice.
10 |
11 | One of these methods which is very useful is axis-angle notation. Specifically any 3D rotation can be represented as a rotation in a plane about some axis. The reason that this is possible is that all 3D rotations can be written as the product of exactly 2 reflections. The common line of the plane between the two planes of reflection is called the *axis of the rotation*, and the angle between the two planes is called the *angle of rotation*. This definition turns out to be independent of whichever two plane reflections we use to factor the rotation.
12 |
13 | This relation between rotations and axis-angle pairs is captured by a beautiful result called Rodrigues' rotation formula. In GLSL, we can translate Rodrigues' rotation formula into a function that rotates a point `p` about the unit axis `n` with angle `theta` as follows:
14 |
15 | ```glsl
16 | vec3 rotatePoint(vec3 p, vec3 n, float theta) {
17 | return (
18 | p * cos(theta) + cross(n, p) *
19 | sin(theta) + n * dot(p, n) *
20 | (1.0 - cos(theta))
21 | );
22 | }
23 | ```
24 |
--------------------------------------------------------------------------------
/exercises/geom-5/files/rotate.glsl:
--------------------------------------------------------------------------------
1 | highp mat4 rotation(highp vec3 n, highp float theta) {
2 |
3 | //TODO: Using Rodrigues' formula, find a matrix which performs a rotation about the axis n by theta radians
4 |
5 | return mat4(1, 0, 0, 0,
6 | 0, 1, 0, 0,
7 | 0, 0, 1, 0,
8 | 0, 0, 0, 1);
9 | }
10 |
11 | #pragma glslify: export(rotation)
--------------------------------------------------------------------------------
/exercises/geom-5/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: rotations
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/geom-5/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/geom-5/shaders/expected.glsl:
--------------------------------------------------------------------------------
1 | highp mat4 rotation(highp vec3 n, highp float theta) {
2 |
3 | float s = sin(theta);
4 | float c = cos(theta);
5 | float oc = 1.0 - c;
6 |
7 | return mat4(
8 | oc * n.x * n.x + c, oc * n.x * n.y + n.z * s, oc * n.z * n.x - n.y * s, 0.0,
9 | oc * n.x * n.y - n.z * s, oc * n.y * n.y + c, oc * n.y * n.z + n.x * s, 0.0,
10 | oc * n.z * n.x + n.y * s, oc * n.y * n.z - n.x * s, oc * n.z * n.z + c, 0.0,
11 | 0.0, 0.0, 0.0, 1.0
12 | );
13 | }
14 |
15 | #pragma glslify: export(rotation)
16 |
--------------------------------------------------------------------------------
/exercises/geom-5/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1,1,1,1);
3 | }
--------------------------------------------------------------------------------
/exercises/geom-5/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | attribute vec3 position;
3 |
4 | uniform mat4 view, projection;
5 | uniform vec3 rotationVec;
6 | uniform float theta;
7 |
8 | vec3 rotatePoint(vec3 p, vec3 n, float theta) {
9 | return p * cos(theta) + cross(n, p) * sin(theta) + n * dot(p, n) * (1.0 - cos(theta));
10 | }
11 |
12 | void main() {
13 | gl_Position = projection * view * vec4(rotatePoint(position, normalize(rotationVec), theta), 1);
14 | }
--------------------------------------------------------------------------------
/exercises/gpgpu-1/README.md:
--------------------------------------------------------------------------------
1 | # Game of Life
2 |
3 | ## Exercise
4 |
5 | In this [exercise's directory](/open/gpgpu-1) you'll find a file called `life.glsl`. Modify this file to create a fragment shader that implements Conway's game of life.
6 |
7 | The fragment shader will be executed once per cell. Output vec4(1,1,1,1) if the cell is on, otherwise vec4(0,0,0,1) if the cell is off. The previous state of the world is stored in the sampler `prevState` and the size of the state buffer passed in the uniform `stateSize`.
8 |
9 | ***
10 |
11 | The practice of using graphics processing units for purposes other than 3D graphics is called general purpose GPU computing, or GPGPU for short. This exercise explores using the GPU to solve for state updates in cellular automata.
12 |
13 | ## Game of life
14 |
15 | Conway's Game of Life is a well known example of a 2D totalistic cellular automaton. The world in the game of life is a 2D grid of cells, which we shall assume wraps around at the boundary. In the game of life, updates proceed in rounds wherein each cell looks at its 8 neighbors and then determines its new state according to the following rules:
16 |
17 | * Birth: If a cell is off and has exactly 3 neighbors, it turns on
18 | * Life: If a cell is on, and has 2 or 3 neighbors it stays on
19 | * Death: Otherwise, a cell turns off
20 |
21 | Iterating these simple rules many times yields chaotic and mysterious patterns.
22 |
--------------------------------------------------------------------------------
/exercises/gpgpu-1/files/life.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform sampler2D prevState;
4 | uniform vec2 stateSize;
5 |
6 | float state(vec2 coord) {
7 | return texture2D(prevState, fract(coord / stateSize)).r;
8 | }
9 |
10 | void main() {
11 | vec2 coord = gl_FragCoord.xy;
12 |
13 |
14 | //TODO: Compute the next state for the cell at coord
15 | float s = state(coord);
16 |
17 | gl_FragColor = vec4(s,s,s, 1.0);
18 | }
19 |
--------------------------------------------------------------------------------
/exercises/gpgpu-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: game of life
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/gpgpu-1/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/gpgpu-1/shaders/logic_solution.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D prevState;
4 | uniform vec2 stateSize;
5 |
6 | void main() {
7 | vec2 vUv = gl_FragCoord.xy / stateSize;
8 | float s = texture2D(prevState, vUv).r;
9 | float n = 0.0;
10 |
11 | for (int i = -1; i <= 1; i++)
12 | for (int j = -1; j <= 1; j++) {
13 | vec2 coord = fract(vUv + vec2(i, j) / stateSize);
14 | n += texture2D(prevState, coord).r;
15 | }
16 |
17 | gl_FragColor.rgb = vec3(n > 3.0+s || n < 3.0 ? 0.0 : 1.0);
18 | gl_FragColor.a = 1.0;
19 | }
20 |
--------------------------------------------------------------------------------
/exercises/gpgpu-1/shaders/render.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D prevState;
4 | uniform vec2 stateSize;
5 |
6 | void main() {
7 | gl_FragColor = texture2D(prevState, gl_FragCoord.xy / stateSize);
8 | }
9 |
--------------------------------------------------------------------------------
/exercises/gpgpu-1/shaders/triangle.vert:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec2 position;
4 |
5 | void main() {
6 | gl_Position = vec4(position.xy, 0.0, 1.0);
7 | }
8 |
--------------------------------------------------------------------------------
/exercises/gpgpu-2/README.md:
--------------------------------------------------------------------------------
1 | # Heat equation
2 |
3 | ## Exercise
4 |
5 | Implement a shader which computes a single step of the explicit Euler scheme for integrating the heat equation, described below.
6 |
7 | The previous state of `f(x,y,t-1)` will be encoded in a periodic texture. The diffusion constant and damping will be sent in the parameters `kdiffuse` and `kdamping` respectively.
8 |
9 | To get started, there is a file called `heat.glsl` in the directory for this lesson.
10 |
11 | ***
12 |
13 | The damped heat equation is a second order linear partial differential equation which describes the flow of heat through a material. In the continuous 2D setting it is described by the following equation:
14 |
15 | ```
16 | ( d^2 f d^2 f ) d f
17 | kD * ( ----- + ----- ) - kM * f = ---
18 | ( d x^2 d y^2 ) d t
19 | ```
20 |
21 | In this equation, `f(x,y,t)` is time evolving scalar field, representing the distribution of heat in the material. The parameter `kD` is the diffusion constant and controls the rate at which heat spreads, while `kM` is the damping factor which represents the rate at which heat is lost/gained. Equations of this sort are well studied in mechanics and engineering, and its solution has many applications.
22 |
23 | ## Euler integration
24 |
25 | One way to solve this equation is to discretize it and numerically integrate to obtain a solution. Without going too far into the details of how this works, supposing that `f(x,y,t)` is uniformly sampled on a unit grid, then the left hand side of the equation can be written using a stencil operation:
26 |
27 | ```glsl
28 | float laplace(x, y) {
29 | return (
30 | prevState(x-1,y) +
31 | prevState(x+1,y) +
32 | prevState(x,y-1) +
33 | prevState(x,y+1)
34 | ) - 4.0 * prevState(x,y);
35 | }
36 | ```
37 |
38 | And with damping, the update rule for the integrator becomes:
39 |
40 | ```glsl
41 | nextState(x,y) = (1.0 - kDamping) * (
42 | kDiffuse * laplace(x,y) + prevState(x,y)
43 | )
44 | ```
45 |
--------------------------------------------------------------------------------
/exercises/gpgpu-2/files/heat.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D prevState;
4 | uniform vec2 stateSize;
5 | uniform float kdiffuse;
6 | uniform float kdamping;
7 |
8 | float state(vec2 x) {
9 | return texture2D(prevState, fract(x / stateSize)).r;
10 | }
11 |
12 | void main() {
13 | vec2 coord = gl_FragCoord.xy;
14 |
15 | //TODO: Compute next state using a 5-point Laplacian stencil and the rule
16 |
17 | float y = state(coord);
18 |
19 |
20 | gl_FragColor = vec4(y,y,y,1);
21 | }
22 |
--------------------------------------------------------------------------------
/exercises/gpgpu-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: heat equation
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/gpgpu-2/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | // .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/gpgpu-2/shaders/pass-thru.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 | attribute vec2 position;
3 | void main() {
4 | gl_Position = vec4(position, 0.0, 1.0);
5 | }
6 |
--------------------------------------------------------------------------------
/exercises/gpgpu-2/shaders/point-fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform vec4 color;
4 |
5 | void main() {
6 | float r = length(gl_PointCoord - vec2(0.5, 0.5));
7 | if(r > 0.5) {
8 | discard;
9 | }
10 | gl_FragColor = color;
11 | }
--------------------------------------------------------------------------------
/exercises/gpgpu-2/shaders/point-vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec2 ignored;
4 |
5 | uniform vec2 coord;
6 | uniform float size;
7 |
8 | void main() {
9 | gl_Position = vec4(coord, 0, 1);
10 | gl_PointSize = size;
11 | }
--------------------------------------------------------------------------------
/exercises/gpgpu-2/shaders/render.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D state;
4 | uniform vec2 screenSize;
5 |
6 | #define LO vec4(0.1882, 0.2352, 0.2549, 1)
7 | #define HI vec4(0.7686, 0.9764, 0.3411, 1)
8 |
9 | void main() {
10 | float value = texture2D(state, gl_FragCoord.xy / screenSize).r;
11 | gl_FragColor = mix(LO, HI, clamp(value * 1.1 - 0.1, 0.0, 1.0));
12 | }
13 |
--------------------------------------------------------------------------------
/exercises/gpgpu-2/shaders/update.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform sampler2D prevState;
4 | uniform vec2 stateSize;
5 | uniform float kdiffuse, kdamping;
6 |
7 | float state(vec2 x) {
8 | return texture2D(prevState, x / stateSize).r;
9 | }
10 |
11 | float laplacian(vec2 x) {
12 | return (state(x+vec2(-1,0)) + state(x+vec2(1,0)) + state(x+vec2(0,1)) + state(x+vec2(0,-1))) - 4.0 * state(x);
13 | }
14 |
15 | void main() {
16 | vec2 coord = gl_FragCoord.xy;
17 |
18 | float w = laplacian(coord);
19 | float p = state(coord);
20 | float y = (1.0 - kdamping) * (kdiffuse * w + p);
21 |
22 | gl_FragColor = vec4(y,y,y,y);
23 | }
24 |
--------------------------------------------------------------------------------
/exercises/gpgpu-3/README.md:
--------------------------------------------------------------------------------
1 | # Wave equation
2 |
3 | ## Exercise
4 |
5 | Implement a fragment shader which computes the the next state by explicit Euler integration.
6 |
7 | The previous state is stored in the texture `prevState[0]` and the previous-previous state is in `prevState[1]`. To help get started, a template file called `wave.glsl` has been created in the directory for this lesson.
8 |
9 | **WARNING:** This lesson requires the floating point texture extension. If your GPU/browser do not support this, then this lesson will not work.
10 |
11 | ***
12 |
13 | The (damped) wave equation is similar to the heat equation, except that it has a second order time derivative:
14 |
15 | ```
16 | ( d^2 f d^2 f ) d^2 f
17 | kD * ( ----- + ----- ) - kM * f = -----
18 | ( d x^2 d y^2 ) d t^2
19 | ```
20 |
21 | As before, this equation can be discretized using explicit Euler integration and finite differences. However, because of the second order time dependency it is necessary to buffer two states instead of just one. This leads to the following update rule:
22 |
23 | ```
24 | f(x,y,t+1) = (1 - kdamping) * (
25 | kdiffuse * laplace(f)(x, y, t) +
26 | 2 * f(x,y,t)
27 | ) - f(x,y,t-1)
28 | ```
29 |
30 | Where again laplace(f) is computed using a 5-point stencil:
31 |
32 | ```
33 | laplace(f)(x,y,t) = (
34 | f(x-1,y,t) +
35 | f(x+1,y,t) +
36 | f(x,y-1,t) +
37 | f(x,y+1,t)
38 | ) - 4 * f(x,y,t)
39 | ```
40 |
--------------------------------------------------------------------------------
/exercises/gpgpu-3/files/wave.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D prevState[2];
4 | uniform vec2 stateSize;
5 |
6 | float state0(vec2 x) {
7 | return texture2D(prevState[0], fract(x / stateSize)).r;
8 | }
9 | float state1(vec2 x) {
10 | return texture2D(prevState[1], fract(x / stateSize)).r;
11 | }
12 |
13 | void main() {
14 | vec2 coord = gl_FragCoord.xy;
15 |
16 | //TODO: Solve for next state using a 5-point Laplacian stencil and the explicit Euler rule
17 |
18 | float y = state0(coord);
19 |
20 | gl_FragColor = vec4(y,y,y,1);
21 | }
22 |
--------------------------------------------------------------------------------
/exercises/gpgpu-3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: wave equation
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/gpgpu-3/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/gpgpu-3/shaders/pass-thru.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 | attribute vec2 position;
3 | void main() {
4 | gl_Position = vec4(position, 0.0, 1.0);
5 | }
6 |
--------------------------------------------------------------------------------
/exercises/gpgpu-3/shaders/point-fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform vec4 color;
4 |
5 | void main() {
6 | float r = length(gl_PointCoord - vec2(0.5, 0.5));
7 | if(r > 0.5) {
8 | discard;
9 | }
10 | gl_FragColor = color;
11 | }
--------------------------------------------------------------------------------
/exercises/gpgpu-3/shaders/point-vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec2 ignored;
4 |
5 | uniform vec2 coord;
6 | uniform float size;
7 |
8 | void main() {
9 | gl_Position = vec4(coord, 0, 1);
10 | gl_PointSize = size;
11 | }
--------------------------------------------------------------------------------
/exercises/gpgpu-3/shaders/render.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D state;
4 | uniform vec2 screenSize;
5 |
6 | #define LO vec3(0.1921, 0.0980, 0.1764)
7 | #define HI vec3(1.0, 0.7558, 0.8578)
8 |
9 | void main() {
10 | float x = texture2D(state, gl_FragCoord.xy / screenSize).x;
11 | gl_FragColor = vec4(mix(LO, HI, x), 1.0);
12 | }
13 |
--------------------------------------------------------------------------------
/exercises/gpgpu-3/shaders/update.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform sampler2D prevState[2];
4 | uniform vec2 stateSize;
5 | uniform float kdiffuse, kdamping;
6 |
7 | float state0(vec2 x) {
8 | return texture2D(prevState[0], fract(x / stateSize)).r;
9 | }
10 | float state1(vec2 x) {
11 | return texture2D(prevState[1], fract(x / stateSize)).r;
12 | }
13 |
14 | float laplacian(vec2 x) {
15 | return (state0(x+vec2(-1,0)) + state0(x+vec2(1,0)) + state0(x+vec2(0,1)) + state0(x+vec2(0,-1))) - 4.0 * state0(x);
16 | }
17 |
18 | void main() {
19 | vec2 coord = gl_FragCoord.xy;
20 |
21 | float w = laplacian(coord);
22 | float p0 = state0(coord);
23 | float p1 = state1(coord);
24 | float y = (1.0 - kdamping) * (kdiffuse * w + 2.0 * p0) - p1;
25 |
26 | gl_FragColor = vec4(y,y,y,y);
27 | }
28 |
--------------------------------------------------------------------------------
/exercises/intro-0/README.md:
--------------------------------------------------------------------------------
1 | # Hello, GLSL!
2 |
3 | ## Exercise
4 |
5 | Each exercise has its own directory preloaded with shaders for you to edit.
6 | Generally, there will be
7 | a link like this one that *will
8 | open that directory for you*. If the link does not work for you, you can find
9 | the files for each exercise in the `answers/` directory.
10 |
11 | In this exercise's directory you'll find a file called `hello.glsl` that
12 | exports a function called `brightness`. Currently it returns `-1.0` – simply
13 | change it to `1.0` to pass the exercise.
14 |
15 | ***
16 |
17 | ## How Shader School Works
18 |
19 | For each lesson there is an **expected** and an **actual** shader. The former is
20 | provided by us, and your goal is to simply modify the **actual** shader so that
21 | it matches the expected one.
22 |
23 | ### The Diff Tool
24 |
25 | You'll notice some buttons and a slider below: these are for modifying how the
26 | two are displayed so that you can easily track down any differences between
27 | them.
28 |
29 | By default, "slide" mode will be enabled, which will display your
30 | shader's output on the left and our shader's on the right. "Onion" mode will
31 | allow you to overlay the two – in both cases, you can use the slider to adjust
32 | how much is displayed of either the actual or expected output. There's also
33 | "diff" mode, which will display pixels of different values brighter than the
34 | others. If they're exactly the same, they'll be black. Here,
35 | you can use the slider to adjust the sensitivity of the the comparison.
36 |
37 | ### Verifying the Lesson
38 |
39 | Once you're confident that the two shaders are equivalent, you can click the
40 | green icon above to verify (and hopefully, pass!) the lesson.
41 |
--------------------------------------------------------------------------------
/exercises/intro-0/files/hello.glsl:
--------------------------------------------------------------------------------
1 | float brightness() {
2 | return -1.0;
3 | }
4 |
5 | //Do not change this line
6 | #pragma glslify: export(brightness)
7 |
--------------------------------------------------------------------------------
/exercises/intro-0/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: introduction to glsl
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/intro-0/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(brfs)
27 | .concat(envify)
28 | .concat(live)
29 | .concat(glslify)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/intro-0/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec3 ambient, specular, lightPosition;
4 | uniform float shininess;
5 | varying vec3 fragNormal, fragPosition, lightDirection;
6 |
7 | void main() {
8 | vec3 diffuse = vec3(1.0);
9 | vec3 eyeDirection = normalize(fragPosition);
10 | vec3 normal = normalize(fragNormal);
11 | vec3 light = normalize(lightDirection);
12 |
13 | float lambert = dot(normal, light);
14 | float phong = pow(max(dot(reflect(light, normal), eyeDirection), 0.0), shininess);
15 |
16 | vec3 lightColor = ambient + diffuse * lambert + specular * phong;
17 | vec3 boost = vec3(0.1, 0.3, 0.4) * dot(fragNormal, vec3(0.0, -1.0, 0.0));
18 | gl_FragColor = vec4(lightColor + boost, 1);
19 | }
20 |
--------------------------------------------------------------------------------
/exercises/intro-0/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position, normal;
4 |
5 | uniform mat4 model, view, projection;
6 | uniform mat4 inverseModel, inverseView, inverseProjection;
7 | uniform vec3 lightPosition;
8 | varying vec3 fragNormal, fragPosition, lightDirection;
9 |
10 | void main() {
11 | vec4 viewPosition = view * model * vec4(position, 1.0);
12 | vec4 viewNormal = vec4(normal, 0.0) * inverseModel * inverseView;
13 | vec4 viewLight = view * vec4(lightPosition, 1.0);
14 |
15 | gl_Position = projection * viewPosition;
16 | fragNormal = viewNormal.xyz;
17 | fragPosition = viewPosition.xyz;
18 | lightDirection = viewLight.xyz - viewPosition.xyz;
19 | }
20 |
--------------------------------------------------------------------------------
/exercises/intro-1/README.md:
--------------------------------------------------------------------------------
1 | # Introduction to GLSL
2 |
3 | ## Exercise
4 |
5 | In this lesson's directory you'll
6 | find a file called `hello.glsl` that exports a function called `sum`. Fix this
7 | function to return the sum of its first two arguments.
8 |
9 | ***
10 |
11 | ## Overview of GLSL
12 |
13 | This lesson covers the basics of shader programming with GLSL. In a few words, GLSL is a statically typed imperative programming language. Coming from JavaScript, the syntax of GLSL should be familiar with the same basic control flow structures: if statements and for loops, and semicolons and curly braces for delimiting statements and blocks.
14 |
15 | However, there is one weird thing that makes GLSL different from most other programming languages -- *all GLSL programs must use a finite amount of memory and terminate in a finite time*. This means no infinite loops, no recursion, no memory allocation and no strings.
16 |
17 | ## Variables
18 |
19 | Variables in GLSL must be assigned a type when they are declared (unlike JavaScript) and are lexically scoped. While there are many datatypes in GLSL, to get started we are going to focus on scalar variables, which represent single numerical or logical values. In GLSL, there only three scalar types:
20 |
21 | * `bool` - which stores a logical truth value of either `true` or `false`
22 | * `int` - which stores a signed integer value
23 | * `float` - which is a fractional number
24 |
25 | Here are some examples of how to declare a variable in GLSL:
26 |
27 | ```glsl
28 | //Comments are written with slashes just like in JS
29 |
30 | //Declares an integer called myInt:
31 | //(note semicolons are not optional)
32 | int myInt;
33 |
34 | //Can declare multiple values on one line,
35 | //all of these have type bool
36 | bool a, b, c;
37 |
38 | //Variables can be initialized when
39 | //they are declared
40 | bool someBoolean = true;
41 | ```
42 |
43 | ## Operators
44 |
45 | Scalar datatypes support the same basic arithmetic operations as JavaScript. On floating point numbers, the following operators are all defined: `+,-,*,/,<,>,<=,>=,==,!=`. Additionally, variables can be updated by assignment using the `=` operator, and the related, `+=,-=,*=,/=,++,--` arithmetic assignments. For example,
46 |
47 | ```glsl
48 | //Declare floats x and y
49 | mediump float x = 1.0, y = -2.0;
50 |
51 | //Now: z = x + 3 * y = -5
52 | mediump float z = x + 3.0 * y;
53 |
54 | //Add 1 to x, so now: x = 2
55 | x++;
56 |
57 | //Now: z = -1
58 | z += 2.0 * x;
59 | ```
60 |
61 | ## Procedures
62 |
63 | GLSL allows for complex shaders to be decomposed into subroutines using C-like syntax. These subroutines may or may not return a value by a return statement. Here is a subroutine which adds two integers together:
64 |
65 | ```glsl
66 | //Declare a subroutine called "addTwoInts"
67 | //with return type "int" that accepts two
68 | //arguments, "x" and "y" both int type
69 | int addTwoInts(int x, int y) {
70 | //Use a return statement to return a value
71 | return x + y;
72 | }
73 |
74 | //To declare a subroutine that does not
75 | //return a value, give it the return type "void"
76 | void doNothing() {
77 | }
78 | ```
79 |
--------------------------------------------------------------------------------
/exercises/intro-1/files/hello.glsl:
--------------------------------------------------------------------------------
1 | highp float sum(highp float x, highp float y) {
2 |
3 | //TODO: Implement this function
4 |
5 | return 0.0;
6 | }
7 |
8 | //Do not change this line
9 | #pragma glslify: export(sum)
--------------------------------------------------------------------------------
/exercises/intro-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: introduction to glsl
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/intro-1/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(brfs)
29 | .concat(glslify)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/intro-1/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | varying float value;
3 | void main() {
4 | vec3 color = vec3(sin(value), cos(value+0.7853981633974483), cos(value));
5 | gl_FragColor = vec4(normalize(color), 1.0);
6 | }
--------------------------------------------------------------------------------
/exercises/intro-1/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | attribute vec2 uv;
2 |
3 | uniform mat4 view;
4 | uniform mat4 projection;
5 |
6 | varying float value;
7 |
8 | float expectedFunc(float x, float y) {
9 | return x + y;
10 | }
11 |
12 | void main() {
13 | vec2 coord = (uv / 128.0) - 1.0;
14 | float f = expectedFunc(coord.x, coord.y);
15 | gl_Position = projection * view * vec4(coord.x, f, coord.y, 1.0);
16 | value = f;
17 | }
--------------------------------------------------------------------------------
/exercises/intro-2/files/sides.glsl:
--------------------------------------------------------------------------------
1 | void sideLengths(
2 | highp float hypotenuse,
3 | highp float angleInDegrees,
4 | out highp float opposite,
5 | out highp float adjacent) {
6 |
7 |
8 | //TODO: Calculate side lengths here
9 |
10 | }
11 |
12 | //Do not change this line
13 | #pragma glslify: export(sideLengths)
--------------------------------------------------------------------------------
/exercises/intro-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: qualifiers
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/intro-2/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(brfs)
29 | .concat(glslify)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/intro-2/shaders/expected.glsl:
--------------------------------------------------------------------------------
1 | void sideLengths(
2 | float hypotenuse,
3 | float angleInDegrees,
4 | out float opposite,
5 | out float adjacent) {
6 |
7 | float theta = radians(angleInDegrees);
8 | opposite = hypotenuse * sin(theta);
9 | adjacent = hypotenuse * cos(theta);
10 | }
11 |
12 | //Do not change this line
13 | #pragma glslify: export(sideLengths)
--------------------------------------------------------------------------------
/exercises/intro-2/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1, 1, 1, 1);
3 | }
4 |
--------------------------------------------------------------------------------
/exercises/intro-2/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 | attribute vec4 vertexData;
3 |
4 | uniform highp float angle;
5 |
6 | #pragma glslify: sides=require(./expected.glsl)
7 |
8 | void main() {
9 |
10 | float op, adj;
11 | sides(1.5, angle, op, adj);
12 |
13 | vec2 aVector = vec2(0, op);
14 | vec2 bVector = vec2(adj, 0);
15 | vec2 direction = vertexData.x * aVector + vertexData.y * bVector;
16 | vec2 shift = vertexData.w * normalize(vec2(-direction.y, direction.x)) - vertexData.z * normalize(direction);
17 |
18 | if(vertexData.w + vertexData.z > 0.5) {
19 | shift *= length(direction);
20 | } else {
21 | shift.x = -min(0.8*abs(adj), abs(shift.x));
22 | shift.y = -min(0.8*abs(op), abs(shift.y));
23 | }
24 |
25 | gl_Position = vec4(vec2(-0.5, -0.5) - shift, 0.0, 1.0);
26 | }
27 |
--------------------------------------------------------------------------------
/exercises/intro-3/files/vectors.glsl:
--------------------------------------------------------------------------------
1 | highp vec2 func(highp vec2 a, highp vec2 b) {
2 |
3 | //TODO: Implement the exercise here
4 |
5 | return vec2(1, 0);
6 | }
7 |
8 | //Do not change this line
9 | #pragma glslify: export(func)
--------------------------------------------------------------------------------
/exercises/intro-3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: data types
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/intro-3/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(brfs)
29 | .concat(glslify)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/intro-3/shaders/expected.glsl:
--------------------------------------------------------------------------------
1 | vec2 func(vec2 a, vec2 b) {
2 | return normalize(normalize(a) + normalize(b));
3 | }
4 |
5 | #pragma glslify: export(func)
--------------------------------------------------------------------------------
/exercises/intro-3/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1, 1, 1, 1);
3 | }
4 |
--------------------------------------------------------------------------------
/exercises/intro-3/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 | attribute vec4 vertexData;
3 | uniform vec2 aVector;
4 | uniform vec2 bVector;
5 |
6 | #pragma glslify: func=require(./expected.glsl)
7 |
8 | void main() {
9 | vec2 base = vertexData.x * aVector +
10 | vertexData.y * bVector +
11 | vertexData.z * func(aVector, bVector);
12 | float baseLen = length(base);
13 | float offsetScale = vertexData.w;
14 | vec2 headShift = offsetScale * normalize(vec2(-base.y, base.x)) - abs(offsetScale) * normalize(base);
15 | gl_Position = vec4(base + headShift, 0.0, 1.0);
16 | }
17 |
--------------------------------------------------------------------------------
/exercises/intro-4/README.md:
--------------------------------------------------------------------------------
1 | # Branching
2 |
3 | ## Exercise
4 |
5 | In this exercise write a subroutine to test if a point is contained in a bounding box defined by a pair of upper and lower bounds. A template file called `box.glsl` has been created in the directory for this purpose.
6 |
7 | ***
8 |
9 | ## If statements
10 |
11 | Like JavaScript, GLSL has `if` statements for conditional branching. The syntax is identical:
12 |
13 | ```glsl
14 | if(a < 0.5) {
15 | //Executed only if a < 0.5
16 | } else {
17 | //Executed otherwise
18 | }
19 | ```
20 |
21 | Unlike JavaScript though, branches in GLSL are very expensive so they should be used sparingly.
22 |
23 | ## Comparisons
24 |
25 | GLSL also supports component-wise comparison operations for vectors. These are implemented as routines that take a pair of vectors and return a `bvec` whose entries correspond to the value of the predicate:
26 |
27 | * `lessThan(a,b)`
28 | * `lessThanEqual(a,b)`
29 | * `greaterThan(a,b)`
30 | * `greaterThanEqual(a,b)`
31 | * `equal(a,b)`
32 |
33 | for example: `a < b` == `lessThan(a, b)`
34 |
35 | ## Boolean operations
36 |
37 | Boolean vectors also support the following special aggregate operations:
38 |
39 | * `any(b)` returns true if any component of `b` is true, false otherwise
40 | * `all(b)` returns true if all components of `b` are true, false otherwise
41 | * `not(b)` negates the logical value of the components of `b`
42 |
--------------------------------------------------------------------------------
/exercises/intro-4/files/box.glsl:
--------------------------------------------------------------------------------
1 | bool inBox(highp vec2 lo, highp vec2 hi, highp vec2 p) {
2 |
3 | //Test if the point p is inside the box bounded by [lo, hi]
4 |
5 | return false;
6 | }
7 |
8 |
9 | //Do not change this line or the name of the above function
10 | #pragma glslify: export(inBox)
11 |
--------------------------------------------------------------------------------
/exercises/intro-4/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: logic
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/intro-4/index.js:
--------------------------------------------------------------------------------
1 | var matchFBO = require('../../lib/match-fbo')
2 | var drawTriangle = require('a-big-triangle')
3 | var throttle = require('frame-debounce')
4 | var fit = require('canvas-fit')
5 | var getContext = require('gl-context')
6 | var compare = require('gl-compare')
7 | var createShader = require('glslify')
8 | var now = require('right-now')
9 | var fs = require('fs')
10 |
11 | var container = document.getElementById('container')
12 | var canvas = container.appendChild(document.createElement('canvas'))
13 | var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
14 | var gl = getContext(canvas, render)
15 | var comparison = compare(gl, actual, expected)
16 |
17 | comparison.mode = 'slide'
18 | comparison.amount = 0.5
19 |
20 | require('../common')({
21 | description: readme
22 | , compare: comparison
23 | , canvas: canvas
24 | , test: matchFBO(comparison, 0.99)
25 | , dirname: process.env.dirname
26 | })
27 |
28 | window.addEventListener('resize', fit(canvas), false)
29 |
30 | var actualShader = createShader({
31 | vertex: "attribute vec2 uv;void main() {gl_Position = vec4(uv,0,1);}",
32 | fragment: [
33 | "precision mediump float;",
34 | "uniform vec2 screenSize, boxLo, boxHi;",
35 | "#pragma glslify: inBox=require(" + process.env.file_box_glsl + ")",
36 | "void main() {",
37 | "vec2 q = (gl_FragCoord.xy / screenSize);",
38 | "if(inBox(boxLo, boxHi, q)) {",
39 | "gl_FragColor = vec4(1,1,1,1);",
40 | "} else {",
41 | "gl_FragColor = vec4(0,0,0,1);",
42 | "}",
43 | "}"].join("\n"),
44 | inline: true
45 | })(gl)
46 |
47 | var expectedShader = createShader({
48 | frag: './shaders/fragment.glsl'
49 | , vert: './shaders/vertex.glsl'
50 | })(gl)
51 |
52 | var boxLo = [0,0]
53 | var boxHi = [0,0]
54 |
55 | function render() {
56 | var t = 0.0001 * now()
57 | boxLo = [ 0.5*(1.0-0.8*Math.cos(2*t)), 0.5*(1.0+0.8*Math.cos(t + 1.37)) ]
58 | boxHi = [ boxLo[0] + 0.25*(1.0+0.8*Math.cos(0.9*t + 7)), boxLo[1]+0.25*(1.0+0.8*Math.cos(3*t - 1)) ]
59 |
60 | comparison.run()
61 | comparison.render()
62 | }
63 |
64 | function actual(fbo) {
65 | fbo.shape = [canvas.height, canvas.width]
66 | fbo.bind()
67 | actualShader.bind()
68 | actualShader.uniforms.screenSize = [canvas.width, canvas.height]
69 | actualShader.uniforms.boxLo = boxLo
70 | actualShader.uniforms.boxHi = boxHi
71 | drawTriangle(gl)
72 | }
73 |
74 | function expected(fbo) {
75 | fbo.shape = [canvas.height, canvas.width]
76 | fbo.bind()
77 | expectedShader.bind()
78 | expectedShader.uniforms.screenSize = [canvas.width, canvas.height]
79 | expectedShader.uniforms.boxLo = boxLo
80 | expectedShader.uniforms.boxHi = boxHi
81 | drawTriangle(gl)
82 | }
--------------------------------------------------------------------------------
/exercises/intro-4/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(brfs)
29 | .concat(glslify)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/intro-4/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec2 screenSize, boxLo, boxHi;
4 |
5 | bool inBox(highp vec2 lo, highp vec2 hi, highp vec2 x) {
6 | return all(lessThanEqual(lo, x)) && all(lessThanEqual(x, hi));
7 | }
8 |
9 | void main() {
10 | vec2 q = (gl_FragCoord.xy / screenSize);
11 | if(inBox(boxLo, boxHi, q)) {
12 | gl_FragColor = vec4(1,1,1,1);
13 | } else {
14 | gl_FragColor = vec4(0,0,0,1);
15 | }
16 | }
--------------------------------------------------------------------------------
/exercises/intro-4/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | attribute vec2 uv;
2 | void main() {
3 | gl_Position = vec4(uv,0,1);
4 | }
--------------------------------------------------------------------------------
/exercises/intro-5/README.md:
--------------------------------------------------------------------------------
1 | # Loops
2 |
3 |
4 | ## Exercise
5 |
6 | Implement a function which tests whether a point is inside the Mandelbrot set after 100 iterations. The Mandelbrot set is a fractal, which is defined by iterating a chaotic map on the complex plane and testing which points converge. Given an input point `c`, one iteration of the Mandelbrot function is defined as follows:
7 |
8 | ```glsl
9 | vec2 mandelbrot(vec2 z, vec2 c) {
10 | return vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
11 | }
12 | ```
13 |
14 | And some iterations of the map are given as follows:
15 |
16 | ```glsl
17 | vec2 z0 = vec2(0.0,0.0); //No iterations
18 | vec2 z1 = mandelbrot(z0, c); // 1 iteration
19 | vec2 z2 = mandelbrot(z1, c); // 2 iterations
20 |
21 | // ... and so on ...
22 |
23 | vec2 zn = mandelbrot(zn_1, c); // n iterations
24 | ```
25 |
26 | As a general principle, say that a point in the Mandelbrot set diverges if it has a magnitude greater than 2, so we will classify points as inside the set if their magnitude is less than 2:
27 |
28 | ```glsl
29 | bool mandelbrotConverges(vec2 z) {
30 | return length(z) < 2.0;
31 | }
32 | ```
33 |
34 | The Mandelbrot set is the collection of all points which do not diverge. Write a function which given a test point `c` tests if after 100 iterations it is still inside the Mandelbrot set. To get started, a file `mandelbrot.glsl` has been created in this project's directory.
35 |
36 | ***
37 |
38 | ## Loops
39 |
40 | GLSL supports looping, but with the one catch that the number of times the loop executes must be statically determined and bounded. For example, GLSL supports `for` loops just like JavaScript:
41 |
42 | ```glsl
43 | float x = 0.0;
44 | for(int i=0; i<100; ++i) {
45 | x += i; //Executes 100 times
46 | }
47 | ```
48 |
49 | It is also possible to write `while` loops, again with the restriction that the loop must terminate.
50 |
51 | ```glsl
52 | int i = 0;
53 | while(i < 10) {
54 | i = i + 1;
55 | }
56 | ```
57 |
58 | The requirement that loops terminate in a finite number of iterations makes `while` loops a bit trickier to use than `for` loops. It is also possible to terminate loops early using the `break` keyword, or skip an iteration using `continue`, just like in JavaScript.
--------------------------------------------------------------------------------
/exercises/intro-5/files/mandelbrot.glsl:
--------------------------------------------------------------------------------
1 | bool mandelbrot(highp vec2 c) {
2 |
3 | //Test if the point c is inside the mandelbrot set after 100 iterations
4 | vec2 z = vec2(0.0);
5 |
6 | return false;
7 | }
8 |
9 |
10 | //Do not change this line or the name of the above function
11 | #pragma glslify: export(mandelbrot)
12 |
--------------------------------------------------------------------------------
/exercises/intro-5/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: control flow
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/intro-5/index.js:
--------------------------------------------------------------------------------
1 | var matchFBO = require('../../lib/match-fbo')
2 | var drawTriangle = require('a-big-triangle')
3 | var throttle = require('frame-debounce')
4 | var fit = require('canvas-fit')
5 | var getContext = require('gl-context')
6 | var compare = require('gl-compare')
7 | var createShader = require('glslify')
8 | var fs = require('fs')
9 |
10 | var container = document.getElementById('container')
11 | var canvas = container.appendChild(document.createElement('canvas'))
12 | var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
13 | var gl = getContext(canvas, render)
14 | var comparison = compare(gl, actual, expected)
15 |
16 | comparison.mode = 'slide'
17 | comparison.amount = 0.5
18 |
19 | require('../common')({
20 | description: readme
21 | , dirname: process.env.dirname
22 | , compare: comparison
23 | , canvas: canvas
24 | , test: matchFBO(comparison, 0.99)
25 | })
26 |
27 | window.addEventListener('resize', fit(canvas), false)
28 |
29 | var actualShader = createShader({
30 | vertex: "attribute vec2 uv;void main() {gl_Position = vec4(uv,0,1);}",
31 | fragment: [
32 | "precision highp float;",
33 | "uniform vec2 screenSize;",
34 | "#pragma glslify: fractal=require(" + process.env.file_mandelbrot_glsl + ")",
35 | "void main() {",
36 | "vec2 q = 2.0 * (gl_FragCoord.xy / screenSize) - vec2(1.0,1.0);",
37 | "q.x *= screenSize.x / screenSize.y;",
38 | "q.x -= 0.5;",
39 | "vec4 color = vec4(0,0,0,1);",
40 | "if(fractal(q)) {",
41 | "color = vec4(1,1,1,1);",
42 | "}",
43 | "gl_FragColor = color;",
44 | "}"].join("\n"),
45 | inline: true
46 | })(gl)
47 |
48 |
49 | var expectedShader = createShader({
50 | frag: './shaders/fragment.glsl'
51 | , vert: './shaders/vertex.glsl'
52 | })(gl)
53 |
54 | function render() {
55 | comparison.run()
56 | comparison.render()
57 | }
58 |
59 | function actual(fbo) {
60 | fbo.shape = [canvas.height, canvas.width]
61 | fbo.bind()
62 | actualShader.bind()
63 | actualShader.uniforms.screenSize = [canvas.width, canvas.height]
64 | drawTriangle(gl)
65 | }
66 |
67 | function expected(fbo) {
68 | fbo.shape = [canvas.height, canvas.width]
69 | fbo.bind()
70 | expectedShader.bind()
71 | expectedShader.uniforms.screenSize = [canvas.width, canvas.height]
72 | drawTriangle(gl)
73 | }
--------------------------------------------------------------------------------
/exercises/intro-5/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(brfs)
29 | .concat(glslify)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/intro-5/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec2 screenSize;
4 |
5 | bool mandelbrot(vec2 q) {
6 | vec2 z = vec2(0.0, 0.0);
7 | for(int i=0; i<=100; ++i) {
8 | z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + q;
9 | }
10 | return length(z) < 2.0;
11 | }
12 |
13 | void main() {
14 | vec2 q = 2.0 * (gl_FragCoord.xy / screenSize) - vec2(1.0,1.0);
15 | q.x *= screenSize.x / screenSize.y;
16 | q.x -= 0.5;
17 | vec4 color = vec4(0,0,0,1);
18 | if(mandelbrot(q)) {
19 | color = vec4(1,1,1,1);
20 | }
21 | gl_FragColor = color;
22 | }
--------------------------------------------------------------------------------
/exercises/intro-5/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | attribute vec2 uv;
2 | void main() {
3 | gl_Position = vec4(uv,0,1);
4 | }
--------------------------------------------------------------------------------
/exercises/intro-6/README.md:
--------------------------------------------------------------------------------
1 | # Matrices
2 |
3 | ## Exercise
4 |
5 | Write a function to raise a 2x2 matrix `m` to the nth power, where 0 <= n < 16 is an integer. To get started, edit the template file `mpow.glsl` in the directory for this project.
6 |
7 | ***
8 |
9 | Besides vectors, GLSL has special datatypes for representing low dimensional matrices. There are only 3 of these: `mat2, mat3, mat4` which correspond to a 2x2, 3x3 and 4x4 square matrix respectively:
10 |
11 | ```glsl
12 | //Create a a 2x2 identity matrix. Note matrix
13 | //constructors are in column major order.
14 | mat2 I = mat2(1.0, 0.0,
15 | 0.0, 1.0);
16 |
17 | //Equivalently,
18 | mat2 I = mat2(1.0);
19 |
20 | //Matrices can also be constructed by
21 | //giving columns:
22 | vec3 a = vec3(0, 1, 0);
23 | vec3 b = vec3(2, 0, 0);
24 | vec3 c = vec3(0, 0, 4);
25 | mat3 J = mat3(a, b, c);
26 |
27 | //Now:
28 | //J = mat3(0, 2, 0,
29 | // 1, 0, 0,
30 | // 0, 0, 4);
31 | ```
32 |
33 | The columns of matrices can be accessed using the square brackets. For example:
34 |
35 | ```glsl
36 | mat3 m = mat3(1.1, 2.1, 3.1,
37 | 1.2, 2.2, 3.2,
38 | 1.3, 2.3, 3.3);
39 |
40 | //Read out first column of m
41 | vec3 a = m[0];
42 |
43 | //Now: a = vec3(1.1, 2.1, 3.1);
44 | ```
45 |
46 | ### Arithmetic
47 |
48 | Matrices support similar arithmetic operations to vectors. Both scalar addition and vector addition are defined:
49 |
50 | ```glsl
51 | mat2 m = mat2(1, 2,
52 | 3, 4);
53 | mat2 w = mat2(7, 8,
54 | 9, 10);
55 |
56 | //Component wise addition
57 | mat2 h = m + w;
58 |
59 | //Now: h = mat2(8, 10,
60 | // 12, 14)
61 |
62 | //Scalar multiplication
63 | mat2 j = 2.0 * m;
64 | //Now: j = mat2(2, 4,
65 | // 6, 8)
66 | ```
67 |
68 | The multiplication or `*` operator for matrices does not have the same meaning as for vectors. Instead of being applied component-wise, it is used to implement matrix multiplication in the linear algebra sense. If you want to do component-wise multiplication, you can use the `matrixCompMult` function:
69 |
70 | ```glsl
71 | mat2 m = mat2(1, 2,
72 | 3, 4);
73 | mat2 w = mat2(7, 8,
74 | 9, 10);
75 |
76 | mat2 q = matrixCompMult(m, w);
77 |
78 | //q = mat2( 7, 16,
79 | // 27, 40)
80 | ```
81 |
82 | Instead, the `*` operator has the effect of multiplying matrices and transforming vectors:
83 |
84 | ```glsl
85 | mat2 m = mat2(1, 2,
86 | 3, 4);
87 |
88 | vec2 v = m * vec2(1, 2); //v = vec2(7, 10)
89 |
90 | //In GLSL, switching order of arguments is equivalent
91 | //to transposing:
92 | vec2 u = vec2(1, 2) * m; //u = vec2(5, 11)
93 | ```
94 |
--------------------------------------------------------------------------------
/exercises/intro-6/files/mpow.glsl:
--------------------------------------------------------------------------------
1 | mat2 matrixPower(highp mat2 m, int n) {
2 |
3 | //Raise the matrix m to nth power
4 |
5 | // For example:
6 | //
7 | // matrixPower(m, 2) = m * m
8 | //
9 |
10 | return mat2(1.0);
11 | }
12 |
13 | //Do not change this line or the name of the above function
14 | #pragma glslify: export(matrixPower)
--------------------------------------------------------------------------------
/exercises/intro-6/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: matrices
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/intro-6/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(brfs)
29 | .concat(glslify)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/intro-6/shaders/expected.glsl:
--------------------------------------------------------------------------------
1 | mat2 matrixPower(mat2 m1, int n) {
2 | mat2 m2 = m1 * m1;
3 | mat2 m4 = m2 * m2;
4 | mat2 m8 = m4 * m4;
5 |
6 | mat2 result = mat2(1.0);
7 |
8 | if(n >= 8) {
9 | n -= 8;
10 | result *= m8;
11 | }
12 | if(n >= 4) {
13 | n -= 4;
14 | result *= m4;
15 | }
16 | if(n >= 2) {
17 | n -= 2;
18 | result *= m2;
19 | }
20 | if(n >= 1) {
21 | result *= m1;
22 | }
23 |
24 | return result;
25 | }
26 |
27 | //Do not change this line or the name of the above function
28 | #pragma glslify: export(matrixPower)
--------------------------------------------------------------------------------
/exercises/intro-6/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform sampler2D texture;
4 | varying vec2 coord;
5 |
6 | void main() {
7 | gl_FragColor = texture2D(texture, coord);
8 | }
--------------------------------------------------------------------------------
/exercises/intro-6/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | #pragma glslify: matrixPower = require(./expected.glsl)
4 |
5 | attribute vec2 position;
6 | uniform int n;
7 | uniform mat2 mat;
8 | varying vec2 coord;
9 |
10 | void main() {
11 | gl_Position = vec4(position, 0, 1);
12 | vec2 textureCoordinate = vec2(0.5,0.5) - 0.5 * position;
13 | coord = matrixPower(mat, n) * textureCoordinate;
14 | }
--------------------------------------------------------------------------------
/exercises/lesson-plan.md:
--------------------------------------------------------------------------------
1 | # Core lesson plan
2 |
3 | ## Basic GLSL
4 |
5 | * introduction to glsl
6 | * vectors and matrices
7 | * loops and control flow, preprocessor, structs and arrays
8 |
9 | ## Fragment shaders
10 |
11 | * introduction to fragment shaders
12 | * uniform variables & textures
13 |
14 | ## Vertex shaders
15 |
16 | * introduction to vertex shaders (wire cube)
17 | * varying-variables/hello shader (draw flat colored polygons with model/view/projection matrix)
18 |
19 | ## Projective geometry
20 |
21 | * Clip coordinates
22 | * Transformations
23 |
24 | ## Lighting
25 |
26 | * flat shading
27 | * lambertian
28 | * blinn-phong
29 | * point lights
30 | * spot lights
31 | * multiple light sources
32 | * oren-nayar (advanced)
33 | * cook-torrance (advanced)
34 | * (shadows and reflections might be too much, and require multipass stuff)
35 |
36 | ## NPR
37 |
38 | * cel shading
39 | * gooch shading
40 | * real-time hatching (praun et al)
41 |
42 | # Optional stuff
43 |
44 | ## Color space
45 |
46 | * rgb coordinates
47 | * hsl
48 | * dynamic range/tonemapping
49 |
50 | ## Image processing
51 |
52 | * convolutions
53 | + blur
54 | + edge enhancement maybe
55 | * morphological
56 | + dilate/erode
57 | + open/close
58 | * warping
59 | * mip mapping
60 |
61 | ## Antialiasing
62 |
63 | ## Raytracing
64 |
65 | * perhaps have students implement a simple glsl raytracer for some quadric surfaces
66 |
67 | ## More shaders
68 |
69 | * point sprites/billboards
70 | * cube maps
71 | * raycasting
72 | * bump mapping/normal mapping
73 | * cone tracing
74 | * blending/transparency
75 |
76 | ## Extensions
77 |
78 | * dFdx/dFdy extensions
79 | * multiple render targets
80 | * frag_depth
81 |
82 | ## glslify
83 |
84 | * some example using multiple files
85 | * some example using a module from npm (maybe glsl-random?)
86 |
87 | ## Advanced shaders
88 |
89 | * ssao
90 |
91 | ## feedback effects
92 |
93 | * simple texture feedback
94 | * cellular automata
95 | * separate logic and render shaders
96 | * floating point textures
97 | * particle system: gl.POINTS
--------------------------------------------------------------------------------
/exercises/light-1/README.md:
--------------------------------------------------------------------------------
1 | # Lighting models
2 |
3 | ## Exercise
4 |
5 | As a warm up, we will start with the very simplest lighting model, which is called "flat shading". In flat shading, the light reflected from any surface to the detector is assumed to be constant. That is, there is some parameter called `kA` which just determines the color of each fragment.
6 |
7 | The files `vertex.glsl` and `fragment.glsl` have been provided as well as the appropriate uniform and attribute variables for the camera. Apply the model, view and projection transformation matrices as in the `GEOM 1` exercise.
8 |
9 | ***
10 |
11 | ## Aside on the nature of rendering
12 |
13 | Physically, images are formed by the interaction of lightwaves with some detector (like a camera CCD, eyeball retina, etc.). These lightwaves are formed by the interaction of the electromagnetic field with different materials. These interactions include reflection, transmission, absorption and emission of electromagnetic radiation. In human terms, visible light waves are high frequency (with wavelengths on the order of nanometers), and travel at incredible speeds, so to us humans light's wave-like nature is imperceptible. As a result, physical images can be well approximated using geometrical objects, which replaces light waves with "rays".
14 |
15 | The physical interpretation of a ray in geometric optics is that it is a line perpendicular to the wave front representing the amount of energy in the wave at some particular frequency, ignoring polarization. Natural light is composed of infinitely many different frequencies, however human vision can only distinguish between three different classes of frequencies: red, green and blue (though rarely there are some mutants who can see up to 4 or 5 different types of colors). As a result, if we are rendering images we only need to track the amount of energy in the red, green and blue color bands for each ray when we are ray tracing the image.
16 |
17 | In high end computer graphics, ray tracing is widely used to generate physically realistic images. But for interactive applications like games, even the ray tracing approximation of light is still too slow. Instead, real time applications must usually make do with even more limited models of light transport and interactions. In this section and its sequels, we will discuss a few classical models of light transport and you will get to try implementing them as an exercise. These models make different assumptions about the types of materials they represent, which lead to different looking surfaces. Since they are physically motivated, they are generally consistent with what would be expected from a realistic theory of light. Moreover, despite being over-simplified, they are quite capable of producing compelling images and often serve as a useful starting point for developing newer or customized models of light.
18 |
--------------------------------------------------------------------------------
/exercises/light-1/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform mat4 model, view, projection;
4 | uniform vec3 ambient;
5 |
6 | void main() {
7 | gl_FragColor = vec4(1,1,1,1);
8 | }
--------------------------------------------------------------------------------
/exercises/light-1/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position;
4 | uniform mat4 model, view, projection;
5 | uniform vec3 ambient;
6 |
7 | void main() {
8 | gl_Position = vec4(position, 1);
9 | }
10 |
--------------------------------------------------------------------------------
/exercises/light-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: flat shading
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/light-1/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/light-1/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform vec3 ambient;
3 |
4 | void main() {
5 | gl_FragColor = vec4(ambient, 1);
6 | }
--------------------------------------------------------------------------------
/exercises/light-1/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | attribute vec3 position;
3 | uniform mat4 model, view, projection;
4 |
5 | void main() {
6 | gl_Position = projection * view * model * vec4(position, 1.0);
7 | }
--------------------------------------------------------------------------------
/exercises/light-2/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform mat4 model;
4 | uniform mat4 view;
5 | uniform mat4 projection;
6 |
7 | uniform mat4 inverseModel;
8 | uniform mat4 inverseView;
9 | uniform mat4 inverseProjection;
10 |
11 | uniform vec3 ambient;
12 | uniform vec3 diffuse;
13 | uniform vec3 lightDirection;
14 |
15 | void main() {
16 | gl_FragColor = vec4(1,1,1,1);
17 | }
--------------------------------------------------------------------------------
/exercises/light-2/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position;
4 | attribute vec3 normal;
5 |
6 | uniform mat4 model;
7 | uniform mat4 view;
8 | uniform mat4 projection;
9 |
10 | uniform mat4 inverseModel;
11 | uniform mat4 inverseView;
12 | uniform mat4 inverseProjection;
13 |
14 | uniform vec3 ambient;
15 | uniform vec3 diffuse;
16 | uniform vec3 lightDirection;
17 |
18 | void main() {
19 | gl_Position = vec4(position, 1);
20 | }
21 |
--------------------------------------------------------------------------------
/exercises/light-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: diffuse light
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/light-2/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/light-2/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec3 ambient, diffuse, lightDirection;
4 | varying vec3 fragNormal;
5 |
6 | void main() {
7 | vec3 lightColor = ambient + diffuse * max(dot(normalize(fragNormal), normalize(lightDirection)), 0.0);
8 | gl_FragColor = vec4(lightColor, 1);
9 | }
--------------------------------------------------------------------------------
/exercises/light-2/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 | attribute vec3 position, normal;
3 | uniform mat4 model, view, projection;
4 | uniform mat4 inverseModel, inverseView, inverseProjection;
5 | varying vec3 fragNormal;
6 |
7 | void main() {
8 | vec4 worldPosition = model * vec4(position, 1.0);
9 | vec4 worldNormal = vec4(normal, 0.0) * inverseModel * inverseView;
10 |
11 | gl_Position = projection * view * worldPosition;
12 | fragNormal = worldNormal.xyz;
13 | }
--------------------------------------------------------------------------------
/exercises/light-3/README.md:
--------------------------------------------------------------------------------
1 | # Phong lighting
2 |
3 | ## Exercise
4 |
5 | Implement the Phong lighting model in GLSL. To get started, a vertex and fragment shader have been created in the directory for this lesson. The input to these shaders will be as follows:
6 |
7 | ### Attributes
8 |
9 | * `position` the position of each vertex
10 | * `normal` the normal vector the mesh at each vertex
11 |
12 | ### Uniforms
13 |
14 | * `model, view, projection` The forward model, view and projection matrices
15 | * `inverseModel, inverseView, inverseProjection` The inverse of the above transformation matrices
16 | * `ambient` the color and intensity of the ambient light parameter
17 | * `diffuse` the color and intensity of the diffuse light
18 | * `specular` the color and intensity of the specular light
19 | * `lightDirection` the direction of the incoming light
20 | * `shininess` the exponent in the Phong specular parameter
21 |
22 | ### Hint
23 |
24 | To compute the eye direction, use the fact that this is the same as the normalized view position. (You should prove to yourself that this is the case.)
25 |
26 | For finding the half angle between the eye and light direction, remember the exercise from `INTRO 2`.
27 |
28 | ***
29 |
30 | ## Phong Lighting
31 |
32 | The Phong lighting model is probably the most widely used lighting model in computer graphics. It models shiny, or specular, materials like metals, plastic and so on. Physically, what these materials scatter light by hard reflections instead of smoothly diffusing outward. The Phong lighting model approximates this process by adding in an extra term to account for these reflections.
33 |
34 | The general idea is that we first reflect the incoming light direction about the surface normal, then we project this vector onto the view axis and measure its length. The amount of reflected specular light is then assumed to be proportional to some power of this length.
35 |
36 | Explicitly, the parameters in this model are:
37 |
38 | * The light direction
39 | * The surface normal
40 | * The view or "eye" direction
41 | * The "shininess" exponent
42 |
43 | In GLSL, we can write out the Phong lighting weight compactly using the built in functions:
44 |
45 | ```glsl
46 | float phongWeight(
47 | vec3 lightDirection,
48 | vec3 surfaceNormal,
49 | vec3 eyeDirection,
50 | float shininess
51 | ) {
52 | //First reflect light by surface normal
53 | vec3 rlight = reflect(lightDirection, surfaceNormal);
54 |
55 | //Next find the projected length of the reflected
56 | //light vector in the view direction
57 | float eyeLight = max(dot(rlight, eyeDirection), 0.0);
58 |
59 | //Finally exponentiate by the shininess factor
60 | return pow(eyeLight, shininess);
61 | }
62 | ```
63 |
64 | The Phong lighting model is then usually combined with diffuse and ambient terms to give a complete lighting model:
65 |
66 | ```glsl
67 | light = (
68 | ambient
69 | + diffuse * lambert
70 | + specular * phong
71 | )
72 | ```
73 |
--------------------------------------------------------------------------------
/exercises/light-3/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform mat4 model, view, projection;
4 | uniform mat4 inverseModel, inverseView, inverseProjection;
5 | uniform vec3 ambient, diffuse, specular, lightDirection;
6 | uniform float shininess;
7 |
8 | void main() {
9 | gl_FragColor = vec4(1,1,1,1);
10 | }
--------------------------------------------------------------------------------
/exercises/light-3/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position, normal;
4 | uniform mat4 model, view, projection;
5 | uniform mat4 inverseModel, inverseView, inverseProjection;
6 | uniform vec3 ambient, diffuse, specular, lightDirection;
7 | uniform float shininess;
8 |
9 | void main() {
10 | gl_Position = vec4(position, 1);
11 | }
12 |
--------------------------------------------------------------------------------
/exercises/light-3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: phong lighting
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/light-3/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/light-3/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec3 ambient, diffuse, specular, lightDirection;
4 | uniform float shininess;
5 | varying vec3 fragNormal, fragPosition;
6 |
7 | void main() {
8 | vec3 eyeDirection = normalize(fragPosition);
9 | vec3 normal = normalize(fragNormal);
10 | vec3 light = normalize(lightDirection);
11 |
12 | float lambert = max(dot(normal, light), 0.0);
13 | float phong = pow(max(dot(reflect(light, normal), eyeDirection), 0.0), shininess);
14 |
15 | vec3 lightColor = ambient + diffuse * lambert + specular * phong;
16 | gl_FragColor = vec4(lightColor, 1);
17 | }
18 |
--------------------------------------------------------------------------------
/exercises/light-3/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position, normal;
4 |
5 | uniform mat4 model, view, projection;
6 | uniform mat4 inverseModel, inverseView, inverseProjection;
7 | varying vec3 fragNormal, fragPosition;
8 |
9 | void main() {
10 | vec4 viewPosition = view * model * vec4(position, 1.0);
11 | vec4 viewNormal = vec4(normal, 0.0) * inverseModel * inverseView;
12 |
13 | gl_Position = projection * viewPosition;
14 | fragNormal = viewNormal.xyz;
15 | fragPosition = viewPosition.xyz;
16 | }
--------------------------------------------------------------------------------
/exercises/light-4/README.md:
--------------------------------------------------------------------------------
1 | # Point lights
2 |
3 | ## Exercise
4 |
5 | In this exercise, generalize the Phong lighting shader from the previous lesson to support point light sources. The `lightDirection` uniform has been replaced by a new uniform called `lightPosition`.
6 |
7 | Template files have been created in the directory for this lesson, though you may find it expedient to copy your work from the previous directory.
8 |
9 | ***
10 |
11 | Up until now, we have assumed that all our light sources are infinitely far away and so their geometry can be modelled by a single direction vector. Here, we will relax this assumption somewhat by generalizing to the situation where the lights are represented by idealized points which emit light uniformly in all directions.
12 |
13 | To modify our previous lighting model to support point lights, all we need to do is replace the direction vector with a ray extending from the point on the surface to the light source. That is, our new light direction becomes:
14 |
15 | ```glsl
16 | vec3 lightDirection = normalize(
17 | lightPosition - surfacePosition
18 | );
19 | ```
20 |
--------------------------------------------------------------------------------
/exercises/light-4/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform mat4 model;
4 | uniform mat4 view;
5 | uniform mat4 projection;
6 |
7 | uniform mat4 inverseModel;
8 | uniform mat4 inverseView;
9 | uniform mat4 inverseProjection;
10 |
11 | uniform vec3 ambient;
12 | uniform vec3 diffuse;
13 | uniform vec3 specular;
14 |
15 | uniform vec3 lightPosition;
16 |
17 | uniform float shininess;
18 |
19 | void main() {
20 | gl_FragColor = vec4(1,1,1,1);
21 | }
--------------------------------------------------------------------------------
/exercises/light-4/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position;
4 | attribute vec3 normal;
5 |
6 | uniform mat4 model;
7 | uniform mat4 view;
8 | uniform mat4 projection;
9 |
10 | uniform mat4 inverseModel;
11 | uniform mat4 inverseView;
12 | uniform mat4 inverseProjection;
13 |
14 | uniform vec3 lightPosition;
15 |
16 | void main() {
17 | gl_Position = vec4(position, 1);
18 | }
19 |
--------------------------------------------------------------------------------
/exercises/light-4/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: point lights
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/light-4/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/light-4/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec3 ambient;
4 | uniform vec3 diffuse;
5 | uniform vec3 specular;
6 | uniform vec3 lightPosition;
7 |
8 | uniform float shininess;
9 |
10 | varying vec3 fragNormal;
11 | varying vec3 fragPosition;
12 | varying vec3 lightDirection;
13 |
14 | void main() {
15 | vec3 eyeDirection = normalize(fragPosition);
16 | vec3 normal = normalize(fragNormal);
17 | vec3 light = normalize(lightDirection);
18 |
19 | float lambert = max(dot(normal, light), 0.0);
20 | float phong = pow(max(dot(reflect(light, normal), eyeDirection), 0.0), shininess);
21 |
22 | vec3 lightColor = ambient + diffuse * lambert + specular * phong;
23 | gl_FragColor = vec4(lightColor, 1);
24 | }
--------------------------------------------------------------------------------
/exercises/light-4/shaders/point-fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform vec3 diffuse;
3 | void main() {
4 | gl_FragColor = vec4(1,1,1,1);
5 | }
--------------------------------------------------------------------------------
/exercises/light-4/shaders/point-vertex.glsl:
--------------------------------------------------------------------------------
1 | attribute vec3 position;
2 | uniform mat4 model, view, projection;
3 | uniform vec3 lightPosition;
4 | void main() {
5 | gl_Position = projection * view * model * vec4(lightPosition + 0.0 * position, 1);
6 | gl_PointSize = 10.0;
7 | }
8 |
--------------------------------------------------------------------------------
/exercises/light-4/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position;
4 | attribute vec3 normal;
5 |
6 | uniform mat4 model;
7 | uniform mat4 view;
8 | uniform mat4 projection;
9 |
10 | uniform mat4 inverseModel;
11 | uniform mat4 inverseView;
12 | uniform mat4 inverseProjection;
13 |
14 | uniform vec3 lightPosition;
15 |
16 | varying vec3 fragNormal;
17 | varying vec3 fragPosition;
18 | varying vec3 lightDirection;
19 |
20 | void main() {
21 | vec4 viewPosition = view * model * vec4(position, 1.0);
22 | vec4 viewNormal = vec4(normal, 0.0) * inverseModel * inverseView;
23 | vec4 viewLight = view * vec4(lightPosition, 1.0);
24 |
25 | gl_Position = projection * viewPosition;
26 | fragNormal = viewNormal.xyz;
27 | fragPosition = viewPosition.xyz;
28 | lightDirection = viewLight.xyz - viewPosition.xyz;
29 | }
--------------------------------------------------------------------------------
/exercises/light-5/README.md:
--------------------------------------------------------------------------------
1 | # Multiple light sources
2 |
3 | ## Exercise
4 |
5 | Modify the point light shader from the previous lesson to support multiple light sources. Template files for this purpose have been created in the directory for this lesson. The file `light.glsl` is used to define the datatype of the light values. Use these parameters to apply multiple phong lighting contributions in the shader.
6 |
7 | ***
8 |
9 | The Phong lighting model can be extended to support multiple lights by summing up their individual contributions. More generally, if `light0` and `light1` are light values from two different sources, their combined light value is:
10 |
11 | ```glsl
12 | vec3 combinedLight(vec3 light0, vec3 light1) {
13 | return light0 + light1;
14 | }
15 | ```
16 |
17 | In the context of the phong lighting model, the ambient component is usually factored out and each individual point light is given a separate diffuse and specular component.
18 |
19 | ## Structs
20 |
21 | To simplify describing multiple lights, it will be helpful to introduce the concept of a `struct`. These are declared in GLSL using the `struct` keyword. For example, here is one way to set up a struct for a point light source:
22 |
23 | ```glsl
24 | //Declare a datatype for a point light
25 | struct PointLight {
26 | vec3 diffuse;
27 | vec3 specular;
28 | vec3 position;
29 | float shininess;
30 | };
31 |
32 | //Declare a single point light called light
33 | PointLight light;
34 |
35 | //Set the color of the light to red (1,0,0)
36 | light.color = vec3(1, 0, 0);
37 | ```
38 |
39 | ## Arrays
40 |
41 | GLSL also supports arrays, using the same syntax as C. Just like JavaScript array indexes start from `0`, though unlike in JavaScript their size must be declared in advance. For example, here is how to declare an array of 10 point lights in GLSL:
42 |
43 | ```glsl
44 | //Declare an array of 10 point lights
45 | //called "lights"
46 | PointLight lights[10];
47 |
48 | //Modify the first light in the array
49 | lights[0].radius = 100.0;
50 | ```
51 |
--------------------------------------------------------------------------------
/exercises/light-5/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | #pragma glslify: PointLight = require(./light.glsl)
4 |
5 | uniform mat4 model;
6 | uniform mat4 view;
7 | uniform mat4 projection;
8 |
9 | uniform mat4 inverseModel;
10 | uniform mat4 inverseView;
11 | uniform mat4 inverseProjection;
12 |
13 | uniform vec3 ambient;
14 |
15 | uniform PointLight lights[4];
16 |
17 | void main() {
18 | gl_FragColor = vec4(1,1,1,1);
19 | }
--------------------------------------------------------------------------------
/exercises/light-5/files/light.glsl:
--------------------------------------------------------------------------------
1 | //This is the light datatype
2 | struct PointLight {
3 | vec3 diffuse;
4 | vec3 specular;
5 | vec3 position;
6 | float shininess;
7 | };
8 |
9 | //Export the point light data type
10 | #pragma glslify: export(PointLight)
--------------------------------------------------------------------------------
/exercises/light-5/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | #pragma glslify: PointLight = require(./light.glsl)
4 |
5 | attribute vec3 position;
6 | attribute vec3 normal;
7 |
8 | uniform mat4 model;
9 | uniform mat4 view;
10 | uniform mat4 projection;
11 |
12 | uniform mat4 inverseModel;
13 | uniform mat4 inverseView;
14 | uniform mat4 inverseProjection;
15 |
16 | uniform vec3 ambient;
17 |
18 | uniform PointLight lights[4];
19 |
20 | void main() {
21 | gl_Position = vec4(position, 1);
22 | }
23 |
--------------------------------------------------------------------------------
/exercises/light-5/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: multiple lights
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/light-5/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/light-5/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | #pragma glslify: PointLight = require(./light.glsl)
4 |
5 | uniform vec3 ambient;
6 | uniform PointLight lights[4];
7 |
8 | varying vec3 fragNormal;
9 | varying vec3 fragPosition;
10 | varying vec3 lightDirection[4];
11 |
12 | void main() {
13 | vec3 eyeDirection = normalize(fragPosition);
14 | vec3 normal = normalize(fragNormal);
15 | vec3 lightColor = ambient;
16 |
17 | for(int i=0; i<4; ++i) {
18 | vec3 light = normalize(lightDirection[i]);
19 | float lambert = max(dot(normal, light), 0.0);
20 | float phong = pow(max(dot(reflect(light, normal), eyeDirection), 0.0), lights[i].shininess);
21 | lightColor += lambert * lights[i].diffuse + phong * lights[i].specular;
22 | }
23 |
24 | gl_FragColor = vec4(lightColor, 1);
25 | }
--------------------------------------------------------------------------------
/exercises/light-5/shaders/light.glsl:
--------------------------------------------------------------------------------
1 | //This is the light datatype
2 | struct PointLight {
3 | vec3 diffuse;
4 | vec3 specular;
5 | vec3 position;
6 | float shininess;
7 | };
8 |
9 | //Export the point light data type
10 | #pragma glslify: export(PointLight)
--------------------------------------------------------------------------------
/exercises/light-5/shaders/point-fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform vec3 diffuse;
3 | void main() {
4 | gl_FragColor = vec4(diffuse,1);
5 | }
--------------------------------------------------------------------------------
/exercises/light-5/shaders/point-vertex.glsl:
--------------------------------------------------------------------------------
1 | attribute vec3 position;
2 | uniform mat4 model, view, projection;
3 | uniform vec3 lightPosition;
4 | void main() {
5 | gl_Position = projection * view * model * vec4(lightPosition + 0.0 * position, 1);
6 | gl_PointSize = 10.0;
7 | }
8 |
--------------------------------------------------------------------------------
/exercises/light-5/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | #pragma glslify: PointLight = require(./light.glsl)
4 |
5 | attribute vec3 position;
6 | attribute vec3 normal;
7 |
8 | uniform mat4 model;
9 | uniform mat4 view;
10 | uniform mat4 projection;
11 |
12 | uniform mat4 inverseModel;
13 | uniform mat4 inverseView;
14 | uniform mat4 inverseProjection;
15 |
16 | uniform PointLight lights[4];
17 |
18 | varying vec3 fragNormal;
19 | varying vec3 fragPosition;
20 | varying vec3 lightDirection[4];
21 |
22 | void main() {
23 | vec4 viewPosition = view * model * vec4(position, 1.0);
24 | vec4 viewNormal = vec4(normal, 0.0) * inverseModel * inverseView;
25 |
26 | for(int i=0; i<4; ++i) {
27 | vec4 viewLight = view * vec4(lights[i].position, 1.0);
28 | lightDirection[i] = viewLight.xyz - viewPosition.xyz;
29 | }
30 |
31 | gl_Position = projection * viewPosition;
32 | fragNormal = viewNormal.xyz;
33 | fragPosition = viewPosition.xyz;
34 | }
--------------------------------------------------------------------------------
/exercises/npr-1/README.md:
--------------------------------------------------------------------------------
1 | # Non-photorealistic rendering
2 |
3 | ## Exercise
4 |
5 | Modify the Lambert diffuse lighting model from `LIGHT 2` to support cel shading. The shader will be passed an extra uniform called `numBands` which determines the number of levels the light intensity should be quantized to. To get started, modify the template files in this directory.
6 |
7 | ***
8 |
9 | ## Cel shading
10 |
11 | Physically based models of lighting are good when the goal is to create realistic images. However, there are many situations where the goal is to achieve some more artistic or stylized rendering. In this lesson, we will look at cel-shading, which is used to flatten the colors of an image giving them a more cartoony hand drawn look.
12 |
13 | The basic idea behind cel-shading is to start from the Lambert diffuse lighting model, and then apply quantization to intensity values. For example, suppose that we want to round our light value into one 8 different buckets which are uniformly sized. Then we could do something like this:
14 |
15 | ```glsl
16 | float celIntensity = ceil(lightIntensity * 8.0) / 8.0;
17 | ```
18 |
19 | Then we would use `celIntensity` instead of `lightIntensity` in the rest of the calculations within our lighting model.
20 |
--------------------------------------------------------------------------------
/exercises/npr-1/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform mat4 model;
4 | uniform mat4 view;
5 | uniform mat4 projection;
6 |
7 | uniform mat4 inverseModel;
8 | uniform mat4 inverseView;
9 | uniform mat4 inverseProjection;
10 |
11 | uniform vec3 diffuse;
12 | uniform vec3 lightDirection;
13 | uniform float numBands;
14 |
15 | void main() {
16 | gl_FragColor = vec4(1,1,1,1);
17 | }
--------------------------------------------------------------------------------
/exercises/npr-1/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position;
4 | attribute vec3 normal;
5 |
6 | uniform mat4 model;
7 | uniform mat4 view;
8 | uniform mat4 projection;
9 |
10 | uniform mat4 inverseModel;
11 | uniform mat4 inverseView;
12 | uniform mat4 inverseProjection;
13 |
14 | uniform vec3 diffuse;
15 | uniform vec3 lightDirection;
16 | uniform float numBands;
17 |
18 | void main() {
19 | gl_Position = vec4(position,1);
20 | }
21 |
--------------------------------------------------------------------------------
/exercises/npr-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: posterization
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/npr-1/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/npr-1/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec3 diffuse;
4 | uniform vec3 lightDirection;
5 | varying vec3 fragNormal;
6 |
7 | uniform float numBands;
8 |
9 | void main() {
10 | float lambertWeight = max(dot(normalize(fragNormal), normalize(lightDirection)), 0.0);
11 | lambertWeight = ceil(lambertWeight * numBands) / numBands;
12 | vec3 lightColor = diffuse * lambertWeight;
13 | gl_FragColor = vec4(lightColor, 1);
14 | }
--------------------------------------------------------------------------------
/exercises/npr-1/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position;
4 | attribute vec3 normal;
5 |
6 | uniform mat4 model;
7 | uniform mat4 view;
8 | uniform mat4 projection;
9 |
10 | uniform mat4 inverseModel;
11 | uniform mat4 inverseView;
12 | uniform mat4 inverseProjection;
13 |
14 | varying vec3 fragNormal;
15 |
16 | void main() {
17 | vec4 worldPosition = model * vec4(position, 1.0);
18 | vec4 worldNormal = vec4(normal, 0.0) * inverseModel;
19 |
20 | gl_Position = projection * view * worldPosition;
21 | fragNormal = worldNormal.xyz;
22 | }
--------------------------------------------------------------------------------
/exercises/npr-2/README.md:
--------------------------------------------------------------------------------
1 | # Gooch shading
2 |
3 | ## Exercise
4 |
5 | In this exercise, you will implement the two color version of Gooch shading. To get you started, a template vertex and fragment shader has been created in the directory for this project. These shaders will be passed the following parameters:
6 |
7 | ### Attributes
8 |
9 | * `position` vertex position
10 | * `normal` vertex normal
11 |
12 | ### Uniforms
13 |
14 | * `model, view, projection` standard coordinate transformations
15 | * `warm, cool` the two colors which the Gooch shading model interpolates
16 | * `lightDirection` the direction of incident light in the scene
17 |
18 | ***
19 |
20 | Another simple and effective non-photorealistic rendering technique is Gooch shading. Gooch shading is useful for technical illustrations, or otherwise coloring objects in such a way to make fine details and contours pop out. The basic idea in Gooch shading is to modify Lambertian diffuse lighting in two ways:
21 |
22 | 1. First, instead of clamping negative weights to 0, the weights are allowed to range from [-1,1].
23 | 2. Second, instead of interpolating from the diffuse light value to 0, the light color is smoothly interpolated over some other color space.
24 |
25 | Concretely, the weight for the light color in Gooch shading is defined to be:
26 |
27 | ```glsl
28 | float goochWeight(vec3 normal, vec3 lightDirection) {
29 | return 0.5 * (1.0 + dot(normal, lightDirection));
30 | }
31 | ```
32 |
33 | And in two color Gooch shading, the color is given by interpolating between any pair of colors given this weight:
34 |
35 | ```glsl
36 | vec3 goochColor(vec3 cool, vec3 warm, float weight) {
37 | return (1.0 - weight) * cool + weight * warm;
38 | }
39 | ```
40 |
41 | It is also possible to interpolate over more colors or to use textures for assigning the colors instead.
42 |
--------------------------------------------------------------------------------
/exercises/npr-2/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform mat4 model;
4 | uniform mat4 view;
5 | uniform mat4 projection;
6 |
7 | uniform mat4 inverseModel;
8 | uniform mat4 inverseView;
9 | uniform mat4 inverseProjection;
10 |
11 | uniform vec3 warm;
12 | uniform vec3 cool;
13 | uniform vec3 lightDirection;
14 |
15 | void main() {
16 | gl_FragColor = vec4(1,1,1,1);
17 | }
--------------------------------------------------------------------------------
/exercises/npr-2/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position;
4 | attribute vec3 normal;
5 |
6 | uniform mat4 model;
7 | uniform mat4 view;
8 | uniform mat4 projection;
9 |
10 | uniform mat4 inverseModel;
11 | uniform mat4 inverseView;
12 | uniform mat4 inverseProjection;
13 |
14 | uniform vec3 warm;
15 | uniform vec3 cool;
16 | uniform vec3 lightDirection;
17 |
18 | void main() {
19 | gl_Position = vec4(position,1);
20 | }
21 |
--------------------------------------------------------------------------------
/exercises/npr-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: gooch shading
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/npr-2/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/npr-2/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec3 warm;
4 | uniform vec3 cool;
5 | uniform vec3 lightDirection;
6 |
7 | varying vec3 fragNormal;
8 |
9 | uniform float numBands;
10 |
11 | void main() {
12 | float weight = 0.5 * (1.0 + dot(normalize(fragNormal), normalize(lightDirection)));
13 | vec3 lightColor = mix(cool, warm, weight);
14 | gl_FragColor = vec4(lightColor, 1);
15 | }
--------------------------------------------------------------------------------
/exercises/npr-2/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec3 position;
4 | attribute vec3 normal;
5 |
6 | uniform mat4 model;
7 | uniform mat4 view;
8 | uniform mat4 projection;
9 |
10 | uniform mat4 inverseModel;
11 | uniform mat4 inverseView;
12 | uniform mat4 inverseProjection;
13 |
14 | varying vec3 fragNormal;
15 |
16 | void main() {
17 | vec4 worldPosition = model * vec4(position, 1.0);
18 | vec4 worldNormal = vec4(normal, 0.0) * inverseModel;
19 |
20 | gl_Position = projection * view * worldPosition;
21 | fragNormal = worldNormal.xyz;
22 | }
--------------------------------------------------------------------------------
/exercises/playground-flocking/README.md:
--------------------------------------------------------------------------------
1 | # Playground: Flocking
2 |
3 | ## Exercise
4 |
5 | There is no exercise for this one! You're welcome to just muck around with the
6 | code and see the results.
7 |
8 | ***
9 |
10 | ### Craig Reynolds' Boids
11 |
12 | [Boids](http://en.wikipedia.org/wiki/Boids) is an artificial life program first
13 | developed in the mid-1980s which simulates the flocking behavior of birds.
14 | It's a classic example of [emergence](http://en.wikipedia.org/wiki/Emergence)
15 | in simulation and a fun application of GPGPU.
16 |
17 | The algorithm works by assigning each particle a position and a speed, and
18 | then applying the following rules on each frame:
19 |
20 | * **separation:** boids steer away from members of the flock when they get too close.
21 | * **alignment:** boids steer to face the same direction as their neighbours.
22 | * **cohesion:** boids cluster together with their neighbours.
23 |
24 | Despite the simplicity of these rules, they result in formations that very much
25 | resemble flocks of birds. The example here also includes an additional rule
26 | to keep boids on-screen:
27 |
28 | * **central attraction:** boids steer towards the center of the screen.
29 |
--------------------------------------------------------------------------------
/exercises/playground-flocking/files/position.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D positions;
4 | uniform sampler2D speeds;
5 |
6 | #define SIZE 64.0
7 |
8 | void main() {
9 | vec2 currentIndex = gl_FragCoord.xy / vec2(SIZE);
10 | vec2 position = texture2D(positions, currentIndex).xy;
11 | vec2 speed = texture2D(speeds, currentIndex).xy;
12 |
13 | position += speed;
14 |
15 | gl_FragColor.xy = position;
16 | gl_FragColor.zw = vec2(1.0);
17 | }
18 |
--------------------------------------------------------------------------------
/exercises/playground-flocking/files/render.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | varying vec2 fragIndex;
4 | uniform sampler2D positions;
5 | uniform sampler2D speeds;
6 |
7 | void main() {
8 | vec2 position = texture2D(positions, fragIndex).xy;
9 | vec2 speed = texture2D(speeds, fragIndex).xy;
10 |
11 | float x = gl_PointCoord.x - 0.5;
12 | float y = gl_PointCoord.y - 0.5;
13 | if (x*x+y*y>0.25) discard;
14 |
15 | vec3 color = mix(
16 | vec3(1.0, 0.8862, 0.3725)
17 | , vec3(1.0, 0.4313, 0.3411)
18 | , fragIndex.x
19 | );
20 |
21 | gl_FragColor = vec4(color * vec3(0.5), 1.0);
22 | }
23 |
--------------------------------------------------------------------------------
/exercises/playground-flocking/files/render.vert:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec2 intIndex;
4 | varying vec2 fragIndex;
5 | uniform sampler2D positions;
6 | uniform sampler2D speeds;
7 | uniform vec2 screenSize;
8 |
9 | #define SIZE 64.0
10 |
11 | void main() {
12 | vec2 currentIndex = intIndex.xy / vec2(SIZE);
13 | vec2 position = texture2D(positions, currentIndex).xy;
14 | vec2 speed = texture2D(speeds, currentIndex).xy;
15 |
16 | fragIndex = currentIndex;
17 |
18 | gl_PointSize = abs(length(speed)) * 1500.0 + 1.0;
19 | gl_Position.xy = position;
20 | gl_Position.xy -= vec2(0.5);
21 | gl_Position.xy *= vec2(1.75);
22 | gl_Position.x *= screenSize.y / screenSize.x;
23 | // gl_Position.xy -= vec2(0.5);
24 |
25 | gl_Position.zw = vec2(1.0);
26 | }
27 |
--------------------------------------------------------------------------------
/exercises/playground-flocking/files/speed.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D positions;
4 | uniform sampler2D speeds;
5 |
6 | #define CENTER_ATTRACTION 0.000005
7 | #define SPEED_LIMIT 0.00225
8 | #define SEPARATION 0.0000125
9 | #define ALIGNMENT 0.000001
10 | #define COHESION 0.00002
11 | #define SIZE 64.0
12 |
13 | vec2 separation(vec2 position, vec2 maxDistance, vec2 index) {
14 | vec2 influence = vec2(0.0);
15 |
16 | for (float x = 0.0; x < 1.0; x += 1.0 / SIZE)
17 | for (float y = 0.0; y < 1.0; y += 1.0 / SIZE) {
18 | vec2 uv = vec2(x, y);
19 | vec2 pos = texture2D(positions, uv).xy;
20 |
21 | float self = (
22 | x == index.x &&
23 | y == index.y
24 | ) ? 0.0 : 1.0;
25 |
26 | float close = all(
27 | greaterThanEqual(abs(position - pos), maxDistance)
28 | ) ? 1.0 : 0.0;
29 |
30 | influence -= self * close * (position - pos);
31 | }
32 |
33 | return position - influence;
34 | }
35 |
36 | vec2 alignment(vec2 speed) {
37 | vec2 influence = vec2(0.0);
38 |
39 | for (float x = 0.0; x < 1.0; x += 1.0 / SIZE)
40 | for (float y = 0.0; y < 1.0; y += 1.0 / SIZE) {
41 | vec2 uv = vec2(x, y);
42 | vec2 spd = texture2D(speeds, uv).xy;
43 |
44 | influence += spd;
45 | }
46 |
47 | return influence;
48 | }
49 | vec2 cohesion(vec2 position) {
50 | vec2 influence = vec2(0.0);
51 |
52 | for (float x = 0.0; x < 1.0; x += 1.0 / SIZE)
53 | for (float y = 0.0; y < 1.0; y += 1.0 / SIZE) {
54 | vec2 uv = vec2(x, y);
55 | vec2 pos = texture2D(positions, uv).xy;
56 |
57 | influence += pos / SIZE / SIZE;
58 | }
59 |
60 | return influence - position;
61 | }
62 |
63 | void main() {
64 | vec2 currentIndex = gl_FragCoord.xy / vec2(SIZE);
65 | vec2 position = texture2D(positions, currentIndex).xy;
66 | vec2 speed = texture2D(speeds, currentIndex).xy;
67 |
68 | speed += normalize(alignment(speed)) * ALIGNMENT;
69 | speed += normalize(cohesion(position)) * COHESION;
70 | speed += normalize(separation(position, vec2(0.01), currentIndex)) * SEPARATION;
71 |
72 | speed += normalize(vec2(0.5) - position) * CENTER_ATTRACTION;
73 |
74 | gl_FragColor.xy = clamp(speed, -SPEED_LIMIT, SPEED_LIMIT);
75 | gl_FragColor.zw = vec2(1.0);
76 | }
77 |
--------------------------------------------------------------------------------
/exercises/playground-flocking/files/triangle.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | attribute vec2 position;
4 |
5 | void main() {
6 | gl_Position = vec4(position.xy, 0.0, 1.0);
7 | }
8 |
--------------------------------------------------------------------------------
/exercises/playground-flocking/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: flocking
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/playground-flocking/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/playground-gpgpu/README.md:
--------------------------------------------------------------------------------
1 | # GPGPU Playground
2 |
3 | ## Exercise
4 |
5 | There is no exercise for this lesson! Play with the shaders and have fun! The files for this module are stored in the answers/playground directory.
6 |
7 | ***
8 |
9 | For this lesson there are two fragment shaders:
10 |
11 | * `render.glsl` Which draws the buffer state to the screen
12 | * `update.glsl` Which computes the next buffer state from the previous buffer state
13 |
14 | These shaders get the following inputs:
15 |
16 | * `state` which is a history of the previous state textures
17 | * `screenSize` which is the size of the buffer
18 | * `mousePosition` which is the position of the user's mouse in screen coordinates
19 | * `mouseDown` which is a 3D boolean vector
20 | * `time` which is the time in seconds since the shader was initialized
--------------------------------------------------------------------------------
/exercises/playground-gpgpu/files/render.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D state[3]; //State buffer
4 | uniform vec2 screenSize; //Size of screen buffer
5 | uniform vec2 mousePosition; //Position of mouse
6 | uniform bool mouseDown[3]; //Test if mouse left, right, middle is down
7 | uniform float time; //Time since start
8 |
9 | void main() {
10 | gl_FragColor = vec4(texture2D(state[0], gl_FragCoord.xy / screenSize).rgb, 1.0);
11 | }
--------------------------------------------------------------------------------
/exercises/playground-gpgpu/files/update.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D state[2]; //State buffer
4 | uniform vec2 screenSize; //Size of screen buffer
5 | uniform vec2 mousePosition; //Position of mouse
6 | uniform bvec3 mouseDown; //Test if mouse left, right, middle is down
7 | uniform float time; //Time since start
8 |
9 | void main() {
10 | vec3 paintColor = vec3(0,0,0);
11 |
12 | //Paint colors depending on mouse state
13 | float w = exp2(-0.05 * distance(gl_FragCoord.xy, mousePosition));
14 | if(mouseDown.x) {
15 | paintColor.r = w;
16 | }
17 | if(mouseDown.y) {
18 | paintColor.g = w;
19 | }
20 | if(mouseDown.z) {
21 | paintColor.b = w;
22 | }
23 |
24 | //Write out texture
25 | vec2 texCoord = gl_FragCoord.xy / screenSize;
26 | gl_FragColor = texture2D(state[0], texCoord) + vec4(paintColor, 0.0);
27 | }
28 |
--------------------------------------------------------------------------------
/exercises/playground-gpgpu/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: gpgpu playground
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/playground-gpgpu/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/playground-gpgpu/shaders/pass-thru.glsl:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 | attribute vec2 position;
3 | void main() {
4 | gl_Position = vec4(position, 0.0, 1.0);
5 | }
6 |
--------------------------------------------------------------------------------
/exercises/prims-1/README.md:
--------------------------------------------------------------------------------
1 | # Point sprites
2 |
3 | ## Exercise
4 |
5 | Create a vertex/fragment shader pair to render a collection of colored point sprites. A pair of starting shaders have been created in the directory for this lesson to help get started. These shaders get a standard collection of camera matrices factored into the model/view/projection transformations. In addition, the shaders also get 3 attributes:
6 |
7 | * `position` which is the position of each point sprite
8 | * `color` which is the color of each point sprite
9 | * `size` which is the desired radius of the point sprite in pixels
10 |
11 | Modify the starting shaders so that they draw each point sprite as a disk with `size` radius and `color` as the RGB fragment color.
12 |
13 | ***
14 |
15 | ## Point sprites
16 |
17 | Besides rendering triangles, WebGL also has special features for drawing point sprites. Point sprites can be useful for particle effects or 2D objects. From the perspective of shaders there are two extra variables in point sprites which can be used by shaders: `gl_PointSize` and `gl_PointCoord`.
18 |
19 | ### `gl_PointSize`
20 |
21 | The `gl_PointSize` variable is an extra `float` output variable for vertex shaders which is only defined when drawing points. It controls the radius of the point in pixels, and can be assigned by the vertex shader.
22 |
23 | ### `gl_PointCoord`
24 |
25 | `gl_PointCoord` is a special fragment shader input `vec2` variable which gives the coordinate of the fragment in the sprite starting from the upper left corner. The coordinates of `gl_PointCoord` range from 0 to 1.
--------------------------------------------------------------------------------
/exercises/prims-1/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | void main() {
4 | gl_FragColor = vec4(1,1,1,1);
5 | }
--------------------------------------------------------------------------------
/exercises/prims-1/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec3 position, color;
4 | attribute float size;
5 |
6 | uniform mat4 model, view, projection;
7 |
8 | void main() {
9 | gl_Position = projection * view * model * vec4(position, 1.0);
10 | }
11 |
--------------------------------------------------------------------------------
/exercises/prims-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: point sprites
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/prims-1/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/prims-1/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | varying vec3 fragColor;
4 |
5 | void main() {
6 | if(distance(gl_PointCoord.st, vec2(0.5,0.5)) > 0.5) {
7 | discard;
8 | }
9 | gl_FragColor = vec4(fragColor,1.0);
10 | }
--------------------------------------------------------------------------------
/exercises/prims-1/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec3 position, color;
4 | attribute float size;
5 |
6 | uniform mat4 model, view, projection;
7 |
8 | varying vec3 fragColor;
9 |
10 | void main() {
11 | gl_Position = projection * view * model * vec4(position, 1.0);
12 | gl_PointSize = size;
13 | fragColor = color;
14 | }
15 |
--------------------------------------------------------------------------------
/exercises/prims-2/README.md:
--------------------------------------------------------------------------------
1 | # Triangles
2 |
3 | ## Exercise
4 |
5 | Write a fragment shader which colors fragments depending on their relative orientation to the view direction. If they are facing away from the camera, color them with `backColor`, or if they are facing towards the camera color them with `frontColor`. To get started, a template file `fragment.glsl` has been created for you.
6 |
7 | ***
8 |
9 | ### `gl_FrontFacing`
10 |
11 | Triangle primitives in GLSL get a special property called called `gl_FrontFacing` which tests if they are oriented towards the camera or not.
--------------------------------------------------------------------------------
/exercises/prims-2/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform vec3 frontColor, backColor;
4 |
5 | void main() {
6 | gl_FragColor = vec4(1,1,1,1);
7 | }
--------------------------------------------------------------------------------
/exercises/prims-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: triangles
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/prims-2/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/prims-2/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform vec3 frontColor, backColor;
4 |
5 | void main() {
6 | if(gl_FrontFacing) {
7 | gl_FragColor = vec4(frontColor, 1.0);
8 | } else {
9 | gl_FragColor = vec4(backColor, 1.0);
10 | }
11 | }
--------------------------------------------------------------------------------
/exercises/prims-2/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec2 position;
4 | uniform mat4 model, view, projection;
5 | uniform float theta;
6 |
7 | void main() {
8 | vec2 c = 4.0 * (position / 128.0 - vec2(0.25, 0.0));
9 | float f = 0.25 * (cos(c.x + theta) + sin(4.0 * c.x * c.y + 0.5*theta) - 2.0 * sin(2.0 * c.y - 2.0*theta));
10 | gl_Position = projection * view * model * vec4(c.x, f, c.y, 1.0);
11 | }
12 |
--------------------------------------------------------------------------------
/exercises/vert-1/README.md:
--------------------------------------------------------------------------------
1 | # Introduction to vertex shaders
2 |
3 | ## Exercises
4 |
5 | For this exercise you will write a shader which applies a 2D rotation to a vertex. Specifically, create a vertex shader that accepts the following parameters:
6 |
7 | * A `vec2` attribute called `position` representing the position of the vertices in the plane
8 | * A `float` uniform `theta` encoding the amount to rotate by in radians
9 |
10 | Edit the file called `vertex.glsl` in the directory of this project.
11 |
12 | ***
13 |
14 | ## Vertex shaders
15 |
16 | Vertex shaders control how geometry is rendered in WebGL, and are executed on the GPU before fragment shaders. A vertex in OpenGL is one of the corners of a primitive. Primitives in OpenGL are simplices of dimension < 3, and are called:
17 |
18 | * Points - 1 vertex
19 | * Lines - 2 vertices
20 | * Triangles - 3 vertices
21 |
22 | Primitives are drawn by linearly interpolating between their vertices, and vertex shaders control where the vertices of these primitives are placed on the screen. Like fragment shaders, the entry point for a vertex shader is a `void` procedure called `main()`. The output of a vertex shader is written to a special variable called `gl_Position` which controls the placement of the vertex in clip coordinates. Here is an example of a trivial vertex shader program which just outputs a vertex at the center of the screen:
23 |
24 | ```glsl
25 | void main() {
26 | gl_Position = vec4(0, 0, 0, 1);
27 | }
28 | ```
29 |
30 | ## Attributes
31 |
32 | In addition to getting inputs from `uniform` variables, vertex shaders can also accept per-vertex information via `attribute` variables. The content, type and number of attribute variables is customizable within WebGL. As an example of how to declare and use an attribute variable, here is a simple 2D shader that just passes through the vertex coordinates:
33 |
34 | ```glsl
35 | attribute vec2 position;
36 |
37 | void main() {
38 | gl_Position = vec4(position, 0, 1);
39 | }
40 | ```
41 |
--------------------------------------------------------------------------------
/exercises/vert-1/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform float theta;
4 |
5 | attribute vec2 position;
6 |
7 | void main() {
8 |
9 | //TODO: rotate position by theta radians about the origin
10 |
11 | gl_Position = vec4(position, 0, 1.0);
12 | }
13 |
--------------------------------------------------------------------------------
/exercises/vert-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: introduction to vertex shaders
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/vert-1/index.js:
--------------------------------------------------------------------------------
1 | var matchFBO = require('../../lib/match-fbo')
2 | var throttle = require('frame-debounce')
3 | var fit = require('canvas-fit')
4 | var getContext = require('gl-context')
5 | var compare = require('gl-compare')
6 | var createBuffer = require('gl-buffer')
7 | var createVAO = require('gl-vao')
8 | var createShader = require('glslify')
9 | var fs = require('fs')
10 | var now = require('right-now')
11 |
12 | var container = document.getElementById('container')
13 | var canvas = container.appendChild(document.createElement('canvas'))
14 | var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
15 | var gl = getContext(canvas, render)
16 | var comparison = compare(gl, actual, expected)
17 |
18 | comparison.mode = 'slide'
19 | comparison.amount = 0.5
20 |
21 | require('../common')({
22 | description: readme
23 | , compare: comparison
24 | , canvas: canvas
25 | , test: matchFBO(comparison, 0.99)
26 | , dirname: process.env.dirname
27 | })
28 |
29 | window.addEventListener('resize', fit(canvas), false)
30 |
31 |
32 | var vertexData = [0, 0.5, -0.5, -0.5, 0.5, -0.5]
33 | var vertexBuffer = createBuffer(gl, vertexData)
34 | var vertexArray = createVAO(gl, [
35 | {
36 | "buffer": vertexBuffer,
37 | "size": 2
38 | }])
39 |
40 | var actualShader = createShader({
41 | frag: './shaders/fragment.glsl'
42 | , vert: process.env.file_vertex_glsl
43 | })(gl)
44 |
45 | var expectedShader = createShader({
46 | frag: './shaders/fragment.glsl'
47 | , vert: './shaders/vertex.glsl'
48 | })(gl)
49 |
50 |
51 | var theta = 0.0
52 |
53 | function render() {
54 | theta = 0.0001 * now()
55 | comparison.run()
56 | comparison.render()
57 | }
58 |
59 | function actual(fbo) {
60 | fbo.shape = [canvas.height, canvas.width]
61 | fbo.bind()
62 | gl.clear(gl.COLOR_BUFFER_BIT)
63 | actualShader.bind()
64 | actualShader.uniforms.theta = theta
65 | vertexArray.bind()
66 | vertexArray.draw(gl.TRIANGLES, 3)
67 | }
68 |
69 | function expected(fbo) {
70 | fbo.shape = [canvas.height, canvas.width]
71 | fbo.bind()
72 | gl.clear(gl.COLOR_BUFFER_BIT)
73 | expectedShader.bind()
74 | expectedShader.uniforms.theta = theta
75 | vertexArray.bind()
76 | vertexArray.draw(gl.TRIANGLES, 3)
77 | }
--------------------------------------------------------------------------------
/exercises/vert-1/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/vert-1/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1, 1, 1, 1);
3 | }
--------------------------------------------------------------------------------
/exercises/vert-1/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform float theta;
4 |
5 | attribute vec2 position;
6 |
7 | void main() {
8 |
9 | float c = cos(theta);
10 | float s = sin(theta);
11 |
12 | mat2 rot = mat2(c, s, -s, c);
13 |
14 | gl_Position = vec4(rot * position, 0, 1.0);
15 | }
16 |
--------------------------------------------------------------------------------
/exercises/vert-2/README.md:
--------------------------------------------------------------------------------
1 | # Varying variables
2 |
3 | ## Exercise
4 |
5 | In this exercise you will write a minimal vertex shader and fragment shader for rendering colored objects.
6 |
7 | Specifically, you should declare a vertex shader with two attributes: `position` and `color`. Your fragment shader should output the value of `color` to the `gl_FragColor` register, which will be automatically interpolated. This will be used to draw a shaded triangle.
8 |
9 | To get you started, a template vertex and fragment shader have been created in the directory for this lesson.
10 |
11 | Hint : The triangles' 3 corners are the only vertices used, so you (a) need to figure out their coordinates, and (b) construct an expression that colors each vertex correctly.
12 |
13 | ***
14 |
15 | ## Connecting vertex shaders to fragment shaders
16 |
17 | In addition to defining the position of vertices, vertex shaders can also send information directly to fragment shaders using the `varying` type qualifier. `varying` variables, like `attribute`s and `uniform`s are declared at global scope within a shader. `varying` variables must have a datatype of either `float`, `vec2`, `vec3` or `vec4`. By default, `varying` variables are linearly interpolated across the rendered primitive. In a vertex shader, one could declare a variable like this:
18 |
19 | ```glsl
20 | attribute vec4 position;
21 |
22 | //Declare a varying variable called fragPosition
23 | varying vec4 fragPosition;
24 |
25 | void main() {
26 | gl_Position = position;
27 |
28 | //Set fragPosition variable for the
29 | //fragment shader output
30 | fragPosition = position;
31 | }
32 | ```
33 |
34 | Then in the fragment shader, the `fragPosition` variable can be used like so:
35 |
36 | ```glsl
37 | precision highp float;
38 |
39 | varying vec4 fragPosition;
40 |
41 | void main() {
42 | gl_FragColor = fragPosition;
43 | }
44 | ```
45 |
--------------------------------------------------------------------------------
/exercises/vert-2/files/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | void main() {
4 | gl_FragColor = vec4(1,1,1,1);
5 | }
--------------------------------------------------------------------------------
/exercises/vert-2/files/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec4 position;
4 | attribute vec3 color;
5 |
6 | void main() {
7 | gl_Position = position;
8 | }
9 |
--------------------------------------------------------------------------------
/exercises/vert-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school: varying variables
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/exercises/vert-2/index.js:
--------------------------------------------------------------------------------
1 | var matchFBO = require('../../lib/match-fbo')
2 | var throttle = require('frame-debounce')
3 | var fit = require('canvas-fit')
4 | var getContext = require('gl-context')
5 | var compare = require('gl-compare')
6 | var createBuffer = require('gl-buffer')
7 | var createVAO = require('gl-vao')
8 | var createShader = require('glslify')
9 | var fs = require('fs')
10 | var now = require('right-now')
11 |
12 | var container = document.getElementById('container')
13 | var canvas = container.appendChild(document.createElement('canvas'))
14 | var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
15 | var gl = getContext(canvas, render)
16 | var comparison = compare(gl, actual, expected)
17 |
18 | comparison.mode = 'slide'
19 | comparison.amount = 0.5
20 |
21 | require('../common')({
22 | description: readme
23 | , compare: comparison
24 | , canvas: canvas
25 | , test: matchFBO(comparison, 0.99)
26 | , dirname: process.env.dirname
27 | })
28 |
29 | window.addEventListener('resize', fit(canvas), false)
30 |
31 |
32 | var vertexBuffer = createBuffer(gl, [0, 0.5, 0, 1, -0.5, -0.5, 0, 1, 0.5, -0.5, 0, 1])
33 | var colorBuffer = createBuffer(gl, [1,0,0,0,1,0,0,0,1])
34 | var vertexArray = createVAO(gl, [
35 | {
36 | "buffer": vertexBuffer,
37 | "size": 4
38 | },
39 | {
40 | "buffer": colorBuffer,
41 | "size": 3
42 | }
43 | ])
44 |
45 | var actualShader = createShader({
46 | frag: process.env.file_fragment_glsl
47 | , vert: process.env.file_vertex_glsl
48 | })(gl)
49 |
50 | actualShader.attributes.position.location = 0
51 | actualShader.attributes.color.location = 1
52 |
53 | var expectedShader = createShader({
54 | frag: './shaders/fragment.glsl'
55 | , vert: './shaders/vertex.glsl'
56 | })(gl)
57 |
58 | expectedShader.attributes.position.location = 0
59 | expectedShader.attributes.color.location = 1
60 |
61 | var theta = 0.0
62 |
63 | function render() {
64 | theta = 0.0001 * now()
65 | comparison.run()
66 | comparison.render()
67 | }
68 |
69 | function actual(fbo) {
70 | fbo.shape = [canvas.height, canvas.width]
71 | fbo.bind()
72 | gl.clear(gl.COLOR_BUFFER_BIT)
73 | actualShader.bind()
74 | vertexArray.bind()
75 | vertexArray.draw(gl.TRIANGLES, 3)
76 | }
77 |
78 | function expected(fbo) {
79 | fbo.shape = [canvas.height, canvas.width]
80 | fbo.bind()
81 | gl.clear(gl.COLOR_BUFFER_BIT)
82 | expectedShader.bind()
83 | vertexArray.bind()
84 | vertexArray.draw(gl.TRIANGLES, 3)
85 | }
--------------------------------------------------------------------------------
/exercises/vert-2/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function(sourceFiles) {
4 | var glslify = ['-t', require.resolve('glslify')]
5 | var live = ['-t', require.resolve('glslify-live')]
6 | var brfs = ['-t', require.resolve('brfs')]
7 | var envify = ['-t', '[', require.resolve('envify')]
8 |
9 | envify.push('--dirname', path.basename(__dirname))
10 | sourceFiles.forEach(function(file) {
11 | var base = path.basename(file).replace(/\./g, '_')
12 | envify.push('--file_' + base)
13 | envify.push(file)
14 | })
15 |
16 | envify.push(']')
17 |
18 | return require('beefy')({
19 | cwd: __dirname
20 | , entries: ['index.js']
21 | , quiet: false
22 | , live: false
23 | , debug: false
24 | , watchify: false
25 | , bundlerFlags: []
26 | .concat(envify)
27 | .concat(live)
28 | .concat(glslify)
29 | .concat(brfs)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/exercises/vert-2/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | varying vec3 fragColor;
4 |
5 | void main() {
6 | gl_FragColor = vec4(fragColor, 1);
7 | }
--------------------------------------------------------------------------------
/exercises/vert-2/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec4 position;
4 | attribute vec3 color;
5 |
6 | varying vec3 fragColor;
7 |
8 | void main() {
9 | fragColor = color;
10 | gl_Position = position;
11 | }
--------------------------------------------------------------------------------
/intro.txt:
--------------------------------------------------------------------------------
1 |
2 | ==============================
3 | = ~~~~~ shader-school ~~~~~~ =
4 | ==============================
5 |
--------------------------------------------------------------------------------
/lib/close-window.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/create-answers.js:
--------------------------------------------------------------------------------
1 | var exercises = require('../exercises.json')
2 | var exmap = require('./exercise-map')
3 | var inquirer = require('inquirer')
4 | var wordwrap = require('wordwrap')
5 | var mkdirp = require('mkdirp')
6 | var chalk = require('chalk')
7 | var path = require('path')
8 | var fs = require('fs')
9 |
10 | exercises = Object.keys(exercises).map(function(key) {
11 | return exercises[key]
12 | })
13 |
14 | module.exports = createAnswers
15 |
16 | function createAnswers(root, done) {
17 | var counter = 0
18 |
19 | inquirer.prompt([{
20 | 'type': 'confirm'
21 | , 'name': 'ok'
22 | , 'default': true
23 | , 'message': wordwrap(4, 80)(
24 | "We're about to populate this directory with some code for you to " +
25 | "use for your answers. If they've already been created then don't worry, " +
26 | "they won't be replaced. Continue?"
27 | ).replace(/^\s+/, '')
28 | }], function(result) {
29 | if (!result.ok) return process.exit(1)
30 | console.error()
31 |
32 | exercises.forEach(createExercise)
33 | if (!counter) return done && done()
34 | })
35 |
36 | function createExercise(subdir, i) {
37 | var ref = exmap[subdir]
38 | var dst = path.resolve(root, ref)
39 | var src = path.resolve(__dirname, '../exercises/' + subdir + '/files')
40 |
41 | mkdirp.sync(dst)
42 | mkdirp.sync(src)
43 | fs.readdirSync(src).forEach(function(name) {
44 | if (fs.existsSync(path.join(dst, name))) return
45 |
46 | counter++
47 |
48 | fs.createReadStream(path.join(src, name))
49 | .pipe(fs.createWriteStream(path.join(dst, name)))
50 | .once('close', function() {
51 | var filepath = path.join(path.basename(src), name)
52 | console.error(chalk.grey('created: ') + chalk.cyan(filepath))
53 | if (!--counter) return done && done()
54 | })
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/description.js:
--------------------------------------------------------------------------------
1 | var domify = require('domify')
2 |
3 | module.exports = function(html) {
4 | var div = document.createElement('div')
5 |
6 | div.innerHTML = html
7 | div.classList.add('glslify-description')
8 | div.classList.add('enabled')
9 |
10 | var visible = true
11 | var toggle = div.appendChild(domify(
12 | ''
13 | ))
14 |
15 | toggle.addEventListener('click', function(e) {
16 | if (visible = !visible) {
17 | div.classList.add('enabled')
18 | } else {
19 | div.classList.remove('enabled')
20 | }
21 | }, false)
22 |
23 | return div
24 | }
25 |
--------------------------------------------------------------------------------
/lib/diff-ui.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/lib/diff-ui.js:
--------------------------------------------------------------------------------
1 | var findup = require('findup-element')
2 | var mpos = require('mouse-position')
3 | var css = require('insert-css')
4 | var domify = require('domify')
5 | var slice = require('sliced')
6 | var clamp = require('clamp')
7 | var fs = require('fs')
8 |
9 | module.exports = DiffUI
10 |
11 | function DiffUI(compare) {
12 | if (!(this instanceof DiffUI)) return new DiffUI(compare)
13 |
14 | var el = this.el = document.body.appendChild(
15 | domify(fs.readFileSync(
16 | __dirname + '/diff-ui.html', 'utf8'
17 | ))
18 | )
19 |
20 | var buttons = slice(el.querySelectorAll('ul > li'))
21 | var slider = el.querySelector('.diff-slider-inner')
22 | var outerSlider = el.querySelector('.diff-slider')
23 | var mouse = mpos(slider, window)
24 | var sliding = false
25 |
26 | mouse.on('move', function() {
27 | if (!sliding) return
28 | var slide = clamp(mouse.x / 300, 0, 1)
29 | compare.amount = slide
30 | slider.style.width = slide * 100 + '%'
31 | })
32 |
33 | outerSlider.addEventListener('mousedown', function(e) {
34 | sliding = true
35 | }, false)
36 |
37 | window.addEventListener('mouseup', function(e) {
38 | sliding = false
39 | }, false)
40 |
41 | el.addEventListener('click', function(e) {
42 | var button = findup(e.target, 'li')
43 | if (!button) return
44 |
45 | var mode = button.getAttribute('data-mode')
46 | if (mode) return select(mode)
47 | }, false)
48 |
49 | select('slide')
50 | function select(mode) {
51 | compare.mode = mode
52 |
53 | buttons.forEach(function(button) {
54 | button.classList.remove('selected')
55 | })
56 |
57 | el.querySelector('[data-mode="'+mode+'"]')
58 | .classList
59 | .add('selected')
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/exercise-map.js:
--------------------------------------------------------------------------------
1 | var exercises = require('../exercises.json')
2 | var zfill = require('zfill')
3 |
4 | var titles = Object.keys(exercises)
5 | var values = titles.map(function(key) {
6 | return exercises[key]
7 | })
8 |
9 | module.exports = values.reduce(function(map, name, i) {
10 | map[name] = zfill(i, 2) + '-' + name
11 | return map
12 | }, {})
13 |
--------------------------------------------------------------------------------
/lib/match-fbo.js:
--------------------------------------------------------------------------------
1 | var matching = require('gl-fbo-matching')
2 |
3 | module.exports = matchFBO
4 |
5 | function matchFBO(comparison, min) {
6 | min = min || 0.99
7 |
8 | return function test(done) {
9 | comparison.run()
10 |
11 | var similarity = matching(
12 | comparison.actual.fbo
13 | , comparison.expected.fbo
14 | , 0.05
15 | )
16 |
17 | for (var i = 0; i < 3; i++) {
18 | if (similarity[i] < min) {
19 | return done(null, false)
20 | }
21 | }
22 |
23 | return done(null, true)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/progress.js:
--------------------------------------------------------------------------------
1 | var ls = window.localStorage
2 |
3 | module.exports = getProgress
4 | module.exports.set = setProgress
5 | module.exports.get = getProgress
6 |
7 | function getProgress(name) {
8 | return name && !!ls.getItem('progress:' + name)
9 | }
10 |
11 | function setProgress(name, value) {
12 | return name && ls.setItem('progress:' + name, !!value ? '1' : '')
13 | }
14 |
--------------------------------------------------------------------------------
/menu/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | shader-school
6 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/menu/index.js:
--------------------------------------------------------------------------------
1 | var exercises = require('../exercises.json')
2 | var progress = require('../lib/progress')
3 | var sidenote = require('sidenote')
4 | var menu = require('browser-menu')({
5 | x: 0, y: 0
6 | , bg: process.browser ? '#61FF90' : 'green'
7 | , fg: process.browser ? '#34363B' : 'black'
8 | })
9 |
10 | var line = '---------------------------------------------'
11 |
12 | menu.reset()
13 | menu.write('SHADER SCHOOL\n')
14 | menu.element.style.margin = '2em'
15 |
16 | var lcat = null
17 | var cats = []
18 | var keys = Object.keys(exercises)
19 | var rows = sidenote(keys.map(function(name, i) {
20 | var dir = exercises[name]
21 | var parts = name.match(/^(.*?)([A-Z][^\:]+\:)(.*?)$/)
22 | var category = cats[i] = parts[2].slice(0, -1)
23 |
24 | var newname = parts
25 | .slice(1, 2)
26 | .concat(parts.slice(3))
27 | .join('')
28 | .replace(/\s+/, ' ')
29 |
30 | exercises[newname] = exercises[name]
31 |
32 | return [ newname
33 | , progress.get(dir)
34 | ? '[COMPLETE]'
35 | : ' '
36 | ]
37 | }), {
38 | distance: 10
39 | }).map(function(row, i) {
40 | var cat = cats[i]
41 |
42 | if (lcat !== cat) {
43 | var line = '------------------------------------------'
44 |
45 | line = '- ' + cat + ' ' + line.slice(cat.length)
46 | menu.write(line + '\n')
47 | lcat = cat
48 | }
49 |
50 | return menu.add(row), row
51 | })
52 |
53 | menu.on('select', function(label, i) {
54 | var label = keys[i]
55 |
56 | // TODO: use the exit command?
57 | if (!exercises[label]) return console.error(label)
58 |
59 | window.location = exercises[label]
60 | })
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shader-school",
3 | "version": "1.1.0",
4 | "description": "Self directed GLSL lessons",
5 | "main": "index.js",
6 | "bin": "index.js",
7 | "scripts": {
8 | "pack": "rm -rf answers; rm -rf node_modules && npm install && npm dedupe && node prepack && find . -type file | grep -v workshop.tar.gz | grep -v .git | tar -cvzf ./workshop.tar.gz -T - && node postpack",
9 | "test": "echo \"Error: no test specified\" && exit 1",
10 | "start": "node start.js"
11 | },
12 | "authors": [
13 | "Hugh Kennedy (http://hughsk.io/)",
14 | "Mikola Lysenko (http://0fps.net)",
15 | "Chris Dickinson (http://neversaw.us)"
16 | ],
17 | "homepage": "http://nodeschool.io/#shader-school",
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/stackgl/shader-school.git"
21 | },
22 | "license": "ISC",
23 | "dependencies": {
24 | "a-big-triangle": "0.0.0",
25 | "apprise": "^1.0.0",
26 | "autoprefixer": "^1.2.0",
27 | "baboon-image": "^1.0.0",
28 | "beefy": "^2.0.2",
29 | "brfs": "^1.1.1",
30 | "browser-menu": "^0.1.0",
31 | "browserify": "^6.3.2",
32 | "canvas-fit": "0.0.0",
33 | "chalk": "^0.4.0",
34 | "clamp": "^1.0.0",
35 | "conway-hart": "^0.1.0",
36 | "domify": "^1.2.2",
37 | "ecstatic": "^1.0.1",
38 | "envify": "^1.2.1",
39 | "findup-element": "0.0.0",
40 | "frame-debounce": "0.0.0",
41 | "gl-axes": "^2.2.3",
42 | "gl-buffer": "^2.0.8",
43 | "gl-compare": "^1.0.0",
44 | "gl-compare-sidebar": "^1.1.1",
45 | "gl-context": "^0.1.0",
46 | "gl-fbo": "^1.1.2",
47 | "gl-fbo-matching": "^1.0.0",
48 | "gl-matrix": "^2.1.0",
49 | "gl-texture2d": "^1.2.0",
50 | "gl-vao": "^1.1.3",
51 | "glsldoc": "0.0.4",
52 | "glslify": "^1.5.0",
53 | "glslify-live": "^2.0.3",
54 | "google-fonts": "0.0.0",
55 | "highlight.js": "^8.0.0",
56 | "inquirer": "^0.5.1",
57 | "insert-css": "^0.1.1",
58 | "marked": "^0.3.2",
59 | "memoize-sync": "0.0.2",
60 | "mesh-normals": "^1.0.0",
61 | "mkdirp": "^0.5.0",
62 | "mouse-position": "^1.0.0",
63 | "mouse-pressed": "0.0.1",
64 | "ndarray": "^1.0.15",
65 | "ndarray-distance": "0.0.0",
66 | "opener": "^1.3.0",
67 | "quotemeta": "0.0.0",
68 | "raf": "^2.0.1",
69 | "remove-element": "0.0.0",
70 | "rework": "^0.20.3",
71 | "rework-inline": "^0.2.0",
72 | "rework-npm": "^0.6.1",
73 | "right-now": "^1.0.0",
74 | "sidenote": "^1.0.0",
75 | "sliced": "0.0.5",
76 | "stanford-dragon": "^1.0.0",
77 | "wordwrap": "0.0.2",
78 | "xhr": "^1.9.0",
79 | "zfill": "0.0.2"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/postpack.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 |
3 | var pkg = JSON.parse(fs.readFileSync(
4 | __dirname + '/package.json'
5 | , 'utf8'))
6 |
7 | delete pkg.scripts.postinstall
8 |
9 | fs.writeFileSync(__dirname + '/package.json', JSON.stringify(pkg, null, 2))
10 |
--------------------------------------------------------------------------------
/prepack.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 |
3 | var pkg = JSON.parse(fs.readFileSync(
4 | __dirname + '/package.json'
5 | , 'utf8'))
6 |
7 | pkg.scripts.postinstall = 'npm rebuild --prefix ./'
8 |
9 | fs.writeFileSync(__dirname + '/package.json', JSON.stringify(pkg, null, 2))
10 |
--------------------------------------------------------------------------------
/start.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'development'
2 |
3 | var mkdirp = require('mkdirp')
4 | var path = require('path')
5 | var answers = path.resolve(__dirname, 'answers')
6 |
7 | // mkdir answers; cd answers && NODE_ENV=development node ..
8 | mkdirp.sync(answers)
9 | process.chdir(answers)
10 | require('./')
11 |
--------------------------------------------------------------------------------
/style/index.js:
--------------------------------------------------------------------------------
1 | var auto = require('autoprefixer')('last 2 versions')
2 | var memoize = require('memoize-sync')
3 | var rwnpm = require('rework-npm')
4 | var rework = require('rework')
5 | var fs = require('fs')
6 |
7 | module.exports = process.env.NODE_ENV === 'development'
8 | ? getCSS
9 | : memoize(getCSS, function(){})
10 |
11 | function getCSS() {
12 | var css = fs.readFileSync(__dirname + '/index.css', 'utf8')
13 |
14 | css = rework(css)
15 | .use(rwnpm({ dir: __dirname }))
16 | .use(rework.ease())
17 | .use(rework.inline(__dirname + '/../assets'))
18 | .toString()
19 |
20 | css = auto.process(css).css
21 |
22 | return css
23 | }
24 |
--------------------------------------------------------------------------------
/style/reset.css:
--------------------------------------------------------------------------------
1 | /*
2 | html5doctor.com Reset Stylesheet
3 | v1.6.1
4 | Last Updated: 2010-09-17
5 | Author: Richard Clark - http://richclarkdesign.com
6 | Twitter: @rich_clark
7 | */
8 | abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:0 0}body{line-height:1}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:0 0}ins{background-color:#ff9;color:#000;text-decoration:none}mark{background-color:#ff9;color:#000;font-style:italic;font-weight:700}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help}table{border-collapse:collapse;border-spacing:0}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}input,select{vertical-align:middle}
9 |
--------------------------------------------------------------------------------