Last active
December 20, 2021 09:02
-
-
Save duhaime/f7341fcffc0b7bc224fccbd08c5f9629 to your computer and use it in GitHub Desktop.
Three.js Frame Buffer
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
/* | |
@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? |
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
<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