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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more