├── .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 | [![NPM](https://nodei.co/npm/shader-school.png?downloads=true&stars=true)](https://nodei.co/npm/shader-school/) 4 | [![NPM](https://nodei.co/npm-dl/shader-school.png)](https://nodei.co/npm/shader-school/) 5 | 6 | **An introduction to GLSL shaders and graphics programming that runs in your web browser.** 7 | 8 | ![shader-school](http://imgur.com/Wc74MPW.png) 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 | 46 | 47 | 48 | 49 |
Mikola LysenkoGitHub/mikolalysenkoTwitter/@mikolalysenko
Hugh KennedyGitHub/hughskTwitter/@hughskennedy
Chris DickinsonGitHub/chrisdickinsonTwitter/@isntitvacant
50 | 51 | ## Color Scheme 52 | 53 | ![color scheme](http://imgur.com/mcbbaNt.png) 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 | ![screenshot](http://imgur.com/snv1Axn.png) 69 | 70 | ![screenshot](http://imgur.com/GJtvajl.png) 71 | 72 | ![screenshot](http://imgur.com/hegi9dZ.png) 73 | 74 | ![screenshot](http://imgur.com/QYyBoea.png) 75 | 76 | ![screenshot](http://imgur.com/MZyhKjs.png) 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 |
2 | 7 | 8 |
9 |
10 |
11 |
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 | --------------------------------------------------------------------------------