Last active
February 12, 2018 16:27
-
-
Save fu5ha/1c1617f55486559c357b155a48878df1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- Gray Olson | |
CSE 470 --> | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" > | |
<title>3D Sierpinski Gasket</title> | |
<script id="fragment-shader" type="x-shader/x-fragment"> | |
precision mediump float; | |
// Varying coming from the vertex shader--is interpolated version of the pre-generated colors that are sent into the gpu | |
varying vec4 fColor; | |
// Uniform that gets passed in of the time passed since the beginning of the animation (in milliseconds) | |
uniform float uTime; | |
// Uniform of the resolution of the screen. Used for adjusting frag colors based on their screen space position. | |
uniform vec2 uResolution; | |
void main() | |
{ | |
// uvb is the normalized (0. to 1.) version of the x, y and z fragment coordinates based on the screen resolution | |
// and using the x size of the resolution for the normalization depth | |
vec3 uvb = gl_FragCoord.xyz / uResolution.xyx; | |
// This vector is then added to the existing color, making the colors change slightly as they move across the screen. Add operations | |
// are basically equivalent to shining a light of that color onto the existing color, so it doesn't completely replace but rather modifies | |
// the existing vertex color from the vertex shader. | |
gl_FragColor = fColor + vec4(uvb, 1.); | |
} | |
</script> | |
<script id="vertex-shader" type="x-shader/x-vertex"> | |
#define PI 3.14159265 | |
// The two attributes passed in from the CPU, position and color. | |
attribute vec4 vPosition; | |
attribute vec3 vColor; | |
// Varying that is passed to the fragment shader and is interpolated from the vColor attribute | |
varying vec4 fColor; | |
// Uniform of the projection matrix which is passed in from the CPU | |
uniform mat4 uProjection; | |
// Uniform of the time since the animation began which is used in the animation. | |
uniform mediump float uTime; | |
// A helper function that creates a homogeneous rotation matrix based on an axis and an angle | |
mat4 makeRotation3d(vec3 axis, float angle) { | |
axis = normalize(axis); | |
float s = sin(angle); | |
float c = cos(angle); | |
float oc = 1.0 - c; | |
return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, | |
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, | |
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, | |
0.0, 0.0, 0.0, 1.0); | |
} | |
void main() | |
{ | |
// Compute the distance from the current vertex to the origin--used in determining how much rotation will be applied | |
float d = distance(vPosition.xyz, vec3(0., 0., 0.)); | |
// Create the matrix that will warp the vertex. It is based both on the distance from the origin above as well | |
// as the time since the beginning of the animation. The sin function is used because it takes any input and gives | |
// a repeating, normalized value between -1. and 1. which is very useful in making repeated animations/patterns. | |
mat4 warp = makeRotation3d(vec3(0., 1., 0.), PI/3.*d*sin(uTime*1.25)); | |
// Create the matrix that rotates the entire pyramid. The composition of first the spin around the y axis, which is | |
// based on the time since the start of the animation, and then a rotation by -PI/8. on the x axis to make the camera | |
// appear to be above the pyramid and looking down at it slightly. | |
mat4 rot = makeRotation3d(vec3(1., 0., 0.), -PI/8.) * makeRotation3d(vec3(0., 1., 0.), PI/6. - uTime + PI/3.*(cos(uTime*1.25) + 1.)); | |
// The two previous rotations are composed, the warp first and then the rotation | |
mat4 totalRot = rot * warp; | |
// Assign the correct color, just directly copying the vertex color and openGL automatically interpolates it between verticies in the | |
// fragment shader. | |
fColor = vec4(vColor, 1.0); | |
// Compute the final position which starts with the initial position, rotates it, then projects it using the passed in projection | |
// matrix. | |
gl_Position = uProjection * totalRot * vPosition; | |
} | |
</script> | |
<script type="text/javascript" src="../Common/webgl-utils.js"></script> | |
<script type="text/javascript" src="../Common/initShaders.js"></script> | |
<script type="text/javascript" src="../Common/MV.js"></script> | |
<script type="text/javascript" src="assignment1_over.js"></script> | |
</head> | |
<body> | |
<!--HW470: --> | |
<h1> | |
CSE 470 HW #1 Extra: 3D Sierpinski Gasket Twirl | |
</h1> | |
<p><em>Author:</em> Gray Olson <em>Date: </em>January 19, 2018.</p> | |
<p><em>Description:</em> Displays four 2d Sierpinski gaskets in the shape of a pyramid with a twirly 3d matix transformation applied. Colored using pre-generated vertex colors per-side and per-vertex, which are also modified by a color derived the final clip-space coordinates in the fragment shader.</p> | |
<p><em>Functionality:</em></p> | |
<ul> | |
<li>Each vertex of the original gasket is assigned a color based on a base color which is provided manually per-side as well as its (x,y,z) position. Since this position is inside the range [-1, 1] and colors are on the range [0, 1], the location of the vertex was multiplied by 0.5 and then 0.5 was added to each component. These are then multiplied with the base color. Finally a color is derived from the clip-space coordinates after transformations in the vertex shader and this color is then added to the base color derived at each vertex.</li> | |
<li>Each vertex is transformed in the vertex shader first by creating a 3d rotation matrix about the Y axis with the angle of rotation being PI/4 * (distance from origin) * sin(time elapsed*1.25). It is then rotated about the y axis again by a factor of the time elapsed to create an animation and finally is rotated about the X axis so that the camera view is not straight on. It is then projected using an orthographic projection matrix generated by MV.js.</li> | |
<li>Built-in depth sampling is used to ensure closer polygons are drawn on top of further ones.</li> | |
<li>Vertex and color data is packed together into the same buffer and then extracted using vertex attributes with proper stride and offset applied.</li> | |
</ul> | |
<p><em>Parameters:</em></p> | |
<ul> | |
<li>NumTimesToSubdivide: Sets the number of recursions when performing the triangle subdividions.</li> | |
<li>Morphing controlled by constants in the vertex shader, which gets multiplied by the distance from the origin of that vertex and the time elapsed since the start of the animation and then applied as a rotation about the origin.</li> | |
</ul> | |
<p><em>Resources: </em>Prof. Angel's code, examples of building a 3d rotation matrix, Mozilla developer reference for WebGL api.</p> | |
<canvas id="gl-canvas" width="1024" height="512"> | |
Oops ... your browser doesn't support the HTML5 canvas element | |
</canvas> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Gray Olson | |
// CSE 470 | |
var canvas; | |
var gl; | |
var points = []; | |
var colors = []; | |
// Setup for the uProjection and uTime uniforms. This needs to happen outside the function so that they can be accessed in other places. | |
var uProjLoc, uTimeLoc; | |
var initialTime; | |
var NumTimesToSubdivide = 6; | |
var DEBUG_LOG = false; | |
window.onload = function init() | |
{ | |
canvas = document.getElementById( "gl-canvas" ); | |
gl = WebGLUtils.setupWebGL( canvas ); | |
if ( !gl ) { alert( "WebGL isn't available" ); } | |
// | |
// Initialize our data for the Sierpinski Gasket | |
// | |
// First, initialize the sides of the gasket, each have 3 verticies that make up that triangle | |
// HW470 | |
var sides = [ | |
[ | |
vec3( -1, 0, -1 ), | |
vec3( 0, 1, 0 ), | |
vec3( 1, 0, -1 ), | |
], | |
[ | |
vec3( -1, 0, 1 ), | |
vec3( 0, 1, 0 ), | |
vec3( -1, 0, -1 ), | |
], | |
[ | |
vec3( -1, 0, 1 ), | |
vec3( 0, 1, 0 ), | |
vec3( 1, 0, 1 ), | |
], | |
[ | |
vec3( 1, 0, 1 ), | |
vec3( 0, 1, 0 ), | |
vec3( 1, 0, -1 ), | |
], | |
]; | |
// Next, actually create the gaskets from each side. | |
// A base color is also passed in so that each side has | |
// a different color to start out with. | |
divideTriangle( sides[0][0], sides[0][1], sides[0][2], | |
NumTimesToSubdivide, vec3(1.0, 0.3, 0.0)); | |
divideTriangle( sides[1][0], sides[1][1], sides[1][2], | |
NumTimesToSubdivide, vec3(0.0, 1.0, 0.3)); | |
divideTriangle( sides[2][0], sides[2][1], sides[2][2], | |
NumTimesToSubdivide, vec3(0.0, 0.3, 1.0)); | |
divideTriangle( sides[3][0], sides[3][1], sides[3][2], | |
NumTimesToSubdivide, vec3(0.3, 0.0, 1.0)); | |
// | |
// Configure WebGL | |
// | |
gl.viewport( 0, 0, canvas.width, canvas.height ); | |
gl.clearColor( 0.21, 0.2, 0.25, 1.0 ); | |
gl.enable(gl.DEPTH_TEST); | |
// Load shaders and initialize attribute buffers | |
var program = initShaders( gl, "vertex-shader", "fragment-shader" ); | |
gl.useProgram( program ); | |
// Load the data into the GPU | |
// Create vertex buffer containing position and color data and send initial values. The data is packed together | |
// into one buffer with the vertex data first and then the color data right after it. | |
var vBuff = gl.createBuffer(); | |
gl.bindBuffer( gl.ARRAY_BUFFER, vBuff ); | |
gl.bufferData( gl.ARRAY_BUFFER, packData(flatten(points), flatten(colors)), gl.STATIC_DRAW ); | |
// Associate vPosition attribute in shader to the buffer. The appropriate "stride" is set, which is the number of bytes to skip | |
// from the start of the data for that attribute to the start of the next data. So, the total size of all the attributes in that buffer. | |
// In this case, both position and color are stored as 3 floats, and floats are 4 bytes each, so the stride is 6 * 4 bytes. | |
var vPosLoc = gl.getAttribLocation( program, "vPosition" ); | |
gl.vertexAttribPointer( vPosLoc, 3, gl.FLOAT, false, 6 * 4, 0 ); | |
gl.enableVertexAttribArray( vPosLoc ); | |
// Associate vColor attribute in shader to the buffer. Same story as before, except this time we also offset the first instance of data | |
// for this attribute by 3 * 4 bytes since we want to skip over the position data to get to the color data. | |
var vColLoc = gl.getAttribLocation( program, "vColor" ); | |
gl.vertexAttribPointer( vColLoc, 3, gl.FLOAT, false, 6 * 4, 3 * 4 ); | |
gl.enableVertexAttribArray( vColLoc ); | |
// Setup uniforms | |
var uResLoc = gl.getUniformLocation( program, "uResolution" ); | |
uProjLoc = gl.getUniformLocation( program, "uProjection" ); | |
uTimeLoc = gl.getUniformLocation( program, "uTime" ); | |
var aspect = canvas.width/canvas.height; | |
// Create orthographic projection matrix that is normalized based on the aspect ratio of the canvas so that it isn't warped | |
// depending on the aspect ratio of the canvas. | |
var proj = ortho(-1.5 * aspect, 1.5 * aspect, -1.5, 1.5, -5, 5); | |
initialTime = (new Date()).getTime() / 1000; | |
gl.uniformMatrix4fv(uProjLoc, false, flatten(proj)); | |
gl.uniform1f(uTimeLoc, 0.0); | |
gl.uniform2f(uResLoc, canvas.width, canvas.height); | |
render(); | |
}; | |
// Packs arrays of verticies and colors together, assuming vert[3n+0..2] is associated with col[3n+0..2] (meaning these arrays | |
// must already be flattened into 1d arrays) | |
function packData(verts, cols) { | |
var length = verts.length + cols.length; | |
var b = new Float32Array(length); | |
for (var i = 0; i<verts.length/3; i++) { | |
b[i*6] = verts[i*3]; | |
b[i*6 + 1] = verts[i*3 + 1]; | |
b[i*6 + 2] = verts[i*3 + 2]; | |
b[i*6 + 3] = cols[i*3]; | |
b[i*6 + 4] = cols[i*3 + 1]; | |
b[i*6 + 5] = cols[i*3 + 2]; | |
} | |
return b; | |
} | |
function printColor(c) { | |
console.log(`Color r: ${c[0]} g: ${c[1]} b: ${c[2]}`); | |
} | |
// converts from clip coordinates to an rgb color (0. to 1.) then multiplies that color with a given input color. Useful to assign varying | |
// colors that all relate to a base color. | |
function clipToColor(v, col) { | |
var conv = add(scale(0.5, v), vec3(0.5, 0.5, 0.5)); | |
return vec3(conv[0]*col[0], conv[1]*col[1], conv[2]*col[2]); | |
} | |
function triangle( a, b, c ) | |
{ | |
points.push( a, b, c ); | |
} | |
function divideTriangle( a, b, c, count, col ) | |
{ | |
// check for end of recursion | |
if ( count === 0 ) { | |
triangle( a, b, c ); | |
// HW470 | |
var colA = clipToColor(a, col); | |
var colB = clipToColor(b, col); | |
var colC = clipToColor(c, col); | |
colors.push( colA, colB, colC ); | |
if (DEBUG_LOG) { | |
console.log("New triangle, vertex colors:"); | |
printColor(colA); | |
printColor(colB); | |
printColor(colC); | |
} | |
} | |
else { | |
//bisect the sides | |
var ab = mix( a, b, 0.5 ); | |
var ac = mix( a, c, 0.5 ); | |
var bc = mix( b, c, 0.5 ); | |
--count; | |
// three new triangles | |
divideTriangle( a, ab, ac, count, col ); | |
divideTriangle( c, ac, bc, count, col ); | |
divideTriangle( b, bc, ab, count, col ); | |
} | |
} | |
function render() | |
{ | |
// Find the elapsed time and send that to the uTime uniform | |
var curTime = (new Date()).getTime() / 1000; | |
var elapsed = curTime - initialTime; | |
gl.uniform1f(uTimeLoc, elapsed); | |
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT ); | |
gl.drawArrays( gl.TRIANGLES, 0, points.length ); | |
window.requestAnimationFrame(render); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment