A Pen by Joseph Frias on CodePen.
Created
January 31, 2017 05:16
-
-
Save joseph45666/bc1e16bd28c478da3415107c37b080e1 to your computer and use it in GitHub Desktop.
Gravity (three.js / instancing / glsl)
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
<!-- | |
A little experiment to see how instanced geometries work and how they perform compared to regular geometries. | |
Yep. There is no 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
// <<<<<---------------- RECOMMENDED EDITOR WIDTH ---------------->>>>> | |
// some constants | |
const ARROW_FORWARD = new THREE.Vector3(0, 0, 1); | |
const UP = new THREE.Vector3(0, 1, 0); | |
// v3 is used as temporary vector where needed | |
const v3 = new THREE.Vector3(); | |
/** | |
* Initializes the demo. | |
*/ | |
function init(scene) { | |
// helpers | |
const numInstances = 4000; | |
// setup instance-attribute buffers | |
const iOffsets = new Float32Array(numInstances * 3); | |
const iRotations = new Float32Array(numInstances * 4); | |
const iColors = new Float32Array(numInstances * 4); | |
// setup geometry with instance-attributes | |
const geometry = new THREE.InstancedBufferGeometry(); | |
geometry.copy(getArrowGeometry()); | |
geometry.addAttribute('iOffset', | |
new THREE.InstancedBufferAttribute(iOffsets, 3, 1)); | |
geometry.addAttribute('iRotation', | |
new THREE.InstancedBufferAttribute(iRotations, 4, 1)); | |
geometry.addAttribute('iColor', | |
new THREE.InstancedBufferAttribute(iColors, 4, 1)); | |
geometry.attributes.iRotation.setDynamic(true); | |
geometry.attributes.iOffset.setDynamic(true); | |
// attach entities | |
const arrows = []; | |
for (let i = 0; i < numInstances; i++) { | |
arrows.push(new Arrow(i, { | |
position: iOffsets, | |
rotation: iRotations, | |
color: iColors | |
})); | |
} | |
const mesh = new THREE.Mesh(geometry, material); | |
mesh.frustumCulled = false; | |
scene.add(mesh); | |
// return the renderloop-function | |
let t0 = performance.now(); | |
return t => { | |
// limit the maximum timestep per renderframe (otherwise arrows | |
// will dissappear if you switch away from the tab for a while) | |
const dt = Math.min((t - t0) / 1000, 0.1); | |
for (let i=0; i < numInstances; i++) { | |
arrows[i].update(dt); | |
} | |
geometry.attributes.iRotation.needsUpdate = true; | |
geometry.attributes.iOffset.needsUpdate = true; | |
t0 = t; | |
}; | |
} | |
/** | |
* The Arrow-class implements the logic for simulation and | |
* buffer-updates for the arrow-instances. | |
*/ | |
class Arrow { | |
constructor(index, buffers) { | |
this.index = index; | |
this.buffers = buffers; | |
this.offsets = { | |
position: index * 3, | |
rotation: index * 4, | |
color: index * 4 | |
} | |
this.rotation = new THREE.Quaternion(); | |
this.position = new THREE.Vector3(); | |
this.velocity = new THREE.Vector3(); | |
this.color = new THREE.Color(); | |
this.init(); | |
this.update(); | |
} | |
/** | |
* Initialize arrow with randomized color, position and velocity. | |
* Velocity is computed to always™ be below escape-velocity | |
* (based on entirely unscientific number tweaking). | |
*/ | |
init() { | |
this.color.setHSL(rnd(0.5, 0.7), 0.3, rnd(0.4, 0.6)); | |
this.color.toArray(this.buffers.color, this.offsets.color); | |
this.position.setFromSpherical({ | |
radius: rnd(10, 300, 1.6), | |
phi: Math.PI/2 + rnd(-0.1, 0.1), | |
theta: rnd(2 * Math.PI) | |
}); | |
v3.set(rnd(5), rnd(4), rnd(3)); | |
this.velocity.copy(this.position) | |
.cross(UP) | |
// prevent too many arrows from reaching escape-velocity | |
.multiplyScalar(9/this.position.length()) | |
.add(v3); | |
} | |
/** | |
* Update the velocity, position and orientation for a | |
* given timestep. | |
* | |
* NOTE: extremely hot code (4000 arrows: ~240k calls/second), | |
* avoid allocations and preventable calculations. | |
*/ | |
update(dt) { | |
// update velocity from 'gravity' towards origin | |
v3.copy(this.position) | |
.multiplyScalar(-3 / this.position.lengthSq()); | |
this.velocity.add(v3); | |
// update position from velocity | |
v3.copy(this.velocity).multiplyScalar(dt); | |
this.position.add(v3); | |
// update rotation from direction of velocity | |
v3.copy(this.velocity).normalize(); | |
this.rotation.setFromUnitVectors(ARROW_FORWARD, v3); | |
// write to buffers | |
this.position.toArray(this.buffers.position, this.offsets.position); | |
this.rotation.toArray(this.buffers.rotation, this.offsets.rotation); | |
} | |
} | |
/** | |
* Creates the arrow-geometry. | |
* @return {THREE.BufferGeometry} | |
*/ | |
function getArrowGeometry() { | |
const shape = new THREE.Shape([ | |
[-0.8, -1], [-0.03, 1], [-0.01, 1.017], [0.0, 1.0185], | |
[0.01, 1.017], [0.03, 1], [0.8, -1], [0, -0.5] | |
].map(p => new THREE.Vector2(...p))); | |
const geom = new THREE.ExtrudeGeometry(shape, { | |
amount: 0.3, bevelSize: 0.1, bevelThickness: 0.1, bevelSegments: 5 | |
}); | |
const mat = new THREE.Matrix4() | |
.makeRotationX(Math.PI / 2) | |
.setPosition(new THREE.Vector3(0, 0.15, 0)); | |
geom.applyMatrix(mat); | |
return new THREE.BufferGeometry().fromGeometry(geom); | |
} | |
/** | |
* The material required to render the instanced geometry. | |
*/ | |
const material = new THREE.RawShaderMaterial({ | |
uniforms: {}, | |
vertexShader: ` | |
precision highp float; | |
// uniforms (all provided by default by three.js) | |
uniform mat4 modelViewMatrix; | |
uniform mat4 projectionMatrix; | |
uniform mat3 normalMatrix; | |
// default attributes | |
attribute vec3 position; | |
attribute vec3 normal; | |
// instance attributes | |
attribute vec3 iOffset; | |
attribute vec4 iRotation; | |
attribute vec4 iColor; | |
// shading-parameters | |
varying vec3 vLighting; | |
varying vec4 vColor; | |
vec3 rotate(const vec3 v, const vec4 q) { | |
// apply rotation (https://goo.gl/Cq3FU0) | |
vec3 t = 2.0 * cross(q.xyz, v); | |
return v + q.w * t + cross(q.xyz, t); | |
} | |
void main() { | |
// compute lighting (https://goo.gl/oS2vIY) | |
vec3 ambientColor = vec3(1.0) * 0.4; | |
vec3 directionalColor = vec3(1.0) * 0.8; | |
vec3 lightDirection = vec3(0.0, 0.0, 1.0); | |
// diffuse-shading | |
vec3 n = rotate(normalMatrix * normal, iRotation); | |
vLighting = ambientColor + | |
(directionalColor * max(dot(n, lightDirection), 0.0)); | |
vColor = iColor; | |
// instance-transform, mesh-transform and projection | |
gl_Position = projectionMatrix * modelViewMatrix * | |
vec4(iOffset + rotate(position, iRotation), 1.0); | |
} | |
`, | |
fragmentShader: ` | |
precision highp float; | |
varying vec3 vLighting; | |
varying vec4 vColor; | |
void main() { | |
gl_FragColor = vColor * vec4(vLighting, 1.0); | |
} | |
`, | |
side: THREE.DoubleSide, | |
transparent: false | |
}); | |
/** | |
* Random numbers, with range and optional bias. | |
*/ | |
function rnd(min = 1, max = 0, pow = 1) { | |
if (arguments.length < 2) { | |
max = min; | |
min = 0; | |
} | |
const rnd = (pow === 1) ? | |
Math.random() : | |
Math.pow(Math.random(), pow); | |
return (max - min) * rnd + min; | |
} | |
// ---- bootstrapping-code | |
const width = window.innerWidth; | |
const height = window.innerHeight; | |
// .... renderer | |
const renderer = new THREE.WebGLRenderer({ | |
alpha: true, antialias: true | |
}); | |
renderer.setSize(width, height); | |
// .... scene | |
const scene = new THREE.Scene(); | |
// .... camera and controls | |
const camera = new THREE.PerspectiveCamera( | |
60, width / height, 0.1, 5000); | |
const controls = new THREE.OrbitControls(camera); | |
camera.position.set(-80, 50, 20); | |
camera.lookAt(new THREE.Vector3(0, 0, 0)); | |
// .... run demo-code | |
const update = init(scene, camera); | |
requestAnimationFrame(function loop(time) { | |
controls.update(); | |
if (update) { update(performance.now()); } | |
renderer.render(scene, camera); | |
requestAnimationFrame(loop); | |
}); | |
// .... bind events | |
window.addEventListener('resize', ev => { | |
const width = window.innerWidth; | |
const height = window.innerHeight; | |
renderer.setSize(width, height); | |
camera.aspect = width / height; | |
camera.updateProjectionMatrix(); | |
}); | |
document.body.appendChild(renderer.domElement); |
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
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/build/three.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script> | |
<script src="https://cdn.rawgit.com/wearekuva/oui/master/dist/oui.js"></script> |
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
body { margin: 0; overflow: hidden; background-color: black; } | |
canvas { width: 100vw; height: 100vh; } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment