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
}