Skip to content

Instantly share code, notes, and snippets.

@wonglok
Last active February 11, 2019 20:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wonglok/66760f51d56bb1acf833dcbb4f987e78 to your computer and use it in GitHub Desktop.
Save wonglok/66760f51d56bb1acf833dcbb4f987e78 to your computer and use it in GitHub Desktop.
Something similar to Geometry Shader
#include <common>
varying vec3 v_tt;
void main() {
gl_FragColor = vec4(
abs(v_tt.x),
abs(v_tt.y),
abs(v_tt.z),
1.0
);
}
#include <common>
uniform sampler2D tPos;
// uniform sampler2D tIdx;
varying vec3 v_tt;
void main() {
// vec3 newPos = vec3(1.0);
// position is changed to host uv vals
vec4 tt = texture2D(tPos, position.xy);
// vec4 idx = texture2D(tIdx, position.xy);
v_tt = normalize(tt.xyz);
vec4 mvPosition = modelViewMatrix * tt;
vec4 outputPos = projectionMatrix * mvPosition;
gl_Position = outputPos;
}
/* eslint-disable */
/**
* @author yomboprime https://github.com/yomboprime
*
* GPUComputationRenderer, based on SimulationRenderer by zz85
*
* The GPUComputationRenderer uses the concept of variables. These variables are RGBA float textures that hold 4 floats
* for each compute element (texel)
*
* Each variable has a fragment shader that defines the computation made to obtain the variable in question.
* You can use as many variables you need, and make dependencies so you can use textures of other variables in the shader
* (the sampler uniforms are added automatically) Most of the variables will need themselves as dependency.
*
* The renderer has actually two render targets per variable, to make ping-pong. Textures from the current frame are used
* as inputs to render the textures of the next frame.
*
* The render targets of the variables can be used as input textures for your visualization shaders.
*
* Variable names should be valid identifiers and should not collide with THREE GLSL used identifiers.
* a common approach could be to use 'texture' prefixing the variable name; i.e texturePosition, textureVelocity...
*
* The size of the computation (sizeX * sizeY) is defined as 'resolution' automatically in the shader. For example:
* #DEFINE resolution vec2( 1024.0, 1024.0 )
*
* -------------
*
* Basic use:
*
* // Initialization...
*
* // Create computation renderer
* var gpuCompute = new GPUComputationRenderer( 1024, 1024, renderer );
*
* // Create initial state float textures
* var pos0 = gpuCompute.createTexture();
* var vel0 = gpuCompute.createTexture();
* // and fill in here the texture data...
*
* // Add texture variables
* var velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, pos0 );
* var posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, vel0 );
*
* // Add variable dependencies
* gpuCompute.setVariableDependencies( velVar, [ velVar, posVar ] );
* gpuCompute.setVariableDependencies( posVar, [ velVar, posVar ] );
*
* // Add custom uniforms
* velVar.material.uniforms.time = { value: 0.0 };
*
* // Check for completeness
* var error = gpuCompute.init();
* if ( error !== null ) {
* console.error( error );
* }
*
*
* // In each frame...
*
* // Compute!
* gpuCompute.compute();
*
* // Update texture uniforms in your visualization materials with the gpu renderer output
* myMaterial.uniforms.myTexture.value = gpuCompute.getCurrentRenderTarget( posVar ).texture;
*
* // Do your rendering
* renderer.render( myScene, myCamera );
*
* -------------
*
* Also, you can use utility functions to create ShaderMaterial and perform computations (rendering between textures)
* Note that the shaders can have multiple input textures.
*
* var myFilter1 = gpuCompute.createShaderMaterial( myFilterFragmentShader1, { theTexture: { value: null } } );
* var myFilter2 = gpuCompute.createShaderMaterial( myFilterFragmentShader2, { theTexture: { value: null } } );
*
* var inputTexture = gpuCompute.createTexture();
*
* // Fill in here inputTexture...
*
* myFilter1.uniforms.theTexture.value = inputTexture;
*
* var myRenderTarget = gpuCompute.createRenderTarget();
* myFilter2.uniforms.theTexture.value = myRenderTarget.texture;
*
* var outputRenderTarget = gpuCompute.createRenderTarget();
*
* // Now use the output texture where you want:
* myMaterial.uniforms.map.value = outputRenderTarget.texture;
*
* // And compute each frame, before rendering to screen:
* gpuCompute.doRenderTarget( myFilter1, myRenderTarget );
* gpuCompute.doRenderTarget( myFilter2, outputRenderTarget );
*
*
*
* @param {int} sizeX Computation problem size is always 2d: sizeX * sizeY elements.
* @param {int} sizeY Computation problem size is always 2d: sizeX * sizeY elements.
* @param {WebGLRenderer} renderer The renderer
*/
import * as THREE from 'three';
export default function GPUComputationRenderer( sizeX, sizeY, renderer ) {
this.variables = [];
this.currentTextureIndex = 0;
var scene = new THREE.Scene();
var camera = new THREE.Camera();
camera.position.z = 1;
var passThruUniforms = {
texture: { value: null }
};
var passThruShader = createShaderMaterial( getPassThroughFragmentShader(), passThruUniforms );
var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), passThruShader );
scene.add( mesh );
this.addVariable = function( variableName, computeFragmentShader, initialValueTexture ) {
var material = this.createShaderMaterial( computeFragmentShader );
var variable = {
name: variableName,
initialValueTexture: initialValueTexture,
material: material,
dependencies: null,
renderTargets: [],
wrapS: null,
wrapT: null,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter
};
this.variables.push( variable );
return variable;
};
this.setVariableDependencies = function( variable, dependencies ) {
variable.dependencies = dependencies;
};
this.init = function() {
if ( ! renderer.extensions.get( "OES_texture_float" ) ) {
return "No OES_texture_float support for float textures.";
}
if ( renderer.capabilities.maxVertexTextures === 0 ) {
return "No support for vertex shader textures.";
}
for ( var i = 0; i < this.variables.length; i++ ) {
var variable = this.variables[ i ];
// Creates rendertargets and initialize them with input texture
variable.renderTargets[ 0 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter );
variable.renderTargets[ 1 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter );
this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 0 ] );
this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 1 ] );
// Adds dependencies uniforms to the ShaderMaterial
var material = variable.material;
var uniforms = material.uniforms;
if ( variable.dependencies !== null ) {
for ( var d = 0; d < variable.dependencies.length; d++ ) {
var depVar = variable.dependencies[ d ];
if ( depVar.name !== variable.name ) {
// Checks if variable exists
var found = false;
for ( var j = 0; j < this.variables.length; j++ ) {
if ( depVar.name === this.variables[ j ].name ) {
found = true;
break;
}
}
if ( ! found ) {
return "Variable dependency not found. Variable=" + variable.name + ", dependency=" + depVar.name;
}
}
uniforms[ depVar.name ] = { value: null };
material.fragmentShader = "\nuniform sampler2D " + depVar.name + ";\n" + material.fragmentShader;
}
}
}
this.currentTextureIndex = 0;
return null;
};
this.compute = function() {
var currentTextureIndex = this.currentTextureIndex;
var nextTextureIndex = this.currentTextureIndex === 0 ? 1 : 0;
for ( var i = 0, il = this.variables.length; i < il; i++ ) {
var variable = this.variables[ i ];
// Sets texture dependencies uniforms
if ( variable.dependencies !== null ) {
var uniforms = variable.material.uniforms;
for ( var d = 0, dl = variable.dependencies.length; d < dl; d++ ) {
var depVar = variable.dependencies[ d ];
uniforms[ depVar.name ].value = depVar.renderTargets[ currentTextureIndex ].texture;
}
}
// Performs the computation for this variable
this.doRenderTarget( variable.material, variable.renderTargets[ nextTextureIndex ] );
}
this.currentTextureIndex = nextTextureIndex;
};
this.getCurrentRenderTarget = function( variable ) {
return variable.renderTargets[ this.currentTextureIndex ];
};
this.getAlternateRenderTarget = function( variable ) {
return variable.renderTargets[ this.currentTextureIndex === 0 ? 1 : 0 ];
};
function addResolutionDefine( materialShader ) {
materialShader.defines.resolution = 'vec2( ' + sizeX.toFixed( 1 ) + ', ' + sizeY.toFixed( 1 ) + " )";
}
this.addResolutionDefine = addResolutionDefine;
// The following functions can be used to compute things manually
function createShaderMaterial( computeFragmentShader, uniforms ) {
uniforms = uniforms || {};
var material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: getPassThroughVertexShader(),
fragmentShader: computeFragmentShader
} );
addResolutionDefine( material );
return material;
}
this.createShaderMaterial = createShaderMaterial;
this.createRenderTarget = function( sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter ) {
sizeXTexture = sizeXTexture || sizeX;
sizeYTexture = sizeYTexture || sizeY;
wrapS = wrapS || THREE.ClampToEdgeWrapping;
wrapT = wrapT || THREE.ClampToEdgeWrapping;
minFilter = minFilter || THREE.NearestFilter;
magFilter = magFilter || THREE.NearestFilter;
var renderTarget = new THREE.WebGLRenderTarget( sizeXTexture, sizeYTexture, {
wrapS: wrapS,
wrapT: wrapT,
minFilter: minFilter,
magFilter: magFilter,
format: THREE.RGBAFormat,
type: ( /(iPad|iPhone|iPod)/g.test( navigator.userAgent ) ) ? THREE.HalfFloatType : THREE.FloatType,
stencilBuffer: false
} );
return renderTarget;
};
this.createTexture = function( sizeXTexture, sizeYTexture ) {
sizeXTexture = sizeXTexture || sizeX;
sizeYTexture = sizeYTexture || sizeY;
var a = new Float32Array( sizeXTexture * sizeYTexture * 4 );
var texture = new THREE.DataTexture( a, sizeX, sizeY, THREE.RGBAFormat, THREE.FloatType );
texture.needsUpdate = true;
return texture;
};
this.renderTexture = function( input, output ) {
// Takes a texture, and render out in rendertarget
// input = Texture
// output = RenderTarget
passThruUniforms.texture.value = input;
this.doRenderTarget( passThruShader, output);
passThruUniforms.texture.value = null;
};
this.doRenderTarget = function( material, output ) {
mesh.material = material;
renderer.render( scene, camera, output );
mesh.material = passThruShader;
};
// Shaders
function getPassThroughVertexShader() {
return "void main() {\n" +
"\n" +
" gl_Position = vec4( position, 1.0 );\n" +
"\n" +
"}\n";
}
function getPassThroughFragmentShader() {
return "uniform sampler2D texture;\n" +
"\n" +
"void main() {\n" +
"\n" +
" vec2 uv = gl_FragCoord.xy / resolution.xy;\n" +
"\n" +
" gl_FragColor = texture2D( texture, uv );\n" +
"\n" +
"}\n";
}
}
import * as THREE from 'three'
import 'imports-loader?THREE=three!three/examples/js/postprocessing/EffectComposer.js'
import 'imports-loader?THREE=three!three/examples/js/postprocessing/RenderPass.js'
import 'imports-loader?THREE=three!three/examples/js/postprocessing/MaskPass.js'
import 'imports-loader?THREE=three!three/examples/js/postprocessing/ShaderPass.js'
import 'imports-loader?THREE=three!three/examples/js/shaders/CopyShader.js'
import 'imports-loader?THREE=three!three/examples/js/shaders/FXAAShader.js'
import 'imports-loader?THREE=three!three/examples/js/shaders/ConvolutionShader.js'
import 'imports-loader?THREE=three!three/examples/js/shaders/LuminosityHighPassShader.js'
import 'imports-loader?THREE=three!three/examples/js/postprocessing/UnrealBloomPass.js'
import 'imports-loader?THREE=three!three/examples/js/controls/OrbitControls.js'
import * as vertexer from './vertexer/vertexer'
import * as dat from 'dat.gui'
var CONFIG = {
edit: true && !(process.env.NODE_ENV === 'production'),
// camPos: [0.00000905161650112143, -1.6328903203517724, 0.017842728918007384],
// camPos: [0, 0, 275],
camPos: [70.6803230191502, 126.7125806507623, -401.1762804647324],
bgColor: 0x50505,
useComposer: true,
bloomPass: {
threshold: 0.00001,
// strength: 4.5,
strength: 2.3,
radius: 1.0
}
}
var renderer, composer, size, scene, camera, rAFID, dpi, gui, graph, bloomPass
var syncSizeHandler = () => {
composer.setSize(size.width * dpi, size.height * dpi)
renderer.setPixelRatio(dpi)
renderer.setSize(size.width, size.height)
camera.aspect = size.width / size.height
camera.updateProjectionMatrix()
}
var addColor = ({ gui, color }) => {
let obj = {
color: color.getHex()
}
let api = gui.addColor(obj, 'color')
api.onChange((val) => {
color.setHex(val)
color.needsUpdate = true
})
return api
}
var setupEditorGUI = ({ dom }) => {
let visible = true
gui = new dat.GUI({ name: 'home', autoPlace: visible })
gui.add(bloomPass, 'threshold', 0, 1);
gui.add(bloomPass, 'strength', 0, 10);
gui.add(bloomPass, 'radius', 0, 3);
addColor({ gui, color: scene.background })
var control = new THREE.OrbitControls(camera, dom)
control.addEventListener('change', () => {
console.log(`${camera.position.x}, ${camera.position.y}, ${camera.position.z}`)
})
setInterval(() => {
control.update()
}, 1000 / 60)
window.addEventListener('cleanup-gl', () => {
gui.destroy()
})
}
var setupCamera = () => {
let fov = 75
let aspect = size.width / size.height
let near = 0.1
let far = 10000
camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
camera.position.fromArray(CONFIG.camPos)
camera.lookAt(0,0,0)
}
var setupRenderer = ({ dom }) => {
renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
preserveDrawingBuffer: true
})
renderer.domElement.style.marginBottom = '-6px'
dom.appendChild(renderer.domElement)
}
var setupScene = () => {
scene = new THREE.Scene()
scene.background = new THREE.Color(CONFIG.bgColor)
}
var setupWindowResize = ({ dom }) => {
var resizer = () => {
var rect = dom.getBoundingClientRect()
size = {
width: rect.width,
height: rect.height,
aspect: rect.width / rect.height
}
dpi = window.devicePixelRatio || 1.0
}
window.addEventListener('resize', resizer)
resizer()
window.addEventListener('resize', syncSizeHandler)
}
var setupComposer = () => {
composer = new THREE.EffectComposer(renderer)
let renderBG = new THREE.RenderPass(scene, camera)
bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(size.width * dpi, size.height * dpi), 1.5, 0.4, 0.85)
bloomPass.renderToScreen = true
bloomPass.threshold = CONFIG.bloomPass.threshold
bloomPass.strength = CONFIG.bloomPass.strength
bloomPass.radius = CONFIG.bloomPass.radius
composer.addPass(renderBG)
composer.addPass(bloomPass)
}
var run = () => {
let useBloom = CONFIG.useComposer
if (useBloom && scene && camera && renderer && composer) {
composer.render()
} else if (scene && camera && renderer) {
renderer.render(scene, camera)
}
}
export const setupGraph = ({ dom }) => {
// objects
graph = graph || {}
graph.vertexer = vertexer.getAPI({ dom, renderer, scene, camera, gui, CONFIG })
return {
runAll: () => {
graph.vertexer.render()
}
}
}
export const setup = ({ dom }) => {
setupWindowResize({ dom })
setupRenderer({ dom })
setupCamera()
setupScene()
setupComposer()
// sync once
syncSizeHandler()
CONFIG.edit && setupEditorGUI({ dom })
let runners = setupGraph({ dom })
function loop () {
rAFID = window.requestAnimationFrame(loop)
runners.runAll()
run()
}
rAFID = window.requestAnimationFrame(loop)
}
export const clean = () => {
window.cancelAnimationFrame(rAFID)
window.dispatchEvent(new Event('cleanup-gl'))
}
precision highp float;
precision highp sampler2D;
uniform float time;
uniform sampler2D tIdx;
mat3 rotateX(float rad) {
float c = cos(rad);
float s = sin(rad);
return mat3(
1.0, 0.0, 0.0,
0.0, c, s,
0.0, -s, c
);
}
mat3 rotateY(float rad) {
float c = cos(rad);
float s = sin(rad);
return mat3(
c, 0.0, -s,
0.0, 1.0, 0.0,
s, 0.0, c
);
}
mat3 rotateZ(float rad) {
float c = cos(rad);
float s = sin(rad);
return mat3(
c, s, 0.0,
-s, c, 0.0,
0.0, 0.0, 1.0
);
}
mat3 rotateQ (vec3 axis, float rad) {
float hr = rad / 2.0;
float s = sin( hr );
vec4 q = vec4(axis * s, cos( hr ));
vec3 q2 = q.xyz + q.xyz;
vec3 qq2 = q.xyz * q2;
vec2 qx = q.xx * q2.yz;
float qy = q.y * q2.z;
vec3 qw = q.w * q2.xyz;
return mat3(
1.0 - (qq2.y + qq2.z), qx.x - qw.z, qx.y + qw.y,
qx.x + qw.z, 1.0 - (qq2.x + qq2.z), qy - qw.x,
qx.y - qw.y, qy + qw.x, 1.0 - (qq2.x + qq2.y)
);
}
void main () {
vec2 cellSize = 1.0 / resolution.xy;
vec2 newCell = gl_FragCoord.xy;
vec2 uv = newCell * cellSize;
vec4 pos = texture2D(tPos, uv);
vec4 idx = texture2D(tIdx, uv);
bool isInvalid = false;
float vertexIDX = idx.x;
float squareIDX = idx.y;
float totalPoints = idx.z;
float lineNums = 500.0;
float stackIDX = floor(squareIDX / lineNums);
float lineIDX = mod(squareIDX, lineNums);
if (lineIDX > 300.0) {
isInvalid = true;
}
if (stackIDX > 300.0) {
isInvalid = true;
}
if (isInvalid) {
discard;
return;
}
float sX = 0.3;
float sY = 0.3;
float gapX = 20.0 * 1.0 / sX;
float gapY = 0.0;
float w = sX * (2.0 + gapX);
float h = sY * (2.0 + gapY);
float offsetX = (w * lineIDX);
float offsetY = (h * stackIDX);
float offsetZ = (0.0);
if (vertexIDX == 0.0) {
pos.x = 1.0 * sX;
pos.y = 1.0 * sY;
pos.z = 0.0;
} else if (vertexIDX == 1.0) {
pos.x = -1.0 * sX;
pos.y = 1.0 * sY;
pos.z = 0.0;
} else if (vertexIDX == 2.0) {
pos.x = -1.0 * sX;
pos.y = -1.0 * sY;
pos.z = 0.0;
} else if (vertexIDX == 3.0) {
pos.x = 1.0 * sX;
pos.y = 1.0 * sY;
pos.z = 0.0;
} else if (vertexIDX == 4.0) {
pos.x = -1.0 * sX;
pos.y = -1.0 * sY;
pos.z = 0.0;
} else if (vertexIDX == 5.0) {
pos.x = 1.0 * sX;
pos.y = -1.0 * sY;
pos.z = 0.0;
} else {
isInvalid = true;
}
pos.y += offsetY;
pos.x += offsetX;
pos.z += offsetZ;
float pX = pos.x;
float pY = pos.y;
float piz = 0.01 * 2.0 * 3.14159265;
pos.xyz = rotateQ(normalize(vec3(1.0, 1.0, 1.0)), time + pY * piz) * rotateZ(time + pY * piz) * pos.xyz;
pos.z += sin(time + pX * piz * 0.333) * 50.0;
if (isInvalid) {
pos.w = 0.0;
discard;
} else {
pos.w = 1.0;
gl_FragColor = pos;
}
}
import * as THREE from 'three'
// import 'imports-loader?THREE=three!three/examples/js/GPUComputationRenderer.js'
import GPUComputationRenderer from '../../shared/GPGPU.js'
/* eslint-enable */
export const makeAPI = ({ renderer, scene, camera, gui, CONFIG }) => {
var api = {}
var WIDTH = 1024;
var gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer)
// pos IDX
var posIdx = gpuCompute.createTexture();
var slot = posIdx.image.data;
var p = 0;
for ( var j = 0; j < WIDTH; j ++ ) {
for ( var i = 0; i < WIDTH; i ++ ) {
let id = p / 4;
slot[p + 0] = id % 6; // slot idx
slot[p + 1] = Math.floor(id / 6); // vertex idx
slot[p + 2] = Math.floor(WIDTH * WIDTH / 4.0 / 6.0); // point idx
slot[p + 3] = 0;
p += 4;
}
}
var posDynamic = gpuCompute.createTexture();
var posVar = gpuCompute.addVariable('tPos', require('raw-loader!./tPos.glowing.frag'), posDynamic );
posVar.material.uniforms.tIdx = { value: posIdx };
posVar.material.uniforms.time = { value: 0 };
gpuCompute.setVariableDependencies( posVar, [ posVar ] );
var error = gpuCompute.init();
if (error !== null) {
console.error(error)
}
var geo = new THREE.BufferGeometry();
let getUVInfo = () => {
let newArr = []
var na = 0;
for ( var j = 0; j < WIDTH; j ++ ) {
for ( var i = 0; i < WIDTH; i ++ ) {
newArr[na + 0] = i / WIDTH;
newArr[na + 1] = j / WIDTH;
newArr[na + 2] = 0;
na += 3;
}
}
return newArr
}
geo.addAttribute('position', new THREE.Float32BufferAttribute(getUVInfo(), 3));
geo.addAttribute('posIdx', new THREE.Float32BufferAttribute(posIdx.image.data, 4));
var uniforms = {
time: { value: 0 },
tPos: { value: null }
}
var material = new THREE.ShaderMaterial({
transparent: true,
uniforms,
defines: {
resolution: `vec2(${renderer.domElement.width.toFixed(1)}, ${renderer.domElement.height.toFixed(1)})`
},
vertexShader: require('raw-loader!./display.vert'),
fragmentShader: require('raw-loader!./display.frag'),
side: THREE.DoubleSide
})
var mesh = new THREE.Mesh(geo, material)
scene.add(mesh)
api.render = () => {
posVar.material.uniforms.time.value = window.performance.now() * 0.001
uniforms.tPos.value = gpuCompute.getCurrentRenderTarget(posVar).texture
uniforms.time.value = window.performance.now() * 0.001
gpuCompute.compute()
}
return api
}
export const getAPI = ({ renderer, scene, camera, gui, CONFIG }) => {
let api = makeAPI({ renderer, scene, camera, gui, CONFIG })
return api
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment