diff --git a/README.md b/README.md index d4ef264..5a189f1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # [Project 1: Noise](https://github.com/CIS700-Procedural-Graphics/Project1-Noise) +## Updates +Name: Grace Xu +Implemented up to 5 octaves of noise and gui controls for level of detail for the icosahedron geometry and strength of the noise (via persistence and number of octaves). Color is linear interpolated between black and white using the same noise strength. + ## Objective Get comfortable with using three.js and its shader support and generate an interesting 3D, continuous surface using a multi-octave noise algorithm. diff --git a/src/main.js b/src/main.js index 92b19a4..379be9e 100644 --- a/src/main.js +++ b/src/main.js @@ -4,8 +4,31 @@ import Framework from './framework' import Noise from './noise' import {other} from './noise' +// used to animate the icosahedron +var programStartTime; + +var myMaterial = new THREE.ShaderMaterial({ + uniforms: { + time: { // Check the Three.JS documentation for the different allowed types and values + type: "f", + value: Date.now() + }, + noiseStrength: { + type: "f", + value: 2.0 + }, + numOctaves: { + type: "f", + value: 3 + } + }, + vertexShader: require('./shaders/my-vert.glsl'), + fragmentShader: require('./shaders/my-frag.glsl') + }); + // called after the scene loads function onLoad(framework) { + programStartTime = Date.now(); var scene = framework.scene; var camera = framework.camera; var renderer = framework.renderer; @@ -15,46 +38,55 @@ function onLoad(framework) { // LOOK: the line below is synyatic sugar for the code above. Optional, but I sort of recommend it. // var {scene, camera, renderer, gui, stats} = framework; - // initialize a simple box and material - var box = new THREE.BoxGeometry(1, 1, 1); + // initialize icosahedron object + var guiFields = { + icosahedronDetail: 3, + noiseStrength: 2.0, + numOctaves: 3 + } - 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 icosahedronGeometry = new THREE.IcosahedronGeometry(1, guiFields.icosahedronDetail); + var texturedIcosahedron = new THREE.Mesh(icosahedronGeometry, myMaterial); + scene.add(texturedIcosahedron); + // set camera position - camera.position.set(1, 1, 2); + camera.position.set(1, 1, 5); camera.lookAt(new THREE.Vector3(0,0,0)); - scene.add(adamCube); - // edit params and listen to changes like this // more information here: https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage gui.add(camera, 'fov', 0, 180).onChange(function(newVal) { camera.updateProjectionMatrix(); }); + + gui.add(guiFields, 'icosahedronDetail', 0, 5).step(1).onFinishChange(function(newVal) { + scene.remove(texturedIcosahedron); + guiFields.icosahedronDetail = newVal; + icosahedronGeometry = new THREE.IcosahedronGeometry(1, newVal); + texturedIcosahedron = new THREE.Mesh(icosahedronGeometry, myMaterial); + scene.add(texturedIcosahedron); + }); + + // changes persistence of noise + gui.add(guiFields, 'noiseStrength', 0.6, 8.0).onFinishChange(function(newVal) { + myMaterial.uniforms.noiseStrength.value = newVal; + myMaterial.needsUpdate = true; + }); + + // determines number of octaves of noise + gui.add(guiFields, 'numOctaves', 1, 5).step(1).onFinishChange(function(newVal) { + myMaterial.uniforms.numOctaves.value = newVal; + myMaterial.needsUpdate = true; + }); } // called on frame updates function onUpdate(framework) { - // console.log(`the time is ${new Date()}`); + // animates icosahedron + myMaterial.uniforms.time.value = Date.now() - programStartTime; + myMaterial.needsUpdate = true; } // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate Framework.init(onLoad, onUpdate); - -// console.log('hello world'); - -// console.log(Noise.generateNoise()); - -// Noise.whatever() - -// console.log(other()) \ No newline at end of file diff --git a/src/noise.js b/src/noise.js index 2ba8699..7464a8c 100644 --- a/src/noise.js +++ b/src/noise.js @@ -1,16 +1,70 @@ +// a bunch of 3d pseudo-random noise functions that return floating point numbers between -1.0 and 1.0 +function generateNoise1(x, y, z) { + var n = x + y + z * 57; + n = (n<<13) ^ n; + return ( 1.0 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7FFFFFFF) / 1073741824.0); +} + +function linearInterpolate(a, b, t) { + return a * (1 - t) + b * t; +} + +function cosineInterpolate(a, b, t) { + var cos_t = (1 - Math.cos(t * Math.PI)) * 0.5; + return linearInterpolate(a, b, cos_t); +} + +// given a point in 3d space, produces a noise value by interpolating surrounding points +function interpolateNoise(x, y, z) { + var integerX = Math.floor(x); + var weightX = x - integerX; + + var integerY = Math.floor(y); + var weightY = y - integerY; + var integerZ = Math.floor(z); + var weightZ = z - integerZ; -function generateNoise() { - return Math.random() + var v1 = generateNoise1(integerX, integerY, integerZ); + var v2 = generateNoise1(integerX, integerY, integerZ + 1); + var v3 = generateNoise1(integerX, integerY + 1, integerZ + 1); + var v4 = generateNoise1(integerX, integerY + 1, integerZ); + + var v5 = generateNoise1(integerX + 1, integerY, integerZ); + var v6 = generateNoise1(integerX + 1, integerY, integerZ + 1); + var v7 = generateNoise1(integerX + 1, integerY + 1, integerZ + 1); + var v8 = generateNoise1(integerX + 1, integerY + 1, integerZ); + + var i1 = cosineInterpolate(v1, v5, weightX); + var i2 = cosineInterpolate(v2, v6, weightX); + var i3 = cosineInterpolate(v3, v7, weightX); + var i4 = cosineInterpolate(v4, v8, weightX); + + var ii1 = cosineInterpolate(i1, i4, weightY); + var ii2 = cosineInterpolate(i2, i3, weightY); + + return cosineInterpolate(ii1, ii2 , weightZ); } -function whatever() { - console.log('hi'); +// a multi-octave noise generation function that sums multiple noise functions together +// with each subsequent noise function increasing in frequency and decreasing in amplitude +function generateMultiOctaveNoise(x, y, z, numOctaves) { + var total = 0; + var persistence = 1/2.0; + + //loop for some number of octaves + for (var i = 0; i < numOctaves; i++) { + var frequency = Math.pow(2, i); + var amplitude = Math.pow(persistence, i); + + total += interpolateNoise(x * frequency, y * frequency, z * frequency) * amplitude; + } + + return total; } export default { - generateNoise: generateNoise, - whatever: whatever + generateMultiOctaveNoise: generateMultiOctaveNoise } export function other() { diff --git a/src/shaders/adam-frag.glsl b/src/shaders/adam-frag.glsl deleted file mode 100644 index 5dfa18c..0000000 --- a/src/shaders/adam-frag.glsl +++ /dev/null @@ -1,13 +0,0 @@ -varying vec2 vUv; -varying float noise; -uniform sampler2D image; - - -void main() { - - vec2 uv = vec2(1,1) - vUv; - vec4 color = texture2D( image, uv ); - - gl_FragColor = vec4( color.rgb, 1.0 ); - -} \ No newline at end of file diff --git a/src/shaders/adam-vert.glsl b/src/shaders/adam-vert.glsl deleted file mode 100644 index e4b8cc0..0000000 --- a/src/shaders/adam-vert.glsl +++ /dev/null @@ -1,6 +0,0 @@ - -varying vec2 vUv; -void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); -} \ No newline at end of file diff --git a/src/shaders/my-frag.glsl b/src/shaders/my-frag.glsl new file mode 100644 index 0000000..20fb1bd --- /dev/null +++ b/src/shaders/my-frag.glsl @@ -0,0 +1,16 @@ +varying vec2 vUv; +varying float vNoise; +varying vec3 vNormal; + +float linearInterpolate(float a, float b, float t) { + return a * (1.0 - t) + b * t; +} + +void main() { + float r = linearInterpolate(0.0, 0.9, vNoise); + float g = linearInterpolate(0.0, 0.9, vNoise); + float b = linearInterpolate(0.0, 0.9, vNoise); + + gl_FragColor = vec4(r, g, b, 1.0); + //gl_FragColor = vec4(vNormal.rgb, 1.0); +} \ No newline at end of file diff --git a/src/shaders/my-vert.glsl b/src/shaders/my-vert.glsl new file mode 100644 index 0000000..7649826 --- /dev/null +++ b/src/shaders/my-vert.glsl @@ -0,0 +1,90 @@ +varying vec2 vUv; +varying vec3 vNormal; +varying float vNoise; +uniform int numOctaves; +uniform float time; +uniform float noiseStrength; +const int aLargeNumber = 10; + +float generateNoise(int x, int y, int z, int numOctave) { + if (numOctave == 0) { + return fract(sin(dot(vec3(x,y,z), vec3(12.9898, 78.23, 107.81))) * 43758.5453); + } else if (numOctave == 1) { + return fract(sin(dot(vec3(z,x,y), vec3(16.363, 43.597, 199.73))) * 69484.7539); + } else if (numOctave == 2) { + return fract(sin(dot(vec3(y,x,z), vec3(13.0, 68.819, 90.989))) * 92041.9823); + } else if (numOctave == 3) { + return fract(sin(dot(vec3(x,y,z), vec3(98.1577, 47.45029, 154.85161))) * 84499.0); + } else if (numOctave == 4) { + return fract(sin(dot(vec3(z,x,y), vec3(9.75367, 83.3057, 390.353))) * 15485.653); + } +} + +float linearInterpolate(float a, float b, float t) { + return a * (1.0 - t) + b * t; +} + +float cosineInterpolate(float a, float b, float t) { + float cos_t = (1.0 - cos(t * 3.14159265359879323846264)) * 0.5; + return linearInterpolate(a, b, cos_t); +} + +// given a point in 3d space, produces a noise value by interpolating surrounding points +float interpolateNoise(float x, float y, float z, int numOctave) { + int integerX = int(floor(x)); + float weightX = x - float(integerX); + + int integerY = int(floor(y)); + float weightY = y - float(integerY); + + int integerZ = int(floor(z)); + float weightZ = z - float(integerZ); + + float v1 = generateNoise(integerX, integerY, integerZ, numOctave); + float v2 = generateNoise(integerX, integerY, integerZ + 1, numOctave); + float v3 = generateNoise(integerX, integerY + 1, integerZ + 1, numOctave); + float v4 = generateNoise(integerX, integerY + 1, integerZ, numOctave); + + float v5 = generateNoise(integerX + 1, integerY, integerZ, numOctave); + float v6 = generateNoise(integerX + 1, integerY, integerZ + 1, numOctave); + float v7 = generateNoise(integerX + 1, integerY + 1, integerZ + 1, numOctave); + float v8 = generateNoise(integerX + 1, integerY + 1, integerZ, numOctave); + + float i1 = cosineInterpolate(v1, v5, weightX); + float i2 = cosineInterpolate(v2, v6, weightX); + float i3 = cosineInterpolate(v3, v7, weightX); + float i4 = cosineInterpolate(v4, v8, weightX); + + float ii1 = cosineInterpolate(i1, i4, weightY); + float ii2 = cosineInterpolate(i2, i3, weightY); + + return cosineInterpolate(ii1, ii2 , weightZ); +} + +// a multi-octave noise generation function that sums multiple noise functions together +// with each subsequent noise function increasing in frequency and decreasing in amplitude +float generateMultiOctaveNoise(float x, float y, float z) { + float total = 0.0; + float persistence = 1.0/noiseStrength; + + //loop for some number of octaves + for (int i = 0; i < aLargeNumber; i++) { + if (i == numOctaves) break; + float frequency = pow(2.0, float(i)); + float amplitude = pow(persistence, float(i)); + + total += interpolateNoise(x * frequency, y * frequency, z * frequency, i) * amplitude; + } + + return total; +} + +void main() { + float offset = generateMultiOctaveNoise(position[0] + time/999.0, position[1] + time/999.0, position[2] + time/999.0); + vec3 newPosition = position + offset * normal; + + gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 ); + vUv = uv; + vNormal = normal; + vNoise = offset; +} \ No newline at end of file