diff --git a/README.md b/README.md index c636328..496c9b5 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,16 @@

(source: Ken Perlin)

+## Submitted Version +- Features: + - Cube class added + - User can toggle between "Lambert Shader" and "Custom Shader", using corresponding gui button + - User can change the color of the cube in Lamber Shader mode, using currColor color palette + - + - + - +- Live Demo Link: https://raykim1996.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/img/HW00-CustomShader-01.png b/img/HW00-CustomShader-01.png new file mode 100644 index 0000000..a3f0c05 Binary files /dev/null and b/img/HW00-CustomShader-01.png differ diff --git a/img/HW00-CustomShader-02.png b/img/HW00-CustomShader-02.png new file mode 100644 index 0000000..d9c8846 Binary files /dev/null and b/img/HW00-CustomShader-02.png differ diff --git a/img/HW00-LambertShader.png b/img/HW00-LambertShader.png new file mode 100644 index 0000000..cee143b Binary files /dev/null and b/img/HW00-LambertShader.png differ diff --git a/src/geometry/Cube.ts b/src/geometry/Cube.ts new file mode 100644 index 0000000..e2947ee --- /dev/null +++ b/src/geometry/Cube.ts @@ -0,0 +1,99 @@ +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, //front face + 0, 0, 1, 0, + 0, 0, 1, 0, + 0, 0, 1, 0, + 1, 0, 0, 0, //right face + 1, 0, 0, 0, + 1, 0, 0, 0, + 1, 0, 0, 0, + -1, 0, 0, 0, //left face + -1, 0, 0, 0, + -1, 0, 0, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, //up face + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, -1, 0, 0, //down face + 0, -1, 0, 0, + 0, -1, 0, 0, + 0, -1, 0, 0, + 0, 0, -1, 0, //back face + 0, 0, -1, 0, + 0, 0, -1, 0, + 0, 0, -1, 0,]); + + this.positions = new Float32Array([-1, -1, 1, 1, //front face + 1, -1, 1, 1, + 1, 1, 1, 1, + -1, 1, 1, 1, + 1, -1, 1, 1, //right face + 1, -1, -1, 1, + 1, 1, -1, 1, + 1, 1, 1, 1, + -1, -1, -1, 1, //left face + -1, -1, 1, 1, + -1, 1, 1, 1, + -1, 1, -1, 1, + -1, 1, 1, 1, //up face + 1, 1, 1, 1, + 1, 1, -1, 1, + -1, 1, -1, 1, + -1, -1, -1, 1, //down face + 1, -1, -1, 1, + 1, -1, 1, 1, + -1, -1, 1, 1, + -1, 1, -1, 1, //back face + 1, 1, -1, 1, + 1, -1, -1, 1, + -1, -1, -1, 1]); + + this.generateIdx(); + this.generatePos(); + this.generateNor(); + + 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(`Created cube`); + } +}; + +export default Cube; diff --git a/src/main.ts b/src/main.ts index 65a9461..b6579b7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,9 @@ -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'; import Square from './geometry/Square'; +import Cube from './geometry/Cube'; import OpenGLRenderer from './rendering/gl/OpenGLRenderer'; import Camera from './Camera'; import {setGL} from './globals'; @@ -13,17 +14,33 @@ import ShaderProgram, {Shader} from './rendering/gl/ShaderProgram'; const controls = { tesselations: 5, 'Load Scene': loadScene, // A function pointer, essentially + 'Lambert Shader': toggleLambert, + 'Custom Shader': toggleCustom, + currColor: [0, 255, 255, 1], }; let icosphere: Icosphere; let square: Square; +let cube: Cube; let prevTesselations: number = 5; +let time: number = 0; +let shader: number = 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 toggleLambert() { + shader = 0; +} + +function toggleCustom() { + shader = 1; } function main() { @@ -39,6 +56,9 @@ function main() { const gui = new DAT.GUI(); gui.add(controls, 'tesselations', 0, 8).step(1); gui.add(controls, 'Load Scene'); + gui.add(controls, 'Lambert Shader'); + gui.add(controls, 'Custom Shader'); + gui.addColor(controls, "currColor"); // get canvas and webgl context const canvas = document.getElementById('canvas'); @@ -64,6 +84,11 @@ function main() { new Shader(gl.FRAGMENT_SHADER, require('./shaders/lambert-frag.glsl')), ]); + const custom = new ShaderProgram([ + new Shader(gl.VERTEX_SHADER, require('./shaders/custom-vert.glsl')), + new Shader(gl.FRAGMENT_SHADER, require('./shaders/custom-frag.glsl')), + ]); + // This function will be called every frame function tick() { camera.update(); @@ -76,10 +101,27 @@ function main() { icosphere = new Icosphere(vec3.fromValues(0, 0, 0), 1, prevTesselations); icosphere.create(); } - renderer.render(camera, lambert, [ - icosphere, - // square, - ]); + let newColor = vec4.fromValues((controls.currColor[0] / 255.0), + (controls.currColor[1] / 255.0), + (controls.currColor[2] / 255.0), 1); + time += 0.01; + + if (shader == 0) { + renderer.render(camera, lambert, [ + icosphere, + // square, + cube, + ], newColor, time); + } else { + renderer.render(camera, custom, [ + icosphere, + // square, + cube, + ], newColor, time); + } + + + stats.end(); // Tell the browser to call `tick` again whenever it renders a new frame diff --git a/src/rendering/gl/OpenGLRenderer.ts b/src/rendering/gl/OpenGLRenderer.ts index 7e527c2..bac988d 100644 --- a/src/rendering/gl/OpenGLRenderer.ts +++ b/src/rendering/gl/OpenGLRenderer.ts @@ -22,16 +22,16 @@ class OpenGLRenderer { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } - render(camera: Camera, prog: ShaderProgram, drawables: Array) { + render(camera: Camera, prog: ShaderProgram, drawables: Array, color: vec4, time: number) { let model = mat4.create(); let viewProj = mat4.create(); - 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.setTime(time); for (let drawable of drawables) { prog.draw(drawable); diff --git a/src/rendering/gl/ShaderProgram.ts b/src/rendering/gl/ShaderProgram.ts index 67fef40..d72ab24 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() { @@ -78,6 +80,13 @@ class ShaderProgram { } } + setTime(t: number) { + this.use(); + if (this.unifTime !== -1) { + gl.uniform1f(this.unifTime, t); + } + } + setGeometryColor(color: vec4) { this.use(); if (this.unifColor !== -1) { diff --git a/src/shaders/custom-frag.glsl b/src/shaders/custom-frag.glsl new file mode 100644 index 0000000..e173556 --- /dev/null +++ b/src/shaders/custom-frag.glsl @@ -0,0 +1,100 @@ +#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. + +//perlin noise function +vec3 random3(vec3 p) { + return fract(sin(vec3(dot(p,vec3(137.1, 927.6, 371.919)), + dot(p,vec3(716.5, 213.3, 617.14)), + dot(p, vec3(237.69, 313.2, 107.23)))) + *23873.3207); +} + +float surflet(vec3 p, vec3 gridPoint) { + // Compute the distance between p and the grid point along each axis, and warp it with a + // quintic function so we can smooth our cells + vec3 t2 = abs(p - gridPoint); + vec3 t = vec3(1.0) - 6.0 * pow(t2, vec3(5.0)) + 15.0 * pow(t2, vec3(4.0)) - 10.0 * pow(t2, vec3(3.0)); + // Get the random vector for the grid point (assume we wrote a function random2 + // that returns a vec2 in the range [0, 1]) + vec3 gradient = random3(gridPoint) * 2. - vec3(1., 1., 1.); + // Get the vector from the grid point to P + vec3 diff = p - gridPoint; + // Get the value of our height field by dotting grid->P with our gradient + float height = dot(diff, gradient); + // Scale our height field (i.e. reduce it) by our polynomial falloff function + return height * t.x * t.y * t.z; +} + +float perlinNoise3D(vec3 p) +{ + float surfletSum = 0.0; + p = (p + (u_Time + 721.22913)) * 0.5; + for (int dx = 0; dx <= 1; ++dx) { + for (int dy = 0; dy <= 1; ++dy) { + for (int dz = 0; dz <= 1; ++dz) { + surfletSum += surflet(p, floor(p) + vec3(dx, dy, dz)); + } + } + } + return surfletSum; +} + +vec3 palette(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) +{ + return a + b * cos(7.28318 * (c * t + d)); +} + +void main() +{ + // Material base color (before shading) + vec4 diffuseColor = u_Color; + + // apply perlin noise function + vec3 pos = vec3(fs_Pos.x, fs_Pos.y, fs_Pos.z); + float p = perlinNoise3D(pos); + // (fs_Col.xyz / 255.0) + vec3 perlinA = vec3(0.2,0.8,0.8); + vec3 perlinB = vec3(0.5,0.5,0.5); + vec3 perlinC = vec3(1.0,1.0,1.0); + vec3 perlinD = vec3(0.5,0.6,0.6); + vec3 newColor = palette(p, perlinA, perlinB, perlinC, perlinD); + + + // Calculate the diffuse term for Lambert shading + float diffuseTerm = dot(normalize(fs_Nor), normalize(fs_LightVec)); + // Avoid negative lighting values + diffuseTerm = clamp(diffuseTerm, 0.0, 1.0); + + float ambientTerm = 0.2; + + float lightIntensity = diffuseTerm + ambientTerm; //Add a small float value to the color multiplier + //to simulate ambient lighting. This ensures that faces that are not + //lit by our point light are not completely black. + + // Compute final shaded color + out_Col = vec4(newColor.rgb * lightIntensity, diffuseColor.a); + +} diff --git a/src/shaders/custom-vert.glsl b/src/shaders/custom-vert.glsl new file mode 100644 index 0000000..83291bf --- /dev/null +++ b/src/shaders/custom-vert.glsl @@ -0,0 +1,135 @@ +#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 +uniform float u_Time; + +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. + +//noise basis function +float noiseFBM2D(vec2 n) +{ + return (fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453)); +} + +//interpNoise2D +float interpNoise2D(float x, float y) +{ + float intX = floor(x); + float fractX = fract(x); + float intY = floor(y); + float fractY = fract(y); + + float v1 = noiseFBM2D(vec2(intX, intY)); + float v2 = noiseFBM2D(vec2(intX + 1.0, intY)); + float v3 = noiseFBM2D(vec2(intX, intY + 1.0)); + float v4 = noiseFBM2D(vec2(intX + 1.0, intY + 1.0)); + + float i1 = mix(v1, v2, fractX); + float i2 = mix(v3, v4, fractX); + return mix(i1, i2, fractY); +} + +//fbm function +float fbm(float x, float y) +{ + float total = 0.0; + float persistence = 0.5; + float octaves = 4.0; + total = (total + sin((u_Time + 71.22913)) * 0.5); + for(float i = 1.0; i <= octaves; i++) { + float freq = pow(2.0, i); + float amp = pow(persistence, i); + + total += interpNoise2D(x * freq, y * freq) * amp; + } + return total; +} + +vec3 newPosGenerate(vec3 pos, vec3 nor) { + vec3 newPos = vec3(0, 0, 0); + if (nor.x > 0.2) { //right case + newPos = vec3((fbm(pos.z, pos.y) * 2.0), pos.y, pos.z); + } else if (nor.x < -0.2) { //left case + newPos = vec3(-(fbm(pos.z, pos.y) * 2.0), pos.y, pos.z); + } else if (nor.y > 0.2) { //up case + newPos = vec3(pos.x, (fbm(pos.x, pos.z) * 2.0), pos.z); + } else if (nor.y < -0.2) { //up case + newPos = vec3(pos.x, -(fbm(pos.x, pos.z) * 2.0), pos.z); + } else if (nor.z > 0.2) { //front case + newPos = vec3(pos.x, pos.y, (fbm(pos.x, pos.y) * 2.0)); + } else { //back case + newPos = vec3(pos.x, pos.y, -(fbm(pos.x, pos.y) * 2.0)); + } + return newPos; +} + +vec3 newPosGenerate1(vec3 pos, vec3 nor) { + float val = 0.0; + if (nor.x > 0.1) { //right case + val = fbm(pos.y, pos.z); + } else if (nor.x < -0.1) { //left case + val = fbm(pos.y, pos.z); + } else if (nor.y > 0.1) { //up case + val = fbm(pos.x, pos.z); + } else if (nor.y < -0.1) { //up case + val = fbm(pos.x, pos.z); + } else if (nor.z > 0.1) { //front case + val = fbm(pos.x, pos.y); + } else if (nor.z < -0.1) { //back case + val = fbm(pos.x, pos.y); + } + return (normalize(pos) * val * 0.8); +} + +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 * + mix( + (vs_Pos + (sin(vs_Pos * 10.0 + u_Time * 10.0) * 0.1 + cos(vs_Pos * 10.0 + u_Time * 10.0)) * 0.3), + vec4(normalize(vec3(vs_Pos)), 1), + 0.2 + 0.15 * sin(u_Time * 0.2) + ); + + fs_LightVec = lightPos - modelposition; // Compute the direction in which the light source lies + fs_Pos = modelposition; // Pass the vertex positions to the fragment shader + + gl_Position = u_ViewProj * modelposition;// 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..08c9936 100644 --- a/src/shaders/lambert-frag.glsl +++ b/src/shaders/lambert-frag.glsl @@ -12,16 +12,20 @@ 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. + + void main() { // Material base color (before shading) @@ -30,7 +34,7 @@ void main() // Calculate the diffuse term for Lambert shading float diffuseTerm = dot(normalize(fs_Nor), normalize(fs_LightVec)); // Avoid negative lighting values - // diffuseTerm = clamp(diffuseTerm, 0, 1); + diffuseTerm = clamp(diffuseTerm, 0.0, 1.0); float ambientTerm = 0.2; diff --git a/src/shaders/lambert-vert.glsl b/src/shaders/lambert-vert.glsl index 7f95a37..a454753 100644 --- a/src/shaders/lambert-vert.glsl +++ b/src/shaders/lambert-vert.glsl @@ -18,6 +18,7 @@ uniform mat4 u_ModelInvTr; // The inverse transpose of the model matrix. 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 +uniform float u_Time; in vec4 vs_Pos; // The array of vertex positions passed to the shader @@ -28,6 +29,7 @@ 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. @@ -49,5 +51,5 @@ void main() fs_LightVec = lightPos - modelposition; // Compute the direction in which the light source lies gl_Position = u_ViewProj * modelposition;// gl_Position is a built-in variable of OpenGL which is - // used to render the final positions of the geometry's vertices + // used to render the final positions of the geometry's vertices }