Last active
August 29, 2015 14:21
-
-
Save sgrove/100ca330dc52cd0f2ed0 to your computer and use it in GitHub Desktop.
Compare "Learn WebGL 09" with "Learn Gamma 09"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns gampg.learn-gamma.lesson-09 | |
(:require [clojure.string :as s] | |
[gamma.api :as g] | |
[gamma.program :as p] | |
[gamma.tools :as gt] | |
[gamma-driver.drivers.basic :as driver] | |
[gamma-driver.protocols :as dp] | |
[goog.webgl :as ggl] | |
[thi.ng.geom.core :as geom] | |
[thi.ng.geom.core.matrix :as mat :refer [M44]] | |
[thi.ng.geom.webgl.arrays :as arrays])) | |
(def title | |
"9. Lots of moving objects") | |
(def u-p-matrix | |
(g/uniform "uPMatrix" :mat4)) | |
(def u-mv-matrix | |
(g/uniform "uMVMatrix" :mat4)) | |
(def a-position | |
(g/attribute "aVertexPosition" :vec3)) | |
(def a-vertex-normal | |
(g/attribute "aVertexNormal" :vec3)) | |
(def a-texture-coord | |
(g/attribute "aTextureCoord" :vec2)) | |
(def v-texture-coord | |
(g/varying "vTextureCoord" :vec2 :mediump)) | |
(def u-color | |
(g/uniform "uColor" :vec3)) | |
(def u-sampler | |
(g/uniform "uSampler" :sampler2D)) | |
(def program-source | |
(p/program | |
{:vertex-shader {(g/gl-position) (-> u-p-matrix | |
(g/* u-mv-matrix) | |
(g/* (g/vec4 a-position 1))) | |
v-texture-coord a-texture-coord} | |
:fragment-shader (let [texture-color (g/texture2D u-sampler (g/vec2 (g/swizzle v-texture-coord :st)))] | |
{(g/gl-frag-color) (g/* texture-color (g/vec4 u-color 1))})} | |
{:precision {:float :mediump}})) | |
(defn get-perspective-matrix | |
"Be sure to | |
1. pass the WIDTH and HEIGHT of the canvas *node*, not | |
the GL context | |
2. (set! (.-width/height canvas-node) | |
width/height), respectively, or you may see no results, or strange | |
results" | |
[width height] | |
(mat/perspective 45 (/ width height) 0.1 100)) | |
(defn get-normal-matrix [mv] | |
(-> mv | |
(geom/invert) | |
(geom/transpose) | |
(mat/matrix44->matrix33))) | |
(defn get-data [p mv vertices texture texture-coords color] | |
{u-p-matrix p | |
u-mv-matrix mv | |
u-color color | |
u-sampler texture | |
a-position vertices | |
a-texture-coord texture-coords}) | |
(defn reset-gl-canvas! [canvas-node] | |
(let [gl (.getContext canvas-node "webgl") | |
width (.-clientWidth canvas-node) | |
height (.-clientHeight canvas-node)] | |
;; Set the width/height (in terms of GL-resolution) to actual | |
;; canvas-element width/height (or else you'll see blurry results) | |
(set! (.-width canvas-node) width) | |
(set! (.-height canvas-node) height) | |
;; Setup GL Canvas | |
(.viewport gl 0 0 width height))) | |
(defn random-color [] | |
[(js/Math.random) (js/Math.random) (js/Math.random)]) | |
(defn make-star [star-count idx] | |
{:distance (* (/ idx star-count) 5) | |
:rotation-speed (/ idx star-count 10) | |
:angle 0 | |
:color (random-color) | |
:twinkle (random-color)}) | |
;; js/window.requestAnimationFrame doesn't take arguments, so we have | |
;; to store the state elsewhere - in this atom, for example. | |
(defn app-state [width height] | |
(let [star-count 50] | |
{:last-rendered (.getTime (js/Date.)) | |
:scene {:stars (map (partial make-star star-count) (range star-count)) | |
:star-vertices [[-1.0, -1.0, 0.0,] | |
[1.0, -1.0, 0.0,] | |
[-1.0, 1.0, 0.0,] | |
[1.0, 1.0, 0.0]] | |
:star-texture-coords [0.0, 0.0, | |
1.0, 0.0, | |
0.0, 1.0, | |
1.0, 1.0] | |
:mv (mat/matrix44) | |
:p (get-perspective-matrix width height)}})) | |
(defn draw-fn [gl driver program] | |
(fn [state] | |
(.clear gl (bit-or (.-COLOR_BUFFER_BIT gl) (.-DEPTH_BUFFER_BIT gl))) | |
(let [{:keys [p mv texture | |
stars star-vertices | |
star-texture-coords]} (:scene state) | |
time-now (.getTime (js/Date.)) | |
twinkle? (pos? (js/Math.sin (/ time-now 1000)))] | |
(doseq [star stars] | |
(let [mv (-> mv | |
(geom/translate [0 0 -7]) | |
(geom/rotate-around-axis [0 0 1] (:angle star)) | |
(geom/translate [(:distance star) 0 -7]) | |
(geom/rotate-around-axis [0 0 -1] (- (:angle star))))] | |
(driver/draw-program driver program | |
(get-data p mv star-vertices texture star-texture-coords (if twinkle? | |
(:twinkle star) | |
(:color star))) | |
{:draw-mode :triangle-strip})))))) | |
(defn animate [draw-fn step-fn current-value] | |
(js/requestAnimationFrame | |
(fn [time] | |
(let [next-value (step-fn time current-value)] | |
(draw-fn next-value) | |
(animate draw-fn step-fn next-value))))) | |
(def effective-fpms | |
(/ 60 1000)) | |
(defn move-star [elapsed star] | |
(let [distance (- (:distance star) | |
(* effective-fpms elapsed 0.01)) | |
reset (if (pos? distance) 0 5)] | |
(-> star | |
(assoc-in [:distance] (+ distance reset)) | |
(update-in [:angle] + (* (:rotation-speed star)))))) | |
(defn tick | |
"Takes the old world value and produces a new world value, suitable | |
for rendering" | |
[time state] | |
;; We get the elapsed time since the last render to compensate for | |
;; lag, etc. | |
(let [time-now (.getTime (js/Date.)) | |
elapsed (- time-now (:last-rendered state)) | |
cube-diff (/ (* 75 elapsed) 100000)] | |
(-> state | |
;; This is painful at 60FPS. Transducers? | |
(update-in [:scene :stars] (fn [stars] (mapv (partial move-star elapsed) stars))) | |
(assoc-in [:last-rendered] time-now)))) | |
(defn main [gl node] | |
(let [width (.-clientWidth node) | |
height (.-clientHeight node) | |
driver (driver/basic-driver gl) | |
program (dp/program driver program-source) | |
state (app-state width height)] | |
(reset-gl-canvas! node) | |
;; Set the blending function | |
(.blendFunc gl (.-SRC_ALPHA gl) (.-ONE gl)) | |
(.enable gl (.-BLEND gl)) | |
(.disable gl (.-DEPTH_TEST gl)) | |
(.clearColor gl 0 0 0 1) | |
(.clear gl (bit-or (.-COLOR_BUFFER_BIT gl) (.-DEPTH_BUFFER_BIT gl))) | |
(let [image (js/Image.)] | |
(aset image "onload" | |
(fn [] (let [texture {:data {:data image | |
:filter {:min :linear | |
:mag :nearest} | |
:flip-y true | |
:texture-id 0}}] | |
(animate (draw-fn gl driver program) tick (assoc-in state [:scene :texture] texture))))) | |
(aset image "src" "/images/star.gif")))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Learning WebGL — lesson 9</title> | |
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> | |
<script type="text/javascript" src="glMatrix-0.9.5.min.js"></script> | |
<script type="text/javascript" src="webgl-utils.js"></script> | |
<script id="shader-fs" type="x-shader/x-fragment"> | |
precision mediump float; | |
varying vec2 vTextureCoord; | |
uniform sampler2D uSampler; | |
uniform vec3 uColor; | |
void main(void) { | |
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); | |
gl_FragColor = textureColor * vec4(uColor, 1.0); | |
} | |
</script> | |
<script id="shader-vs" type="x-shader/x-vertex"> | |
attribute vec3 aVertexPosition; | |
attribute vec2 aTextureCoord; | |
uniform mat4 uMVMatrix; | |
uniform mat4 uPMatrix; | |
varying vec2 vTextureCoord; | |
void main(void) { | |
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); | |
vTextureCoord = aTextureCoord; | |
} | |
</script> | |
<script type="text/javascript"> | |
var gl; | |
function initGL(canvas) { | |
try { | |
gl = canvas.getContext("experimental-webgl"); | |
gl.viewportWidth = canvas.width; | |
gl.viewportHeight = canvas.height; | |
} catch (e) { | |
} | |
if (!gl) { | |
alert("Could not initialise WebGL, sorry :-("); | |
} | |
} | |
function getShader(gl, id) { | |
var shaderScript = document.getElementById(id); | |
if (!shaderScript) { | |
return null; | |
} | |
var str = ""; | |
var k = shaderScript.firstChild; | |
while (k) { | |
if (k.nodeType == 3) { | |
str += k.textContent; | |
} | |
k = k.nextSibling; | |
} | |
var shader; | |
if (shaderScript.type == "x-shader/x-fragment") { | |
shader = gl.createShader(gl.FRAGMENT_SHADER); | |
} else if (shaderScript.type == "x-shader/x-vertex") { | |
shader = gl.createShader(gl.VERTEX_SHADER); | |
} else { | |
return null; | |
} | |
gl.shaderSource(shader, str); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
alert(gl.getShaderInfoLog(shader)); | |
return null; | |
} | |
return shader; | |
} | |
var shaderProgram; | |
function initShaders() { | |
var fragmentShader = getShader(gl, "shader-fs"); | |
var vertexShader = getShader(gl, "shader-vs"); | |
shaderProgram = gl.createProgram(); | |
gl.attachShader(shaderProgram, vertexShader); | |
gl.attachShader(shaderProgram, fragmentShader); | |
gl.linkProgram(shaderProgram); | |
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { | |
alert("Could not initialise shaders"); | |
} | |
gl.useProgram(shaderProgram); | |
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); | |
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); | |
shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord"); | |
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); | |
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); | |
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); | |
shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler"); | |
shaderProgram.colorUniform = gl.getUniformLocation(shaderProgram, "uColor"); | |
} | |
function handleLoadedTexture(texture) { | |
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
gl.bindTexture(gl.TEXTURE_2D, null); | |
} | |
var starTexture; | |
function initTexture() { | |
starTexture = gl.createTexture(); | |
starTexture.image = new Image(); | |
starTexture.image.onload = function () { | |
handleLoadedTexture(starTexture) | |
} | |
starTexture.image.src = "star.gif"; | |
} | |
var mvMatrix = mat4.create(); | |
var mvMatrixStack = []; | |
var pMatrix = mat4.create(); | |
function mvPushMatrix() { | |
var copy = mat4.create(); | |
mat4.set(mvMatrix, copy); | |
mvMatrixStack.push(copy); | |
} | |
function mvPopMatrix() { | |
if (mvMatrixStack.length == 0) { | |
throw "Invalid popMatrix!"; | |
} | |
mvMatrix = mvMatrixStack.pop(); | |
} | |
function setMatrixUniforms() { | |
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix); | |
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix); | |
} | |
function degToRad(degrees) { | |
return degrees * Math.PI / 180; | |
} | |
var currentlyPressedKeys = {}; | |
function handleKeyDown(event) { | |
currentlyPressedKeys[event.keyCode] = true; | |
} | |
function handleKeyUp(event) { | |
currentlyPressedKeys[event.keyCode] = false; | |
} | |
var zoom = -15; | |
var tilt = 90; | |
var spin = 0; | |
function handleKeys() { | |
if (currentlyPressedKeys[33]) { | |
// Page Up | |
zoom -= 0.1; | |
} | |
if (currentlyPressedKeys[34]) { | |
// Page Down | |
zoom += 0.1; | |
} | |
if (currentlyPressedKeys[38]) { | |
// Up cursor key | |
tilt += 2; | |
} | |
if (currentlyPressedKeys[40]) { | |
// Down cursor key | |
tilt -= 2; | |
} | |
} | |
var starVertexPositionBuffer; | |
var starVertexTextureCoordBuffer; | |
function initBuffers() { | |
starVertexPositionBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, starVertexPositionBuffer); | |
vertices = [ | |
-1.0, -1.0, 0.0, | |
1.0, -1.0, 0.0, | |
-1.0, 1.0, 0.0, | |
1.0, 1.0, 0.0 | |
]; | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); | |
starVertexPositionBuffer.itemSize = 3; | |
starVertexPositionBuffer.numItems = 4; | |
starVertexTextureCoordBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, starVertexTextureCoordBuffer); | |
var textureCoords = [ | |
0.0, 0.0, | |
1.0, 0.0, | |
0.0, 1.0, | |
1.0, 1.0 | |
]; | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW); | |
starVertexTextureCoordBuffer.itemSize = 2; | |
starVertexTextureCoordBuffer.numItems = 4; | |
} | |
function drawStar() { | |
gl.activeTexture(gl.TEXTURE0); | |
gl.bindTexture(gl.TEXTURE_2D, starTexture); | |
gl.uniform1i(shaderProgram.samplerUniform, 0); | |
gl.bindBuffer(gl.ARRAY_BUFFER, starVertexTextureCoordBuffer); | |
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, starVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0); | |
gl.bindBuffer(gl.ARRAY_BUFFER, starVertexPositionBuffer); | |
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, starVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); | |
setMatrixUniforms(); | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, starVertexPositionBuffer.numItems); | |
} | |
function Star(startingDistance, rotationSpeed) { | |
this.angle = 0; | |
this.dist = startingDistance; | |
this.rotationSpeed = rotationSpeed; | |
// Set the colors to a starting value. | |
this.randomiseColors(); | |
} | |
Star.prototype.draw = function (tilt, spin, twinkle) { | |
mvPushMatrix(); | |
// Move to the star's position | |
mat4.rotate(mvMatrix, degToRad(this.angle), [0.0, 1.0, 0.0]); | |
mat4.translate(mvMatrix, [this.dist, 0.0, 0.0]); | |
// Rotate back so that the star is facing the viewer | |
mat4.rotate(mvMatrix, degToRad(-this.angle), [0.0, 1.0, 0.0]); | |
mat4.rotate(mvMatrix, degToRad(-tilt), [1.0, 0.0, 0.0]); | |
if (twinkle) { | |
// Draw a non-rotating star in the alternate "twinkling" color | |
gl.uniform3f(shaderProgram.colorUniform, this.twinkleR, this.twinkleG, this.twinkleB); | |
drawStar(); | |
} | |
// All stars spin around the Z axis at the same rate | |
mat4.rotate(mvMatrix, degToRad(spin), [0.0, 0.0, 1.0]); | |
// Draw the star in its main color | |
gl.uniform3f(shaderProgram.colorUniform, this.r, this.g, this.b); | |
drawStar() | |
mvPopMatrix(); | |
}; | |
var effectiveFPMS = 60 / 1000; | |
Star.prototype.animate = function (elapsedTime) { | |
this.angle += this.rotationSpeed * effectiveFPMS * elapsedTime; | |
// Decrease the distance, resetting the star to the outside of | |
// the spiral if it's at the center. | |
this.dist -= 0.01 * effectiveFPMS * elapsedTime; | |
if (this.dist < 0.0) { | |
this.dist += 5.0; | |
this.randomiseColors(); | |
} | |
}; | |
Star.prototype.randomiseColors = function () { | |
// Give the star a random color for normal | |
// circumstances... | |
this.r = Math.random(); | |
this.g = Math.random(); | |
this.b = Math.random(); | |
// When the star is twinkling, we draw it twice, once | |
// in the color below (not spinning) and then once in the | |
// main color defined above. | |
this.twinkleR = Math.random(); | |
this.twinkleG = Math.random(); | |
this.twinkleB = Math.random(); | |
}; | |
var stars = []; | |
function initWorldObjects() { | |
var numStars = 50; | |
for (var i=0; i < numStars; i++) { | |
stars.push(new Star((i / numStars) * 5.0, i / numStars)); | |
} | |
} | |
function drawScene() { | |
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix); | |
gl.blendFunc(gl.SRC_ALPHA, gl.ONE); | |
gl.enable(gl.BLEND); | |
mat4.identity(mvMatrix); | |
mat4.translate(mvMatrix, [0.0, 0.0, zoom]); | |
mat4.rotate(mvMatrix, degToRad(tilt), [1.0, 0.0, 0.0]); | |
var twinkle = document.getElementById("twinkle").checked; | |
for (var i in stars) { | |
stars[i].draw(tilt, spin, twinkle); | |
spin += 0.1; | |
} | |
} | |
var lastTime = 0; | |
function animate() { | |
var timeNow = new Date().getTime(); | |
if (lastTime != 0) { | |
var elapsed = timeNow - lastTime; | |
for (var i in stars) { | |
stars[i].animate(elapsed); | |
} | |
} | |
lastTime = timeNow; | |
} | |
function tick() { | |
requestAnimFrame(tick); | |
handleKeys(); | |
drawScene(); | |
animate(); | |
} | |
function webGLStart() { | |
var canvas = document.getElementById("lesson09-canvas"); | |
initGL(canvas); | |
initShaders(); | |
initBuffers(); | |
initTexture(); | |
initWorldObjects(); | |
gl.clearColor(0.0, 0.0, 0.0, 1.0); | |
document.onkeydown = handleKeyDown; | |
document.onkeyup = handleKeyUp; | |
tick(); | |
} | |
</script> | |
</head> | |
<body onload="webGLStart();"> | |
<a href="http://learningwebgl.com/blog/?p=1008"><< Back to Lesson 9</a><br /> | |
<canvas id="lesson09-canvas" style="border: none;" width="500" height="500"></canvas> | |
<br/> | |
<input type="checkbox" id="twinkle" /> Twinkle<br/> | |
(Use up/down cursor keys to rotate, and <code>Page Up</code>/<code>Page Down</code> to zoom out/in) | |
<br/> | |
<br/> | |
<a href="http://learningwebgl.com/blog/?p=1008"><< Back to Lesson 9</a> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment