diff --git a/Demo.gif b/Demo.gif new file mode 100644 index 0000000..e0ffa20 Binary files /dev/null and b/Demo.gif differ diff --git a/README.md b/README.md index c636328..062dcfc 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,15 @@

(source: Ken Perlin)

+## Result + +![Image1](https://github.com/QennyS/hw00-webgl-intro/blob/master/Demo.gif) + +I used FBM noise which is adopted from Adam's implementation on Shadertoy and modified it with Iq's warping technique to create such deformed pattern. +As for the vertex shader, I simply make all vertices bounce from it's original position to a new position using sin function. + +Live Link: https://qennys.github.io/hw00-webgl-intro/ + ## Objective - Check that the tools and build configuration we will be using for the class works. - Start learning Typescript and WebGL2 diff --git a/src/geometry/Cube.ts b/src/geometry/Cube.ts new file mode 100644 index 0000000..f69fa6f --- /dev/null +++ b/src/geometry/Cube.ts @@ -0,0 +1,102 @@ +import {vec3, vec4} from 'gl-matrix'; +import Drawable from '../rendering/gl/Drawable'; +import {gl} from '../globals'; + + +class Cube extends Drawable { + indices: Uint32Array; + positions: Float32Array; + normals: Float32Array; + center: vec4; + + constructor(center: vec3) { + super(); // Call the constructor of the super class. This is required. + this.center = vec4.fromValues(center[0], center[1], center[2], 1); + } + + create() { + + this.indices = new Uint32Array ([0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23]); + + this.normals = new Float32Array([ + 0, 0, 1, 0, + 0, 0, 1, 0, + 0, 0, 1, 0, + 0, 0, 1, 0, + + 0, 0, -1, 0, + 0, 0, -1, 0, + 0, 0, -1, 0, + 0, 0, -1, 0, + + -1, 0, 0, 0, + -1, 0, 0, 0, + -1, 0, 0, 0, + -1, 0, 0, 0, + + 1, 0, 0, 0, + 1, 0, 0, 0, + 1, 0, 0, 0, + 1, 0, 0, 0, + + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + + 0, -1, 0, 0, + 0, -1, 0, 0, + 0, -1, 0, 0, + 0, -1, 0, 0]); + + this.positions = new Float32Array([ + -2, -2, 0, 1, + 2, -2, 0, 1, + 2, 2, 0, 1, + -2, 2, 0, 1, + // back + 2, -2, -4, 1, + 2, 2, -4, 1, + -2, 2, -4, 1, + -2, -2, -4, 1, + // left + -2, -2, 0, 1, + -2, -2, -4, 1, + -2, 2, -4, 1, + -2, 2, 0, 1, + // right + 2, -2, 0, 1, + 2, -2, -4, 1, + 2, 2, -4, 1, + 2, 2, 0, 1, + // top + -2, 2, 0, 1, + 2, 2, 0, 1, + 2, 2, -4, 1, + -2, 2, -4, 1, + //bottom + -2, -2, -4, 1, + 2, -2, -4, 1, + 2, -2, 0, 1, + -2, -2, 0, 1 + ]); + + this.generateIdx(); + this.generateNor(); + this.generatePos(); + + this.count = this.indices.length; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.bufIdx); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.bufNor); + gl.bufferData(gl.ARRAY_BUFFER, this.normals, gl.STATIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.bufPos); + gl.bufferData(gl.ARRAY_BUFFER, this.positions, gl.STATIC_DRAW); + + console.log('create cube'); + } + }; + + export default Cube; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 65a9461..c96526f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import {vec3} from 'gl-matrix'; +import {vec3, vec4} from 'gl-matrix'; const Stats = require('stats-js'); import * as DAT from 'dat.gui'; import Icosphere from './geometry/Icosphere'; @@ -7,23 +7,29 @@ import OpenGLRenderer from './rendering/gl/OpenGLRenderer'; import Camera from './Camera'; import {setGL} from './globals'; import ShaderProgram, {Shader} from './rendering/gl/ShaderProgram'; +import Cube from './geometry/Cube'; // Define an object with application parameters and button callbacks // This will be referred to by dat.GUI's functions that add GUI elements. const controls = { tesselations: 5, 'Load Scene': loadScene, // A function pointer, essentially + Color: [255.0, 255.0, 255.0] }; let icosphere: Icosphere; let square: Square; +let cube: Cube; let prevTesselations: number = 5; +let currentTime: number = 0.0; function loadScene() { icosphere = new Icosphere(vec3.fromValues(0, 0, 0), 1, controls.tesselations); icosphere.create(); square = new Square(vec3.fromValues(0, 0, 0)); square.create(); + cube = new Cube(vec3.fromValues(0, 0, 0)); + cube.create(); } function main() { @@ -39,6 +45,7 @@ function main() { const gui = new DAT.GUI(); gui.add(controls, 'tesselations', 0, 8).step(1); gui.add(controls, 'Load Scene'); + gui.addColor(controls, 'Color').onChange(setColor); // get canvas and webgl context const canvas = document.getElementById('canvas'); @@ -64,22 +71,36 @@ function main() { new Shader(gl.FRAGMENT_SHADER, require('./shaders/lambert-frag.glsl')), ]); + const noise = new ShaderProgram([ + new Shader(gl.VERTEX_SHADER, require('./shaders/custom-vert.glsl')), + new Shader(gl.FRAGMENT_SHADER, require('./shaders/noise-frag.glsl')), + ]); + + noise.setGeometryColor(vec4.fromValues(1.0, 1.0, 1.0, 1.0)); + // This function will be called every frame function tick() { camera.update(); stats.begin(); gl.viewport(0, 0, window.innerWidth, window.innerHeight); renderer.clear(); + + noise.setTime(currentTime); + currentTime++; + if(controls.tesselations != prevTesselations) { prevTesselations = controls.tesselations; icosphere = new Icosphere(vec3.fromValues(0, 0, 0), 1, prevTesselations); icosphere.create(); } - renderer.render(camera, lambert, [ + renderer.render(camera, noise, [ icosphere, + // cube // square, ]); + + stats.end(); // Tell the browser to call `tick` again whenever it renders a new frame @@ -98,6 +119,11 @@ function main() { // Start the render loop tick(); + + function setColor() { + noise.setGeometryColor(vec4.fromValues(controls.Color[0] / 255.0, controls.Color[1] / 255.0, controls.Color[2] / 255.0, 1.0)); + } } + main(); diff --git a/src/rendering/gl/OpenGLRenderer.ts b/src/rendering/gl/OpenGLRenderer.ts index 7e527c2..3949f6a 100644 --- a/src/rendering/gl/OpenGLRenderer.ts +++ b/src/rendering/gl/OpenGLRenderer.ts @@ -25,13 +25,13 @@ class OpenGLRenderer { render(camera: Camera, prog: ShaderProgram, drawables: Array) { let model = mat4.create(); let viewProj = mat4.create(); - let color = vec4.fromValues(1, 0, 0, 1); + //let color = vec4.fromValues(1, 0, 0, 1); mat4.identity(model); mat4.multiply(viewProj, camera.projectionMatrix, camera.viewMatrix); prog.setModelMatrix(model); prog.setViewProjMatrix(viewProj); - prog.setGeometryColor(color); + //prog.setGeometryColor(color); for (let drawable of drawables) { prog.draw(drawable); diff --git a/src/rendering/gl/ShaderProgram.ts b/src/rendering/gl/ShaderProgram.ts index 67fef40..4b6d44f 100644 --- a/src/rendering/gl/ShaderProgram.ts +++ b/src/rendering/gl/ShaderProgram.ts @@ -29,6 +29,7 @@ class ShaderProgram { unifModelInvTr: WebGLUniformLocation; unifViewProj: WebGLUniformLocation; unifColor: WebGLUniformLocation; + unifTime: WebGLUniformLocation; constructor(shaders: Array) { this.prog = gl.createProgram(); @@ -48,6 +49,7 @@ class ShaderProgram { this.unifModelInvTr = gl.getUniformLocation(this.prog, "u_ModelInvTr"); this.unifViewProj = gl.getUniformLocation(this.prog, "u_ViewProj"); this.unifColor = gl.getUniformLocation(this.prog, "u_Color"); + this.unifTime = gl.getUniformLocation(this.prog, "u_Time"); } use() { @@ -85,6 +87,13 @@ class ShaderProgram { } } + setTime(t: number) { + this.use(); + if (this.unifTime !== -1) { + gl.uniform1f(this.unifTime, t); + } + } + draw(d: Drawable) { this.use(); diff --git a/src/shaders/custom-vert.glsl b/src/shaders/custom-vert.glsl new file mode 100644 index 0000000..88827e5 --- /dev/null +++ b/src/shaders/custom-vert.glsl @@ -0,0 +1,60 @@ +#version 300 es + +//This is a vertex shader. While it is called a "shader" due to outdated conventions, this file +//is used to apply matrix transformations to the arrays of vertex data passed to it. +//Since this code is run on your GPU, each vertex is transformed simultaneously. +//If it were run on your CPU, each vertex would have to be processed in a FOR loop, one at a time. +//This simultaneous transformation allows your program to run much faster, especially when rendering +//geometry with millions of vertices. + +uniform mat4 u_Model; // The matrix that defines the transformation of the + // object we're rendering. In this assignment, + // this will be the result of traversing your scene graph. + +uniform mat4 u_ModelInvTr; // The inverse transpose of the model matrix. + // This allows us to transform the object's normals properly + // if the object has been non-uniformly scaled. + +uniform mat4 u_ViewProj; // The matrix that defines the camera's transformation. + // We've written a static matrix for you to use for HW2, + // but in HW3 you'll have to generate one yourself + +in vec4 vs_Pos; // The array of vertex positions passed to the shader + +in vec4 vs_Nor; // The array of vertex normals passed to the shader + +in vec4 vs_Col; // The array of vertex colors passed to the shader. + +out vec4 fs_Nor; // The array of normals that has been transformed by u_ModelInvTr. This is implicitly passed to the fragment shader. +out vec4 fs_LightVec; // The direction in which our virtual light lies, relative to each vertex. This is implicitly passed to the fragment shader. +out vec4 fs_Col; // The color of each vertex. This is implicitly passed to the fragment shader. +out vec4 fs_Pos; + +const vec4 lightPos = vec4(5, 5, 3, 1); //The position of our virtual light, which is used to compute the shading of + //the geometry in the fragment shader. + +uniform float u_Time; + +void main() +{ + fs_Col = vs_Col; // Pass the vertex colors to the fragment shader for interpolation + + mat3 invTranspose = mat3(u_ModelInvTr); + fs_Nor = vec4(invTranspose * vec3(vs_Nor), 0); // Pass the vertex normals to the fragment shader for interpolation. + // Transform the geometry's normals by the inverse transpose of the + // model matrix. This is necessary to ensure the normals remain + // perpendicular to the surface after the surface is transformed by + // the model matrix. + + + vec4 modelposition = u_Model * vs_Pos; // Temporarily store the transformed vertex positions for use below + fs_Pos = modelposition; + + vec4 newPos = u_Model * vec4(vs_Pos.xyz * 2.0 , 1); + vec4 interpPos = mix(modelposition, newPos, sin(u_Time * 0.01)); + + fs_LightVec = lightPos - modelposition; // Compute the direction in which the light source lies + + gl_Position = u_ViewProj * interpPos;// gl_Position is a built-in variable of OpenGL which is + // used to render the final positions of the geometry's vertices +} diff --git a/src/shaders/lambert-frag.glsl b/src/shaders/lambert-frag.glsl index 2b8e11b..8a94c87 100644 --- a/src/shaders/lambert-frag.glsl +++ b/src/shaders/lambert-frag.glsl @@ -41,3 +41,5 @@ void main() // Compute final shaded color out_Col = vec4(diffuseColor.rgb * lightIntensity, diffuseColor.a); } + + diff --git a/src/shaders/noise-frag.glsl b/src/shaders/noise-frag.glsl new file mode 100644 index 0000000..2f51a03 --- /dev/null +++ b/src/shaders/noise-frag.glsl @@ -0,0 +1,92 @@ +#version 300 es + +// This is a fragment shader. If you've opened this file first, please +// open and read lambert.vert.glsl before reading on. +// Unlike the vertex shader, the fragment shader actually does compute +// the shading of geometry. For every pixel in your program's output +// screen, the fragment shader is run for every bit of geometry that +// particular pixel overlaps. By implicitly interpolating the position +// data passed into the fragment shader by the vertex shader, the fragment shader +// can compute what color to apply to its pixel based on things like vertex +// position, light position, and vertex color. +precision highp float; + +uniform vec4 u_Color; // The color with which to render this instance of geometry. +uniform float u_Time; + +// These are the interpolated values out of the rasterizer, so you can't know +// their specific values without knowing the vertices that contributed to them +in vec4 fs_Nor; +in vec4 fs_LightVec; +in vec4 fs_Col; +in vec4 fs_Pos; + +out vec4 out_Col; // This is the final output color that you will see on your + // screen for the pixel that is currently being processed. + +#define NUM_OCTAVES 5 + +float random1(vec2 p) { + return fract(sin(dot(p, vec2(456.789, 20487145.123))) * 842478.5453); +} + +float random1( vec3 p ) { + return fract(sin(dot(p, vec3(127.1, 311.7, 191.999))) * 43758.5453); +} + +float mySmootherStep(float a, float b, float t) { + t = t*t*t*(t*(t*6.0 - 15.0) + 10.0); + return mix(a, b, t); +} + +float interpNoise3D1(vec3 p) { + vec3 pFract = fract(p); + float llb = random1(floor(p)); + float lrb = random1(floor(p) + vec3(1.0,0.0,0.0)); + float ulb = random1(floor(p) + vec3(0.0,1.0,0.0)); + float urb = random1(floor(p) + vec3(1.0,1.0,0.0)); + + float llf = random1(floor(p) + vec3(0.0,0.0,1.0)); + float lrf = random1(floor(p) + vec3(1.0,0.0,1.0)); + float ulf = random1(floor(p) + vec3(0.0,1.0,1.0)); + float urf = random1(floor(p) + vec3(1.0,1.0,1.0)); + + float lerpXLB = mySmootherStep(llb, lrb, pFract.x); + float lerpXHB = mySmootherStep(ulb, urb, pFract.x); + float lerpXLF = mySmootherStep(llf, lrf, pFract.x); + float lerpXHF = mySmootherStep(ulf, urf, pFract.x); + + float lerpYB = mySmootherStep(lerpXLB, lerpXHB, pFract.y); + float lerpYF = mySmootherStep(lerpXLF, lerpXHF, pFract.y); + + return mySmootherStep(lerpYB, lerpYF, pFract.z); +} + +float fbm(vec3 p) { + float amp = 0.5; + float freq = 18.0; + float sum = 0.0; + float maxSum = 0.0; + for(int i = 0; i < NUM_OCTAVES; ++i) { + maxSum += amp; + sum += interpNoise3D1(p * freq) * amp; + amp *= 0.5; + freq *= 2.0; + } + return sum / maxSum; +} + +float pattern( vec3 p ) +{ + vec3 q = vec3(fbm(p)); + q += 0.03*sin( vec3(0.27, 0.23, 0.11) * u_Time * 0.1); + + return fbm( p + 2.0*q ); +} + +void main() +{ + vec3 color = vec3(1, 0.5, 0.3); + color = mix(color, u_Color.rgb, pattern(fs_Nor.xyz)); + out_Col = vec4(color.rgb, 1); +}