-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Lesson 52 : Create Particle Systems with TransformFeedback
- Loading branch information
1 parent
6a7bee5
commit cb176d1
Showing
37 changed files
with
5,559 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Fun with WebGL 2.0 - 052 - Create Particle Systems with TransformFeedback | ||
**Description**: | ||
Going to start a sub series related to doing special effects in webgl. To create effects we need to first build a particle system that can handle all the data and interpolate between different states. We're going to explore a new feature to WebGL 2.0 called TransfromFeedback that allows us to have our vertex shader calculate our current state BUT save it so a gpu buffer so as programmers we do not need to have the cpu handle the animations, have it all completely handled in the gpu. | ||
|
||
### Links of Interest | ||
|
||
|
||
### Links | ||
* [Lesson on Youtube](https://youtu.be/PWjIeJDE7Rc) | ||
* [Youtube Series PlayList](https://www.youtube.com/playlist?list=PLMinhigDWz6emRKVkVIEAaePW7vtIkaIF) | ||
* [Fungi Git Change 1](https://github.com/sketchpunk/FunWithWebGL2/commit/6a7bee59682b4c290850ac859f8db51f70caae82) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import gl from "./gl.js"; | ||
import CameraOrbit from "./cameras/Orbit.js"; | ||
import GridFloor from "./primitives/GridFloor.js"; | ||
import Renderer from "./util/Renderer.js"; | ||
import RenderLoop from "./util/RenderLoop.js"; | ||
import VDebug from "./entities/VisualDebugger.js"; | ||
import {KBMCtrl, KBMCtrl_Viewport} from "./util/KBMCtrl.js" | ||
|
||
export default{ | ||
render :Renderer, //Main Render Function | ||
renderLoop :null, //Render loop | ||
lblFPS :null, //Html Element reference to a tag to update text of FPS | ||
|
||
mainCamera :null, //Main camera for the scene | ||
ctrlCamera :null, //Keyboard and Mouse controls for the camera | ||
|
||
debugLine :null, //Renderable used to help debug data or models | ||
debugPoint :null, //Same but with points. | ||
|
||
gridFloor :null, //Just a reference to the renderable for the grid floor. | ||
scene :[], //Array that holds the heirarchy of transforms / renderables. | ||
|
||
deltaTime :0, | ||
sinceStart :0, | ||
|
||
//Begin the GL Context | ||
init:function(){ gl.set("FungiCanvas"); return this; }, | ||
|
||
//Build all the main objects needed to get a scene up and running | ||
ready:function(renderHandler,opt){ | ||
this.mainCamera = new CameraOrbit().setPosition(0,0.5,4).setEulerDegrees(-15,10,0); | ||
this.ctrlCamera = new KBMCtrl().addHandler("camera",new KBMCtrl_Viewport(this.mainCamera),true,true); | ||
|
||
this.renderLoop = new RenderLoop(renderHandler); | ||
this.lblFPS = document.getElementById("lblFPS"); | ||
setInterval(function(){ this.lblFPS.innerHTML = this.renderLoop.fps; }.bind(this),200); | ||
|
||
this.gridFloor = GridFloor(); | ||
this.scene.push(this.gridFloor); | ||
|
||
//Setup Features | ||
if(opt){ | ||
if(opt & 1 == 1) this.scene.push( this.debugLine = new VDebug() ); //DEBUG LINE RENDERER | ||
if(opt & 2 == 2) this.scene.push( this.debugPoint = new VDebug().drawPoints() ); //DEBUG POINT RENDERER | ||
} | ||
}, | ||
|
||
//Get a frame ready to be rendered. | ||
update:function(){ | ||
this.mainCamera.update(); | ||
gl.clear(); | ||
return this; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import Vec3 from "./maths/Vec3.js"; | ||
import Mat3 from "./maths/Mat3.js"; | ||
import Mat4 from "./maths/Mat4.js"; | ||
import Quat from "./maths/Quat.js"; | ||
import DualQuat from "./maths/DualQuat.js"; | ||
|
||
const DEG2RAD = Math.PI/180; | ||
const RAD2DEG = 180/Math.PI; | ||
|
||
//Normalize x value to x range, then normalize to lerp the z range. | ||
function map(x, xMin,xMax, zMin,zMax){ return (x - xMin) / (xMax - xMin) * (zMax-zMin) + zMin; } | ||
|
||
function clamp(v,min,max){ return Math.max(min,Math.min(max,v)); } | ||
|
||
function smoothStep(edge1, edge2, val){ //https://en.wikipedia.org/wiki/Smoothstep | ||
var x = Math.max(0, Math.min(1, (val-edge1)/(edge2-edge1))); | ||
return x*x*(3-2*x); | ||
} | ||
|
||
//Get a number between A and B from a normalized number. | ||
function lerp(a,b,t){ return a + t * (b-a); } | ||
|
||
//From a point in space, closest spot to a 2D line | ||
function closestPointToLine2D(x0,y0,x1,y1,px,py){ | ||
var dx = x1 - x0, | ||
dy = y1 - y0, | ||
t = ((px-x0)*dx + (py-y0)*dy) / (dx*dx+dy*dy), | ||
x = x0 + (dx * t), //Util.lerp(x0, x1, t), | ||
y = y0 + (dy * t); //Util.lerp(y0, y1, t); | ||
return [x,y] | ||
} | ||
|
||
//From a point in space, closest spot to a 3D line | ||
function closestPointToLine3D(a,b,p,out){ | ||
if(out == undefined) out = new Vec3(); | ||
var dx = b.x - a.x, | ||
dy = b.y - a.y, | ||
dz = a.z - a.z, | ||
t = ((p.x-a.x)*dx + (p.y-a.y)*dy + (p.z-a.z)*dz) / (dx*dx+dy*dy+dz*dz), | ||
x = a.x + (dx * t), | ||
y = a.y + (dy * t), | ||
z = a.z + (dz * t); | ||
return out.set(x,y,z); | ||
} | ||
|
||
//Return back the two points that are closes on two infinite lines | ||
//http://geomalgorithms.com/a07-_distance.html | ||
function closestpoint_2Lines(A0,A1,B0,B1){ | ||
var u = A1.clone().sub(A0), | ||
v = B1.clone().sub(B0), | ||
w = A0.clone().sub(B0), | ||
a = Vec3.dot(u,u), // always >= 0 | ||
b = Vec3.dot(u,v), | ||
c = Vec3.dot(v,v), // always >= 0 | ||
d = Vec3.dot(u,w), | ||
e = Vec3.dot(v,w), | ||
D = a*c - b*b, // always >= 0 | ||
tU, tV; | ||
//compute the line parameters of the two closest points | ||
if(D < 0.000001){ // the lines are almost parallel | ||
tU = 0.0; | ||
tV = (b>c ? d/b : e/c); // use the largest denominator | ||
}else{ | ||
tU = (b*e - c*d) / D; | ||
tV = (a*e - b*d) / D; | ||
} | ||
|
||
//Calc Length | ||
//Vector vLen = w + (uT * u) - (vT * v); // = L1(sc) - L2(tc) | ||
//Float len = sqrt( dot(vLen,vLen) ); | ||
|
||
return [ u.scale(tU).add(A0), v.scale(tV).add(B0) ]; | ||
} | ||
|
||
//Return back the two points that are the closests but bound by the limit of two segments | ||
//http://geomalgorithms.com/a07-_distance.html | ||
function closestPointS_2Segments(A0,A1,B0,B1){ | ||
var u = A1.clone().sub(A0), | ||
v = B1.clone().sub(B0), | ||
w = A0.clone().sub(B0), | ||
a = Vec3.dot(u,u), // always >= 0 | ||
b = Vec3.dot(u,v), | ||
c = Vec3.dot(v,v), // always >= 0 | ||
d = Vec3.dot(u,w), | ||
e = Vec3.dot(v,w), | ||
D = a*c - b*b, // always >= 0 | ||
sc, sN, sD = D, // sc = sN / sD, default sD = D >= 0 | ||
tc, tN, tD = D; // tc = tN / tD, default tD = D >= 0 | ||
|
||
// compute the line parameters of the two closest points | ||
if(D < 0.000001){ // the lines are almost parallel | ||
sN = 0.0; // force using point P0 on segment S1 | ||
sD = 1.0; // to prevent possible division by 0.0 later | ||
tN = e; | ||
tD = c; | ||
}else{ // get the closest points on the infinite lines | ||
sN = (b*e - c*d); | ||
tN = (a*e - b*d); | ||
if(sN < 0.0){ // sc < 0 => the s=0 edge is visible | ||
sN = 0.0; | ||
tN = e; | ||
tD = c; | ||
}else if (sN > sD){ // sc > 1 => the s=1 edge is visible | ||
sN = sD; | ||
tN = e + b; | ||
tD = c; | ||
} | ||
} | ||
|
||
if (tN < 0.0){ // tc < 0 => the t=0 edge is visible | ||
tN = 0.0; | ||
// recompute sc for this edge | ||
if (-d < 0.0) sN = 0.0; | ||
else if (-d > a) sN = sD; | ||
else{ | ||
sN = -d; | ||
sD = a; | ||
} | ||
}else if(tN > tD){ // tc > 1 => the t=1 edge is visible | ||
tN = tD; | ||
// recompute sc for this edge | ||
if((-d + b) < 0.0) sN = 0; | ||
else if ((-d + b) > a) sN = sD; | ||
else{ | ||
sN = (-d + b); | ||
sD = a; | ||
} | ||
} | ||
|
||
// finally do the division to get sc and tc | ||
sc = (Math.abs(sN) < 0.000001 ? 0.0 : sN / sD); | ||
tc = (Math.abs(tN) < 0.000001 ? 0.0 : tN / tD); | ||
|
||
// get the difference of the two closest points | ||
//Vector dP = w + (sc * u) - (tc * v); // = S1(sc) - S2(tc) | ||
|
||
return [ u.scale(sc).add(A0), v.scale(tc).add(B0) ]; | ||
} | ||
|
||
|
||
export default { DEG2RAD:DEG2RAD, RAD2DEG:RAD2DEG, | ||
map:map, | ||
clamp:clamp, | ||
smoothStep:smoothStep, | ||
closestPointToLine2D:closestPointToLine2D, | ||
closestPointToLine3D:closestPointToLine3D, | ||
closestpoint_2Lines:closestpoint_2Lines, | ||
closestPointS_2Segments:closestPointS_2Segments, | ||
} | ||
|
||
export { Vec3, Mat3, Mat4, Quat, DualQuat, DEG2RAD, RAD2DEG } |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import gl from "../gl.js"; | ||
import Transform from "../entities/Transform.js"; | ||
import {Vec3, Mat4, Quat, DEG2RAD} from "../Maths.js"; | ||
|
||
class Orbit extends Transform{ | ||
constructor(fov,near,far){ | ||
super(); | ||
//Setup the projection and invert matrices | ||
this.ubo = gl.UBOTransform; | ||
this.projectionMatrix = new Float32Array(16); | ||
this.invertedProjectionMatrix = new Float32Array(16); | ||
this.invertedLocalMatrix = new Float32Array(16); | ||
|
||
var ratio = gl.ctx.canvas.width / gl.ctx.canvas.height; | ||
Mat4.perspective(this.projectionMatrix, fov || 45, ratio, near || 0.1, far || 100.0); | ||
Mat4.invert(this.invertedProjectionMatrix, this.projectionMatrix); //Save Inverted version for Ray Casting. | ||
|
||
this.ubo.update("matProjection",this.projectionMatrix); //Initialize The Transform UBO. | ||
|
||
//Orbit Camera will control things based on euler, its cheating but not ready for quaternions | ||
this.euler = new Vec3(); | ||
} | ||
|
||
//Override how this transfer creates the localMatrix : Call Update, not this function in render loop. | ||
updateMatrix(){ | ||
//Only Update the Matrix if its needed. | ||
//if(!this.position.isModified && !this.rotation.isModified && !this.euler.isModified) return this.localMatrix; | ||
|
||
Quat.setFromEuler(this.rotation,this.euler.x,this.euler.y,this.euler.z,"YXZ"); | ||
Mat4.fromQuaternion(this.localMatrix,this.rotation); | ||
this.localMatrix.resetTranslation().translate(this.position); | ||
|
||
//Set the modified indicator to false on all the transforms. | ||
this.position.isModified = false; | ||
this.rotation.isModified = false; | ||
this.euler.isModified = false; | ||
return this.localMatrix; | ||
} | ||
|
||
//Update the Matrices and UBO. | ||
update(){ | ||
if(this.position.isModified || this.scale.isModified || this.euler.isModified){ | ||
this.updateMatrix(); | ||
Mat4.invert(this.invertedLocalMatrix,this.localMatrix); | ||
} | ||
this.ubo.update("matCameraView",this.invertedLocalMatrix,"posCamera",this.position); | ||
} | ||
|
||
setEulerDegrees(x,y,z){ this.euler.set(x * DEG2RAD,y * DEG2RAD,z * DEG2RAD); return this; } | ||
|
||
worldToScreen(vAry){ | ||
var mat = new Float32Array(16), // Matrix4 Holder | ||
p = [0,0,0,0], // Vec4 | ||
rtn = []; // List of vec2 results | ||
|
||
//Move Points from WorldSpace to -> View Space (View Matrix) -> ClipSpace (ProjMatrix) | ||
Mat4.mult(mat,this.projectionMatrix,this.invertedLocalMatrix); | ||
|
||
for(var i=0; i < vAry.length; i++){ | ||
Mat4.transformVec3(p, vAry[i], mat); | ||
|
||
//Move from Clip Space to NDC Space (Normalized Device Coordinate Space) (-1 to 1 opengl viewport) | ||
if(p[3] != 0){ //only if W is not zero, | ||
p[0] = p[0] / p[3]; | ||
p[1] = p[1] / p[3]; | ||
} | ||
|
||
//Then finally move the points to Screen Space | ||
//Map points from -1 to 1 range into 0 to 1 range, Then multiple by canvas size | ||
rtn.push( // Replaced /2 with *0.5 | ||
( p[0] + 1) * 0.5 * gl.width, | ||
(-p[1] + 1) * 0.5 * gl.height | ||
); | ||
} | ||
|
||
if(vAry.length == 1) return rtn[0]; //Just return the one point | ||
return rtn; //Return all the points | ||
} | ||
} | ||
|
||
export default Orbit; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import Renderable from "./Renderable.js"; | ||
import DynamicBuffer from "../util/DynamicBuffer.js"; | ||
import gl, {VAO, ATTR_POSITION_LOC} from "../gl.js"; | ||
|
||
class BoundingBox extends Renderable{ | ||
constructor(vmin,vmax,matName){ | ||
super(null,matName); | ||
this.vMin = vmin; //TODO Remove | ||
this.vMax = vmax; //TODO REMOVE | ||
this.bounds = [vmin,vmax]; | ||
this.boundFlat = [vmin[0],vmin[1],vmin[2],vmax[0],vmax[1],vmax[2]]; //Save range in one array for sketchpunk's clevers fun | ||
this.drawMode = gl.ctx.LINES; | ||
|
||
//TODO : Maybe dont bother using DynamicBuffer, just create the Vert/Index arrays and create a standard VAO. | ||
|
||
//Build VAO and Buffers for rendering. | ||
var bufByteSize = Float32Array.BYTES_PER_ELEMENT * 4 * 8; //Cube only has 8 points | ||
this.vao = VAO.create(); | ||
VAO.emptyFloatArrayBuffer(this.vao,"bVertices",bufByteSize,ATTR_POSITION_LOC,4,0,0,false) | ||
.emptyIndexBuffer(this.vao,"bIndex",48,false) | ||
.finalize(this.vao,"VisDebug"); | ||
|
||
this.vertBuffer = DynamicBuffer.newFloat(this.vao.bVertices.ptr,4,8); | ||
this.indBuffer = DynamicBuffer.newElement(this.vao.bIndex.ptr,48); | ||
|
||
this.genBox(); | ||
} | ||
|
||
genBox(){ | ||
//Being clever in how to build 8 points of the cube | ||
//Create the floor, then its the same thing but with Y Max instead of YMin. | ||
//Using loop in a creative way to switch between min and max y. | ||
var b = this.boundFlat, c = 4; | ||
|
||
for(var i=1; i < 5; i+=3){ | ||
this.vertBuffer.data.push( b[0],b[i],b[2],c, //Floor Top Left Corner | ||
b[3],b[i],b[2],c, //Floor Top Right | ||
b[3],b[i],b[5],c, //Floor Bottom Right | ||
b[0],b[i],b[5],c); //Floor Bottom left | ||
} | ||
|
||
//Build up the indexes to connect the dots. | ||
var ii,iu; | ||
for(var i=0; i < 4; i++){ | ||
ii = (i+1)%4; | ||
iu = i+4; | ||
this.indBuffer.data.push(i,ii, iu,ii+4, i,iu); //Draw bottom, Top, Then Side | ||
} | ||
|
||
this.vertBuffer.pushToGPU(); | ||
this.indBuffer.pushToGPU(); | ||
|
||
//this.vao.count = this.vertBuffer.getComponentCnt(); | ||
this.vao.count = this.indBuffer.getComponentCnt(); | ||
} | ||
} | ||
|
||
export default BoundingBox; |
Oops, something went wrong.