Skip to content

Instantly share code, notes, and snippets.

@duhaime
Last active December 20, 2021 09:02
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save duhaime/f7341fcffc0b7bc224fccbd08c5f9629 to your computer and use it in GitHub Desktop.
Save duhaime/f7341fcffc0b7bc224fccbd08c5f9629 to your computer and use it in GitHub Desktop.
Three.js Frame Buffer
/*
@author zz85
*/
// Utils for FBO Particles Simulations
THREE.FBOUtils = function( textureWidth, renderer, simulationShader ) {
// Init RTT stuff
gl = renderer.getContext();
if( !gl.getExtension( "OES_texture_float" )) {
alert( "No OES_texture_float support for float textures!" );
return;
}
if( gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) == 0) {
alert( "No support for vertex shader textures!" );
return;
}
var cameraRTT = new THREE.OrthographicCamera(-textureWidth/2, textureWidth/2, textureWidth/2, -textureWidth/2, -1000000, 1000000);
cameraRTT.position.z = 100;
var rtTexturePos = new THREE.WebGLRenderTarget(textureWidth, textureWidth, {
wrapS:THREE.RepeatWrapping,
wrapT:THREE.RepeatWrapping,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
type:THREE.FloatType,
stencilBuffer: false
});
// Shader Stuff
var texture_cpu_to_gpu_vertex_shader = [
"varying vec2 vUv;",
"void main() {",
"vUv = vec2(uv.x, 1.0 - uv.y);",
"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"} "
].join("\n");
var texture_cpu_to_gpu_fragment_shader = [
"varying vec2 vUv;",
"uniform sampler2D tPositions;",
"void main() {",
"vec4 pos = texture2D( tPositions, vUv );",
"gl_FragColor = pos;",
"};"
].join("\n");
var cpu_gpu_material = new THREE.ShaderMaterial({
uniforms: {
tPositions: { type: "t", value: null }
},
vertexShader: texture_cpu_to_gpu_vertex_shader,
fragmentShader: texture_cpu_to_gpu_fragment_shader
});
var sceneRTTPos = new THREE.Scene();
sceneRTTPos.add(cameraRTT);
var plane = new THREE.PlaneGeometry(textureWidth, textureWidth);
quad = new THREE.Mesh(plane, simulationShader);
quad.position.z = -5000;
sceneRTTPos.add(quad);
this.textureWidth = textureWidth;
this.sceneRTTPos = sceneRTTPos;
this.cameraRTT = cameraRTT;
this.renderer = renderer;
this.cpu_gpu_material = cpu_gpu_material;
this.simulationShader = simulationShader;
};
THREE.FBOUtils.createTextureFromData = function(width, height, data, options) {
options || (options = {});
var texture = new THREE.DataTexture(
new Float32Array(data),
width,
height,
THREE.RGBAFormat,
THREE.FloatType,
null,
THREE.RepeatWrapping,
THREE.RepeatWrapping,
THREE.NearestFilter,
THREE.NearestFilter
);
texture.needsUpdate = true;
return texture;
};
THREE.FBOUtils.prototype.renderToTexture = function(texture, renderToTexture) {
this.cpu_gpu_material.uniforms.tPositions.value = texture;
this.renderer.render(this.sceneRTTPos, this.cameraRTT, renderToTexture, false);
};
THREE.FBOUtils.prototype.pushDataToTexture = function(data, renderToTexture) {
var texture = THREE.FBOUtils.createTextureFromData( this.textureWidth, this.textureWidth, data );
this.renderToTexture(texture, renderToTexture);
};
THREE.FBOUtils.prototype.simulate = function(target) {
this.renderer.render(
this.sceneRTTPos,
this.cameraRTT,
target, false);
}
// THREE.FBOUtils.setSimulationShader();
// Perhaps this can be moved outside?
<html>
<head>
<style>
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
</style>
<meta charset='UTF-8'>
</head>
<body>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js'></script>
<script src='https://threejs.org/examples/js/controls/TrackballControls.js'></script>
<script src='FBOUtils.js'></script>
<!-- Simulation shaders: these allow i/o of state to/from shaders -->
<script id='sim-vs' type='x-shader/x-vert'>
precision mediump float;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec2 uv; // stores x,y offsets of each point in texture
attribute vec3 position;
varying vec2 vUv;
void main() {
vUv = vec2(uv.x, 1.0 - uv.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id='sim-fs' type='x-shader/x-frag'>
precision mediump float;
uniform sampler2D tPositions;
uniform sampler2D origin;
varying vec2 vUv;
void main() {
// read the supplied x,y,z vert positions
vec3 pos = texture2D(tPositions, vUv).xyz;
// manipulate the position attributes somehow
pos.x += cos(pos.y) / 100.0;
pos.y += tan(pos.x) / 100.0;
// render the new positional attributes
gl_FragColor = vec4(pos, 1.0);
}
</script>
<!-- Now come the shaders that render what users see -->
<script id='ui-vert' type='x-shader/x-vert'>
precision mediump float;
uniform sampler2D map; // contains positional data read from sim-fs
uniform float width; // width of the texture
uniform float height; // height of the texture
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec2 position;
void main() {
// read the position of this particle
vec3 color = texture2D(map, position.xy).rgb;
// project this particle
gl_Position = projectionMatrix * modelViewMatrix * vec4(color, 1.0);
// set the size of each particle
gl_PointSize = 0.3;
}
</script>
<script id='ui-frag' type='x-shader/x-frag'>
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.5, 0.5, 1.0);
}
</script>
<script>
// generate a scene object
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111);
// generate a camera
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 1000);
camera.position.set(0, 1, -10);
// generate a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio); // <3 retina
renderer.setSize(window.innerWidth, window.innerHeight); // canvas size
document.body.appendChild(renderer.domElement);
// generate controls
var controls = new THREE.TrackballControls(camera, renderer.domElement);
// generate some lights
var ambientLight = new THREE.AmbientLight(0xeeeeee);
scene.add(ambientLight);
/**
* Set up Frame Buffer I/O business
**/
// make sure the user's browser supports textures with float values
if (!renderer.context.getExtension('OES_texture_float')) {
alert('OES_texture_float is not supported :(');
};
// create the initial starting positions of objects
// to get more particles increase w+h
var w = 512,
h = 512,
data = new Float32Array(w*h*3);
for (var i=0; i<data.length; i++) {
data[i] = i % 3 == 0
? 0
: Math.random() * 10
};
var texture = new THREE.DataTexture(data, w, h, THREE.RGBFormat, THREE.FloatType);
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;
texture.needsUpdate = true;
// initialize the texture that will hold positional data
var positionTexture = new THREE.WebGLRenderTarget(w, h, {
wrapS: THREE.RepeatWrapping,
wrapT: THREE.RepeatWrapping,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBFormat,
type: THREE.FloatType,
stencilBuffer: false,
});
positionTexture2 = positionTexture.clone();
// create the shader that will run the simulation
var timer = 0;
var simulationMaterial = new THREE.RawShaderMaterial({
uniforms: {
tPositions: { type: 't', value: texture },
origin: { type: 't', value: texture },
timer: { type: 'f', value: timer },
},
vertexShader: document.querySelector('#sim-vs').textContent,
fragmentShader: document.querySelector('#sim-fs').textContent,
});
// render the data to the simulation shader
var fboParticles = new THREE.FBOUtils(w, renderer, simulationMaterial);
fboParticles.renderToTexture(positionTexture.texture, positionTexture2);
// allocate one buffer as the 'in' buffer and one as the 'out' buffer
fboParticles.in = positionTexture;
fboParticles.out = positionTexture2;
// create the uv attribute, which describes the x,y offsets in texture
// that store the positional information for a given vertex
var geometry = new THREE.Geometry();
for (var i=0, l=w*h; i<l; i++) {
var vertex = new THREE.Vector3();
vertex.x = (i % w) / w;
vertex.y = Math.floor(i/w)/h;
geometry.vertices.push(vertex);
};
// create the shader for the representation of the particles that users see
var material = new THREE.RawShaderMaterial({
uniforms: {
'map': { type: 't', value: positionTexture.texture },
'width': { type: 'f', value: w },
'height': { type: 'f', value: h },
},
vertexShader: document.querySelector('#ui-vert').textContent,
fragmentShader: document.querySelector('#ui-frag').textContent,
depthTest: false,
transparent: true,
});
var mesh = new THREE.Points(geometry, material);
scene.add(mesh);
/**
* Main
**/
function render() {
// update the time attribute in the simulation
timer += 0.01;
simulationMaterial.uniforms.timer.value = timer;
// make output of sim frag shader input to sim vert shader
var lastFrame = fboParticles.in;
fboParticles.in = fboParticles.out;
fboParticles.out = lastFrame;
// run a frame using the last frame of output from sim frag shader
simulationMaterial.uniforms.tPositions.value = fboParticles.in.texture;
fboParticles.simulate(fboParticles.out);
material.uniforms.map.value = fboParticles.out.texture;
renderer.render(scene, camera);
controls.update();
requestAnimationFrame(render);
};
render();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment