Published
Edited
Jul 19, 2019
Fork of GPGPU Ropes
Insert cell
Insert cell
Insert cell
funPart = `
vec4 sample = texture2D(positions, vec2(vUv.x, 1.0 - vUv.y));
vec3 pos = sample.xyz;
float lifetime = sample.a;

// increase lifetime by delta, cap it at 1 and start over
lifetime = mod(lifetime + delta, 1.0);

float x = floor(vUv.x * ${textureSize}.0);
float y = floor((1.0-vUv.y) * ${textureSize}.0);
float index = x + y * ${textureSize}.0;
float speed = 6.0;

if (index == 0.0) {
pos.x = cos(elapsedTime * speed) * 30.0;
// pos.y = sin(elapsedTime * speed) * 40.0;
pos.y = sin(elapsedTime * speed * 10.0) * 30.0;
pos.z = sin(elapsedTime * speed) * 30.0;
// pos.z = 0.0;
} else {
vec2 neighborUv = vec2(
mod(index-1.0, ${textureSize}.0)/${textureSize}.0,
floor((index-1.0)/${textureSize}.0)/${textureSize}.0
);
vec4 neighbor = texture2D(positions, neighborUv);
pos.xyz = mix(sample.xyz, neighbor.xyz, 0.98);
}

// temporary
lifetime = index;

gl_FragColor = vec4(pos.rgb, lifetime);`
Insert cell
Insert cell
Insert cell
textureSize = 64
Insert cell
Insert cell
class GPGPU {
constructor(renderer, textureSize) {
const gl = renderer.getContext()
// we need FLOAT Textures to store positions
//https://github.com/KhronosGroup/WebGL/blob/master/sdk/tests/conformance/extensions/oes-texture-float.html
if (!gl.getExtension('OES_texture_float')) {
alert('client support error - float textures not supported')
throw new Error('float textures not supported')
}
// we need to access textures from within the vertex shader
//https://github.com/KhronosGroup/WebGL/blob/90ceaac0c4546b1aad634a6a5c4d2dfae9f4d124/conformance-suites/1.0.0/extra/webgl-info.html
if (gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) === 0) {
alert('client support error - vertex shader cannot read textures')
throw new Error('vertex shader cannot read textures')
}
this.renderer = renderer
// GPGPU buffer textures
// A
this.positionsA = new THREE.DataTexture(
preloadedDataBuffer(textureSize),
textureSize, textureSize,
THREE.RGBAFormat, THREE.FloatType)
// B
this.positionsB = new THREE.DataTexture(
null, // will be copied over from A
// preloadedDataBuffer(textureSize),
textureSize, textureSize,
THREE.RGBAFormat, THREE.FloatType)
this.positionsA.needsUpdate = true

// rtt setup
this.scene = new THREE.Scene()
this.orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)

// create a target texture
const options = {
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping,
stencilBuffer: false,
minFilter: THREE.NearestFilter, // important as we want to sample square pixels
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
type: (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) ? THREE.HalfFloatType : THREE.FloatType,
}

this.rtts = [
new THREE.WebGLRenderTarget(textureSize, textureSize, options),
new THREE.WebGLRenderTarget(textureSize, textureSize, options)
]
this.rttIndex = 0

// the simulation:
//create a bi-unit quadrilateral and uses the simulation material to update the Float Texture
let geom = new THREE.BufferGeometry()
geom.addAttribute('position', new THREE.BufferAttribute(new Float32Array([
-1, -1, 0,
1, -1, 0,
1, 1, 0,
-1, -1, 0,
1, 1, 0,
-1, 1, 0
]), 3))
geom.addAttribute('uv', new THREE.BufferAttribute(new Float32Array([0,1, 1,1, 1,0, 0,1, 1,0, 0,0 ]), 2))

