Published
Edited
Jun 25, 2020
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
{
replay;
const [[innerRadius, radiusTube]] = values;

const uniforms = {
u_texture: {
type: 't',
value: dataTexture
},
u_size: {
type: 'v2',
value: new THREE.Vector2(width, height)
}
};

// create two targets to render to (aka render to texture)
let target1 = new THREE.WebGLRenderTarget(width, height, targetOptions);
let target2 = new THREE.WebGLRenderTarget(width, height, targetOptions);

// will render game of life to a plane
const shaderGeometry = new THREE.PlaneBufferGeometry(20, 20);

// we need two scenes. One where we render the texture
// and one where we render the output to screen
const shaderScene = new THREE.Scene();
const screenScene = new THREE.Scene();
screenScene.background = new THREE.Color(0xffffff);
screenScene.add(plane);
screenScene.add(spotLight);

// add camera controls
const controls = new THREE.OrbitControls(camera, renderer.domElement);
invalidation.then(() => (controls.dispose(), renderer.dispose()));
controls.addEventListener("change", () =>
renderer.render(screenScene, camera)
);

// we also want to render the texture with its own camera
// (not sure if this is strictly necessary)
const d = 10;
const shaderCamera = new THREE.OrthographicCamera(-d, d, d, -d, 0, 1);

// create shader mesh and add to shader scene
const shaderMaterial = new THREE.ShaderMaterial({
uniforms,
vertexShader,
fragmentShader
});
const shaderMesh = new THREE.Mesh(shaderGeometry, shaderMaterial);
shaderScene.add(shaderMesh);

// create torus
const torusGeometry = new THREE.TorusKnotBufferGeometry(
innerRadius,
radiusTube,
100,
16
);
torusGeometry.rotateX(Math.PI / 1.2);
const torusMaterial = new THREE.MeshBasicMaterial();
const torusMesh = new THREE.Mesh(torusGeometry, torusMaterial);
torusMesh.position.y = 3;
torusMesh.castShadow = true;
screenScene.add(torusMesh);

while (true) {
await Promises.delay(10);

// render shader to first target and set texture value
renderer.render(shaderScene, shaderCamera, target1);
uniforms.u_texture.value = target1.texture;

// texture from target1 as input to target2
renderer.render(shaderScene, shaderCamera, target2);
// output of target2 -> map of torus material
torusMaterial.map = target2.texture;
torusMaterial.needsUpdate = true;

// render to screen
renderer.render(screenScene, camera);

// swap targets
[target1, target2] = [target2, target1];

yield renderer.domElement;
}
}
Insert cell
dataTexture = {
const texture = new THREE.DataTexture(
seed,
width,
height,
THREE.RGBAFormat,
THREE.FloatType
);
texture.needsUpdate = true;
return texture;
}
Insert cell
fragmentShader = `
precision mediump float;
uniform sampler2D u_texture;
uniform vec2 u_size;

void main() {
vec2 position = gl_FragCoord.xy;
int current = int(texture2D(u_texture, position / u_size).x);

int n = -current;
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
n += int(texture2D(u_texture, (position + vec2(dx,dy)) / u_size).x);
}
}
bool c = current == 1 ? 2 <= n && n <= 3 : n == 3;
gl_FragColor = vec4(c, c, c, 1.0);
}
`
Insert cell
Insert cell
seed = {
const data = new Float32Array(4 * width * height);

for (let i = 0; i < 4 * width * height; i += 4) {
const v = Math.random() >= 0.5 ? 1 : 0;
data[i + 0] = v;
data[i + 1] = v;
data[i + 2] = v;
data[i + 3] = 1;
}

return data;
}
Insert cell
renderer = {
const renderer = new THREE.WebGLRenderer({
antialias: true,
transparent: true
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.setSize(width, height);

if (!renderer.extensions.get('OES_texture_float'))
throw "oes texture float not supported";
if (!renderer.extensions.get('OES_texture_half_float'))
throw "oes texture half float not supported";

renderer.setPixelRatio(devicePixelRatio);
return renderer;
}
Insert cell
spotLight = {
const light = new THREE.SpotLight(0xffffff);
light.position.set(0, 20, 15);
light.castShadow = true;
light.shadow.mapSize.width = 1028;
light.shadow.mapSize.height = 1028;
light.shadow.radius = 5;

return light;
}
Insert cell
plane = {
const geometry = new THREE.PlaneBufferGeometry(1000, 1000, 10, 10);
geometry.rotateX(Math.PI / 2);

const material = new THREE.ShadowMaterial({
opacity: 0.5,
color: 0xa3a2a0,
side: THREE.DoubleSide
});

const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = -20;
mesh.receiveShadow = true;

return mesh;
}
Insert cell
camera = {
const fov = 75;
const aspect = width / height;
const near = 0.001;
const far = 10000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 5, 30);
return camera;
}
Insert cell
THREE = {
const THREE = (window.THREE = await require("three@0.99.0/build/three.min.js"));
await require("three@0.99.0/examples/js/controls/OrbitControls.js").catch(
() => {}
);
return THREE;
}
Insert cell
import { slider } from '@bartok32/diy-inputs'
Insert cell
import { checkbox } from '@jashkenas/inputs'
Insert cell
import { inputsGroup } from '@bumbeishvili/input-groups'
Insert cell
height = width / 1.5
Insert cell
textureDim = 512
Insert cell
sliderConfig = {
return {
theme: 'default-round',
background: {
type: 'double',
colors: ['#7295FF', '#efefef']
}
};
}
Insert cell
targetOptions = {
return {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat
};
}
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