diff --git a/README.md b/README.md index 9d68747..a62ace8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,34 @@ # [HW1: Noise](https://github.com/CIS700-Procedural-Graphics/Project1-Noise) +## Stauffer Notes - Project One - Noise + +Sorry for the inelegant code. I didn't have time to make it prettier. +I left my comments in so that I can know what was going on when I get back to the code one day. + +My code implements: + +1. Two independently controlled multi-octave noise (MON) generators. +2. Each uses the same underlying noise generator, unfortunately, although this may give some good continuity effects. +3. Each can have its parameters controlled from the GUI: + + a. N1 is for generator one, N2 is for generator two + + b. Time Scale - scales down the time value to control 'speed' of animation + + c. fundamental - set the fundamental frequency of sampling + + d. harmScale - set the scaling factor between harmonics of the MON. Harmonic N = fundamental * harmScale ^ (N-1). + + e. components - the number of harmonics in the MON. + + f. persistence - scales the amplitude of each component in the MON. Values > 1 are interesting! + + g. symmetry[XYZ] - controls symmetry of noise across each axis. Only looks good if 0 or 1. Intermediate values look good in static renders but I had trouble making it smooth with how I was using time to vary vertex position. + +4. Time is used simply to offset vertex position, and vertex position is used to generate noise. This has awkward effect of making noise look to be moving in a direction in some cases. Better would be to create true 4D noise with time as 4th dim. +5. The default settings combine symmetry in X for a slow movig, high spatial-frequency noise, with symmetry in Y for a faster-moving, low spatial-frequency noise. I think this makes it look alive, like a tiny plankton creature, undulating with water pressure and life processes. + + ## Getting Started 1. [Install Node.js](https://nodejs.org/en/download/). Node.js is a JavaScript runtime. It basically allows you to run JavaScript when not in a browser. For our purposes, this is not necessary. The important part is that with it comes `npm`, the Node Package Manager. This allows us to easily declare and install external dependencies such as [three.js](https://threejs.org/), [dat.GUI](https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage), and [glMatrix](http://glmatrix.net/). Some other packages we'll be using make it significantly easier to develop your code and create modules for better code reuse and clarity. These tools make it _signficantly_ easier to write code in multiple `.js` files without globally defining everything. diff --git a/src/framework.js b/src/framework.js index 9cfcd1b..0a39988 100644 --- a/src/framework.js +++ b/src/framework.js @@ -21,9 +21,14 @@ function init(callback, update) { stats: stats }; + //Stauffer test + window.addEventListener('click', function(event) { + console.log("You clicked:", event.screenX, event.screenY); + }); + // run this function after the window loads window.addEventListener('load', function() { - + console.log(" IN load callback func"); var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 ); var renderer = new THREE.WebGLRenderer( { antialias: true } ); @@ -43,6 +48,7 @@ function init(callback, update) { // resize the canvas when the window changes window.addEventListener('resize', function() { + console.log(" IN resize cback func"); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); diff --git a/src/main.js b/src/main.js index 5fa221d..9a77235 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,43 @@ const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much import Framework from './framework' +//Make this a global so can see it in onUpdate to +// get at uniforms. +//Must be a better way!? +// +var adamMaterial = new THREE.ShaderMaterial({ +uniforms: { + image: { // Check the Three.JS documentation for the different allowed types and values + type: "t", + value: THREE.ImageUtils.loadTexture('./adam.jpg') + }, + //Maybe better to do this by creating a second shader? + uTimeMsec: { value: 0.0 }, + uN1TimeScale: { value: 15000.0 }, + uN1Scale: { value: 0.8 }, + uN1fundamental: { value: 2.71828 }, + uN1overtoneScale: { value: 2.71828 }, + uN1numComponents: { value: 3.0 }, + uN1persistence: { value: 2 }, + uN1symmetryX: {value: 1.0 }, + uN1symmetryY: {value: 0.0 }, + uN1symmetryZ: {value: 0.0 }, + + uN2TimeScale: { value: 1500.0 }, + uN2Scale: { value: 1.0 }, + uN2fundamental: { value: 0.1 }, + uN2overtoneScale: { value: 2.71828 }, + uN2numComponents: { value: 4.0 }, + uN2persistence: { value: 1.5 }, + uN2symmetryX: {value: 0.0 }, + uN2symmetryY: {value: 1.0 }, + uN2symmetryZ: {value: 0.0 } + + }, +vertexShader: require('./shaders/project1-vert.glsl'), +fragmentShader: require('./shaders/project1-frag.glsl') +}); + // called after the scene loads function onLoad(framework) { var scene = framework.scene; @@ -15,21 +52,13 @@ function onLoad(framework) { // initialize a simple box and material var box = new THREE.BoxGeometry(1, 1, 1); - - var adamMaterial = new THREE.ShaderMaterial({ - uniforms: { - image: { // Check the Three.JS documentation for the different allowed types and values - type: "t", - value: THREE.ImageUtils.loadTexture('./adam.jpg') - } - }, - vertexShader: require('./shaders/adam-vert.glsl'), - fragmentShader: require('./shaders/adam-frag.glsl') - }); - var adamCube = new THREE.Mesh(box, adamMaterial); - + var sphere = new THREE.SphereGeometry(1, 120, 120); + + //var adamCube = new THREE.Mesh(box, adamMaterial); + var adamCube = new THREE.Mesh(sphere, adamMaterial); + // set camera position - camera.position.set(1, 1, 2); + camera.position.set(0, 0, 3); camera.lookAt(new THREE.Vector3(0,0,0)); scene.add(adamCube); @@ -39,11 +68,36 @@ function onLoad(framework) { gui.add(camera, 'fov', 0, 180).onChange(function(newVal) { camera.updateProjectionMatrix(); }); + //console.log(adamMaterial.uniforms.uTime.value); + gui.add(adamMaterial.uniforms.uN1TimeScale, 'value', 0.01, 25000).name('N1 Time Scale'); + gui.add(adamMaterial.uniforms.uN1Scale, 'value', 0.01, 10).name('N1 Size Scale'); + gui.add(adamMaterial.uniforms.uN1fundamental, 'value', 0.01, 10).name('N1 fundamental'); + gui.add(adamMaterial.uniforms.uN1overtoneScale, 'value', 0.1, 10).name('N1 harmScale'); + gui.add(adamMaterial.uniforms.uN1numComponents, 'value', 1, 16).name('N1 components'); + gui.add(adamMaterial.uniforms.uN1persistence, 'value', 0.1, 4).name('N1 persistence'); + gui.add(adamMaterial.uniforms.uN1symmetryX, 'value', 0.0, 1.0).name('N1 symmetryX'); + gui.add(adamMaterial.uniforms.uN1symmetryY, 'value', 0.0, 1.0).name('N1 symmetryY'); + gui.add(adamMaterial.uniforms.uN1symmetryZ, 'value', 0.0, 1.0).name('N1 symmetryZ'); + + gui.add(adamMaterial.uniforms.uN2TimeScale, 'value', 0.01, 25000).name('N2 Time Scale'); + gui.add(adamMaterial.uniforms.uN2Scale, 'value', 0, 10).name('N2 Size Scale'); + gui.add(adamMaterial.uniforms.uN2fundamental, 'value', 0.01, 10).name('N2 fundamental'); + gui.add(adamMaterial.uniforms.uN2overtoneScale, 'value', 0.1, 10).name('N2 harmScale'); + gui.add(adamMaterial.uniforms.uN2numComponents, 'value', 1, 16).name('N2 components'); + gui.add(adamMaterial.uniforms.uN2persistence, 'value', 0.1, 4).name('N2 persistence'); + gui.add(adamMaterial.uniforms.uN2symmetryX, 'value', 0.0, 1.0).name('N2 symmetryX'); + gui.add(adamMaterial.uniforms.uN2symmetryY, 'value', 0.0, 1.0).name('N2 symmetryY'); + gui.add(adamMaterial.uniforms.uN2symmetryZ, 'value', 0.0, 1.0).name('N2 symmetryZ'); + + //start the time + framework.startTime = Date.now(); } // called on frame updates function onUpdate(framework) { - console.log(`the time is ${new Date()}`); + //console.log(`the time is ${new Date()}`); + adamMaterial.uniforms.uTimeMsec.value = Date.now() - framework.startTime; + //console.log(adamMaterial.uniforms.uTimeMsec.value); } // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate diff --git a/src/npm-debug.log b/src/npm-debug.log new file mode 100644 index 0000000..c5f15a0 --- /dev/null +++ b/src/npm-debug.log @@ -0,0 +1,24 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'stop' ] +2 info using npm@3.10.10 +3 info using node@v6.9.4 +4 verbose stack Error: missing script: stop +4 verbose stack at run (/usr/local/lib/node_modules/npm/lib/run-script.js:151:19) +4 verbose stack at /usr/local/lib/node_modules/npm/lib/run-script.js:61:5 +4 verbose stack at /usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:356:5 +4 verbose stack at checkBinReferences_ (/usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:320:45) +4 verbose stack at final (/usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:354:3) +4 verbose stack at then (/usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:124:5) +4 verbose stack at /usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:311:12 +4 verbose stack at /usr/local/lib/node_modules/npm/node_modules/graceful-fs/graceful-fs.js:78:16 +4 verbose stack at tryToString (fs.js:455:3) +4 verbose stack at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:442:12) +5 verbose cwd /Users/michael/Box Sync/Penn/MES/700-006-Procedural-Gfx-Spr-2017/week1/HW1/Project1-Noise/src +6 error Darwin 14.5.0 +7 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "stop" +8 error node v6.9.4 +9 error npm v3.10.10 +10 error missing script: stop +11 error If you need help, you may report this error at: +11 error +12 verbose exit [ 1, true ] diff --git a/src/shaders/adam-frag.glsl b/src/shaders/adam-frag.glsl index 64ca0e0..4e3b5f1 100644 --- a/src/shaders/adam-frag.glsl +++ b/src/shaders/adam-frag.glsl @@ -7,6 +7,6 @@ void main() { vec4 color = texture2D( image, vUv ); - gl_FragColor = vec4( color.rgb, 1.0 ); + gl_FragColor = vec4( color.gbr, 1.0 ); } \ No newline at end of file diff --git a/src/shaders/adam-vert.glsl b/src/shaders/adam-vert.glsl index e4b8cc0..aa0d85f 100644 --- a/src/shaders/adam-vert.glsl +++ b/src/shaders/adam-vert.glsl @@ -1,6 +1,6 @@ - +uniform float uScale; varying vec2 vUv; void main() { vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position * uScale, 1.0 ); } \ No newline at end of file diff --git a/src/shaders/project1-frag.glsl b/src/shaders/project1-frag.glsl new file mode 100644 index 0000000..7b9dc8a --- /dev/null +++ b/src/shaders/project1-frag.glsl @@ -0,0 +1,17 @@ +varying vec2 vUv; +varying float vNoise; +uniform sampler2D image; + +float getSin(float t){ + return sin( t * 1.57); +} + +void main() { + + //vec4 color = texture2D( image, vUv ); + //gl_FragColor = vec4( color.gbr, 1.0 ); + //float val = getSin( vUv.x ); + //gl_FragColor = vec4( val, val, val, 1.0 ); + + gl_FragColor = vec4( vNoise*vNoise*vNoise, vNoise*vNoise, (1.0-vNoise) * (1.0-vNoise), 1.0 ); +} \ No newline at end of file diff --git a/src/shaders/project1-vert.glsl b/src/shaders/project1-vert.glsl new file mode 100644 index 0000000..554f02c --- /dev/null +++ b/src/shaders/project1-vert.glsl @@ -0,0 +1,250 @@ +uniform float uTimeMsec; //current time in msec + +uniform float uN1Scale; +uniform float uN1persistence; +uniform float uN1fundamental; +uniform float uN1overtoneScale; +uniform float uN1numComponents; +uniform float uN1symmetryX, uN1symmetryY, uN1symmetryZ; +uniform float uN1TimeScale; //scaling factor for time + +//I think I could just make a uniform vec3 - but how to handle that in gui? +vec3 N1symmetry = vec3( uN1symmetryX, uN1symmetryY, uN1symmetryZ ); + +uniform float uN2Scale; +uniform float uN2persistence; +uniform float uN2fundamental; +uniform float uN2overtoneScale; +uniform float uN2numComponents; +uniform float uN2symmetryX, uN2symmetryY, uN2symmetryZ; +uniform float uN2TimeScale; //scaling factor for time + +vec3 N2symmetry = vec3( uN2symmetryX, uN2symmetryY, uN2symmetryZ ); + +varying vec2 vUv; +varying float vNoise; + + +////////// Interpolation /////////// + +//linear interp +//t must be [0,1] +// +float lerp( float v0, float v1, float t ){ + return v0 + (v1 - v0) * t; +} + +//bilinear interp +//Assumes points are 1.0 unit apart +//ll = "lower left" point, etc +//t01 and t02 are fractional [0,1] position between points[0,1] and points[0,2], respectively +// +float blerp( vec4 points, float t01, float t02 ){ + float llerp = lerp( points[0], points[1], t01 ); + float ulerp = lerp( points[2], points[3], t01 ); + return lerp( llerp, ulerp, t02 ); +} + +//trilinear interp +//Assumes points are 1.0 unit apart +//fll = "front lower left" point, etc +//t01, t02 and tfb are fractional [0,1] position between left/right points, +// lower/upper points, and front/rear points, respectively +// +float trilerp( vec4 face1, vec4 face2, float t01, float t02, float tfb ){ + float fblerp = blerp( face1, t01, t02 ); + float bblerp = blerp( face2, t01, t02 ); + return lerp( fblerp, bblerp, tfb ); +} + +/////////// Noise ////////////// + +//noise example from lecture slides +//Looks like this outputs [0,1] cuz of sin() +// +float noise_gen2(float x, float y) { + float val = sin( dot( vec2(x,y), vec2( 12.9898, 78.233 )) ) * 43758.5453; + return val - floor(val); +} +float noise_gen3(float x, float y, float z) { + float val = sin( dot( vec2(x, y), vec2( z, 78.233 )) ) * 43758.5453; + return val - floor(val); +} + +float noise_gen3b(float x, float y, float z) { + float val = sin( dot( vec2(x+y, y+z), vec2( z+x, (x+y+z)/3.0 /*78.233*/ )) ) * 43758.5453; + //changed to combos of vals to make symmetries around axes less obvious + //seems to make them become a little more radial, at least at the pole + return val - floor(val); + + //Interesting octant symmetries arise: + //float sign = 1.0 * sign(x) * sign(y) * sign(z); + //return ( ( (val - floor(val)) * sign ) + 1.0 ) / 2.0; +} + +float noise_gen3cSymm(float x, float y, float z) { + float xN = sin( dot( vec2(x, 23.1406926327792690), vec2( 2.6651441426902251, 78.233 )) ) * 43758.5453; + float yN = sin( dot( vec2(y, 2.6651441426902251), vec2( 78.233, 23.1406926327792690 )) ) * 43758.5453; + float zN = sin( dot( vec2(z, 78.233), vec2( 23.1406926327792690, 2.6651441426902251 )) ) * 43758.5453; + + /* + //Tried doing variable symmetry here, but is very jump as you move through [0,1]. But I don't + // understand why. For a given x,y,z, xN and xAbs don't change, so the lerp between + // them is smooth, so the change in valx and final val should be smooth. + // The per-axis symmetry looks right when symm val is 1.0, but in between it's jumpy. + float xAbs = sin( dot( vec2(abs(x), 23.1406926327792690), vec2( 2.6651441426902251, 78.233 )) ) * 43758.5453; + float yAbs = sin( dot( vec2(abs(y), 2.6651441426902251), vec2( 78.233, 23.1406926327792690 )) ) * 43758.5453; + float zAbs = sin( dot( vec2(abs(z), 78.233), vec2( 23.1406926327792690, 2.6651441426902251 )) ) * 43758.5453; + + float valx = lerp( xN, xAbs, uN1symmetryX ); + float valy = lerp( yN, yAbs, uN1symmetryY ); + float valz = lerp( zN, zAbs, uN1symmetryZ ); + */ + + float valx = xN, valy = yN, valz = zN; + + float val = (valx + valy + valz) / 3.0; + return fract(val); +} + +//This samples the generated noise at a particular frequency (relative freq's, really). +//x and y are called 'query' points, to distinguish from sample points +//It always samples the noise func at integer values, and interpolates between those. +//freq - Frequency 1 yields samples of floor(x) and floor(x)+1, and similarly for y. +//Frequency f samples at floor(f*x) and floor(f*x)+1. This will make the query points +// for a given domain get spread out over more sample bins and thus get more peaks (i.e. noisier) +// as f increases. This also 'moves' the sample points along for a given query domain, so +// we don't get output that's aligned over different frequencies. +//freq is a float so we can do non-integer harmonics +// +float noise_query3D(float x, float y, float z, float f /*frequency*/){ + //scale by frequency + float xs = x * f; + float ys = y * f; + float zs = z * f; + //"lower-left" value + vec4 face1; + face1[0] = noise_gen3cSymm( floor(xs), floor(ys), floor(zs) ); + face1[1] = noise_gen3cSymm( floor(xs) + 1.0, floor(ys), floor(zs) ); + face1[2] = noise_gen3cSymm( floor(xs), floor(ys) + 1.0, floor(zs) ); + face1[3] = noise_gen3cSymm( floor(xs) + 1.0, floor(ys) + 1.0, floor(zs) ); + + vec4 face2; + face2[0] = noise_gen3cSymm( floor(xs), floor(ys), floor(zs) + 1.0 ); + face2[1] = noise_gen3cSymm( floor(xs) + 1.0, floor(ys), floor(zs) + 1.0 ); + face2[2] = noise_gen3cSymm( floor(xs), floor(ys) + 1.0, floor(zs) + 1.0 ); + face2[3] = noise_gen3cSymm( floor(xs) + 1.0, floor(ys) + 1.0, floor(zs) + 1.0 ); + + return trilerp( face1, face2, xs - floor(xs), ys - floor(ys), zs - floor(zs) ); +} + + +//Simple time-varying sample. +//If just sample cube in two locations and interp, will have same trouble with symmetry. +//The thing to do is just make a 4D noise generator, to make it easy to keep symmetry +/* +float noise_query4D(float x, float y, float z, float t, float f ){ + float val0 = noise_query3D( x, y, z, f ); + float offset = uTimeMsec / uN1TimeScale; + ... +} +*/ + +//Multi-octave noise +//However, is generalized to non-octave overtones series, based on arbitrary overtone scale factor. +//numComponents - number of frequency components to generate, including fundamental +// +float MON(float x, float y, float z, float fundamental, float overtoneScale, float numComponents, float persistence ) { + float f = fundamental; + float amp = 1.0; + float result = 0.0; + float scale = 0.0; //Track accumlated maximum scale to keep result in [0,1] + //for-loop issue + //can't compare against a non-const expression in the loop, cuz (at least + // on some hardware), loop gets unrolled at compile time so it has to + // know how many iterations to do + #define MAX_COMPONENTS 16.0 //Can also do a const var + for( float component = 1.0; component <= MAX_COMPONENTS; component++ ){ + float noise = noise_query3D( x, y, z, f ); + result += noise * amp; + //update for next overtone + f *= overtoneScale; + scale += amp; + amp *= persistence; + if ( component == numComponents ) + break; + } + return result / scale; +} + +////////////// Main /////////////////// + +void main() { + vUv = uv; + //float noise = noise_gen2( uv.x, uv.y ); + //vNoise = noise_query( position.x, position.y, 1.0 /*freq*/ ); + float pOffset = 0.0; //offset to lessen symmetry from verts symmetrical around axes + + //////// First Noise //////// + + //"time" offset + // + vec3 newPos = position; + //this method spreads out sample points over time until things get really spikey + //vec3 newPos = position * vec3( uTimeMsec / uN1TimeScale ); + // + //this ruins symmetry because positions move towards all positive + //vec3 newPos = position + vec3( uTimeMsec / uN1TimeScale ); + // + //this keeps symmetry working but creates lines along axes, between octants - why? + //because it exagerates the difference betweem points at the axes, i.e. between pos/neg values? + //vec3 newPos = position + sign(position) * vec3( uTimeMsec / uN1TimeScale ); + // + //combine the two above methods, using the sign() method for symmetric values + // and blend. But still get artifact at intermediate values. + vec3 newPosR = position + vec3( uTimeMsec / uN1TimeScale ); + vec3 newPosAbs = position + sign(position) * vec3( uTimeMsec / uN1TimeScale ); + for( int i=0; i < 3; i++ ) + newPos[i] = lerp( newPosR[i], newPosAbs[i], N1symmetry[i]); + + + //Doing variable symmetry here by lerping between each vertex component and its abs(). + // This transitions smoothly from [0,1], as opposed to method above in noise_gen3cSymm. + // However when symm vals are close to 0.5, get artifacts in negative octants - looks like + // noise is going to 0, but can't understand why. Maybe cuz projections in noise_gen3cSymm + // get closer to same values since there's less movement in position. I tried using large (500) + // pOffset to get resultant position vals away from 0, but still seeing artifacts. + //AND the symmetry is skewed on local level, like order of quads is getting properly reversed but not + // the contents of each quad + float noiseN1 = MON( + lerp( newPos.x, abs(newPos.x), uN1symmetryX ) + pOffset, + lerp( newPos.y, abs(newPos.y), uN1symmetryY ) + pOffset, + lerp( newPos.z, abs(newPos.z), uN1symmetryZ ) + pOffset, + uN1fundamental, + uN1overtoneScale, + uN1numComponents, + uN1persistence); + + //////// Second noise //////// + + //Should set it up to use a different noise generator, but no time for that now... + + newPosR = position + vec3( uTimeMsec / uN2TimeScale ); + newPosAbs = position + sign(position) * vec3( uTimeMsec / uN2TimeScale ); + for( int i=0; i < 3; i++ ) + newPos[i] = lerp( newPosR[i], newPosAbs[i], N2symmetry[i]); + float noiseN2 = MON( + lerp( newPos.x, abs(newPos.x), uN2symmetryX ), + lerp( newPos.y, abs(newPos.y), uN2symmetryY ), + lerp( newPos.z, abs(newPos.z), uN2symmetryZ ), + uN2fundamental, + uN2overtoneScale, + uN2numComponents, + uN2persistence); + + + //add them together + vNoise = ( noiseN1 * uN1Scale + noiseN2 * uN2Scale ) / (uN1Scale + uN2Scale); + vec3 perturb = normal * vNoise; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position + perturb, 1.0 ); +} \ No newline at end of file