this.simulationMaterial = new THREE.ShaderMaterial({
uniforms: {
positions: {type: 't', value: this.positionsA },
elapsedTime: {type: 'f', value: 0},
delta: {type: 'f', value: 0},
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv; // vec2(uv.x, 1.0-uv.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`,
fragmentShader: `

#define PI 3.1415926536
#define PI2 6.28318530718

uniform float elapsedTime;
uniform float delta;
uniform sampler2D positions; // Data Texture containing original positions
varying vec2 vUv;

vec3 lerp (vec3 valueA, vec3 valueB, float ratio) {
return (valueA * ratio) + (valueB * (1.0 - ratio));
}
void main() {
${funPart}
}
`
})
this.mesh = new THREE.Mesh(geom, this.simulationMaterial)
this.scene.add(this.mesh)
}
simulate (delta, elapsedTime) {
this.rttIndex = (this.rttIndex + 1) % 2
this.simulationMaterial.uniforms.elapsedTime.value = elapsedTime
this.simulationMaterial.uniforms.delta.value = delta
this.renderer.setRenderTarget(this.rtts[this.rttIndex])
this.renderer.clear()
this.renderer.render(this.scene, this.orthoCamera)
this.renderer.setRenderTarget(null)
const texture = this.rtts[this.rttIndex].texture
this.simulationMaterial.uniforms.positions.value = texture
return texture
}
}
Insert cell
Insert cell
Insert cell
function createParticles(textureSize) {
const radius = 50
// create an instanced buffer from a plane buffer geometry
const geometry = new THREE.InstancedBufferGeometry()
geometry.copy(new THREE.PlaneBufferGeometry(1, 1, 1, 1))
// Add/generate attributes buffers for the geometry
const offsets = []
const scales = []
const indexes = []
const lifetimes = []
const spacing = 2
const halfOffset = textureSize * spacing / 2
for (let i = 0; i < numberOfParticles; i += 1) {
offsets.push(
0, 0, 0
)
scales.push(1)
indexes.push(i + 1)
lifetimes.push(0, 0.5 + Math.random() / 2)
}
geometry.addAttribute('offset', new THREE.InstancedBufferAttribute(new Float32Array(offsets), 3))
geometry.addAttribute('scale', new THREE.InstancedBufferAttribute(new Float32Array(scales), 1))
geometry.addAttribute('particleIndex', new THREE.InstancedBufferAttribute(new Float32Array(indexes), 1))
geometry.addAttribute('lifetime', new THREE.InstancedBufferAttribute(new Float32Array(lifetimes), 2))
const material = createRenderMaterial(textureSize)

const mesh = new THREE.Mesh(geometry, material)
return {geometry, material, mesh}
}
Insert cell
function createRenderMaterial (textureSize) {
const textureLoader = new THREE.TextureLoader()
const material = new THREE.RawShaderMaterial( {
uniforms: {
time: { value: 1.0 },
orientation: {type: 'v4', value: new THREE.Quaternion()},
gpgpuOffsets: {type: 't', value: null },
mapper: textureLoader,
// map: new THREE.TextureLoader().load('https://i.imgur.com/h1v0P5s.jpg'),
colorBase: new THREE.Uniform(new THREE.Color(0xd9ff0e)),
colorStart: new THREE.Uniform(new THREE.Color(0x8674fd)),
colorPeak: new THREE.Uniform(new THREE.Color(0x0ed9ff)),
colorEnd: new THREE.Uniform(new THREE.Color(0x333333)),
},
// transparent: true,
// alpha: true,
side: THREE.DoubleSide,
// alphaTest: 0.1,
vertexShader: `
precision highp float;

#define PI 3.1415926536
#define PI2 6.28318530718

attribute vec3 position;
attribute vec3 offset;
attribute float scale;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;

uniform float time;

attribute vec2 uv;
varying vec2 vUv;
attribute float particleIndex;

uniform sampler2D gpgpuOffsets;
varying float vParticleIndex;

uniform vec4 orientation;
uniform sampler2D mapper;
varying vec4 vColor;

vec3 applyQuaternionToVector( vec4 q, vec3 v ){
return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );
}

void main(){
vec4 positions = texture2D(gpgpuOffsets, vec2(
mod(particleIndex-1.0, ${textureSize}.0)/${textureSize}.0,
floor((particleIndex-1.0)/${textureSize}.0)/${textureSize}.0)
);
float lifetime = positions.a;
float i = particleIndex - 1.0;
vec4 sample = texture2D(mapper, vec2(
mod(i, ${textureSize}.0) / ${textureSize}.0,
1.0 - i / ${textureSize}.0 / ${textureSize}.0
));
vec3 pos = position * vec3(scale, scale, 1.0);
// vec3 pos = position * vec3(scale * lifetime * 2.0, scale * lifetime * 2.0, 1.0);
pos = applyQuaternionToVector(orientation, pos);
pos = pos + offset + positions.xyz;

vParticleIndex = particleIndex;
vUv = vec2(uv.x, 1.0-uv.y);

vColor.xyz = sample.xyz;
vColor.a = lifetime;
// vColor.a = sample.a;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0 );
}
`,
fragmentShader: `
precision highp float;

varying vec4 vColor;
varying vec2 vUv;
varying float vParticleIndex;
varying vec2 vLifetime;

uniform float time;

uniform vec3 colorBase;
uniform vec3 colorStart;
uniform vec3 colorPeak;
uniform vec3 colorEnd;
uniform sampler2D mapper;

#define PI 3.1415926536
#define PI2 6.28318530718


float drawBase (in vec2 uv, in float min, in float max) {
float dist = sqrt(dot(uv, uv));
if (dist >= max || dist <= min) {
return 0.0;
}
float sm = smoothstep(max, max - 0.01, dist);
float sm2 = smoothstep(min, min - 0.01, dist);
float alpha = sm * sm2;
return (1.0-alpha);
}

void main() {
float alpha = drawBase(vUv - vec2(0.5), 0.0, 0.5);
// discard if not needed
if (alpha == 0.0) {
discard;
}
vec4 color = vColor;
float r = vColor.a / ${numberOfParticles}.0;
color.rgba = vec4(r, 0.0, 1.0-r, 0.8);
gl_FragColor = color;
}
`,
// side: THREE.DoubleSide,
side: THREE.FrontSide, // only front since the plane will always face the camera
transparent: true,
alphaTest: 0.1,
blending: THREE.NormalBlending
//blending: THREE.MultiplyBlending
// blending: THREE.AdditiveBlending
})
textureLoader.load(imageURI, (texture) => {
material.uniforms.mapper.value = texture
})
return material
}
Insert cell
Insert cell
Insert cell
Insert cell
THREE = {
const THREE = window.THREE = await require('three@0.105/build/three.min.js')
await require('three@0.105/examples/js/controls/OrbitControls.js').catch(() => {})
return THREE
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more