marching
Version:
Marching.js is a JavaScript library that compiles GLSL ray marchers.
457 lines (346 loc) • 14.5 kB
JavaScript
const SDF = {
camera: require( './camera.js' ),
__primitives: require( './primitives.js' ),
vectors: require( './vec.js' ),
distanceOps: require( './distanceOperations.js' ),
alterations: require( './alterations.js' ),
distanceDeforms: require( './distanceDeformations.js' ),
__domainOps: require( './domainOperations.js' ),
__noise: require( './noise.js' ),
__scene: require( './scene.js' ),
__lighting: require( './lighting.js' ),
__materials: require( './material.js' ),
__textures: require( './texture.js' ),
Var: require( './var.js' ).Var,
//Color: require( './color.js' ),
FFT: require( './audio.js' ),
fx: require( './mergepass.js' ),
// a function that generates the fragment shader
renderFragmentShader: require( './renderFragmentShader.js' ),
// additional callbacks that are run once per frame
callbacks: [],
// callbacks that are run *after* all rendering has occurred
postrendercallbacks: [],
geometries: [],
// the main drawing callback
render: null,
// the scene is a chain of Unions combining all elements together
scene: null,
// a speed of 1 corresponds to 60 fps.
delay: 0,
__isPaused:false,
defaultVertexSource:` #version 300 es
in vec2 a_pos;
void main() {
gl_Position = vec4( a_pos, 0., 1. );
}`
,
export( obj ) {
Object.assign(
obj,
this.primitives,
this.vectors,
this.distanceOps,
this.domainOps,
this.distanceDeforms,
//this.alterations
)
this.fx.export( obj )
obj.Light = this.Light
obj.Material = this.Material
obj.Texture = this.Texture
obj.camera = this.camera
obj.callbacks = this.callbacks // XXX remove once API stops using callbacks
obj.FFT = this.FFT
},
init( canvas, shouldInit = false ) {
this.primitives = this.__primitives( this )
this.Scene = this.__scene( this )
this.domainOps = this.__domainOps( this )
this.noise = this.__noise( this )
this.export( this )
this.canvas = canvas//document.createElement('canvas')
this.lighting = this.__lighting( this )
this.Light = this.lighting.light
this.materials = this.__materials( this )
this.Material = this.materials.material
this.textures = this.__textures( this )
this.Texture = this.textures.texture
this.gl = this.canvas.getContext( 'webgl2', { antialias:true, alpha:true })
},
// generate shaders, initialize camera, start rendering loop
createScene( ...args ) {
const scene = this.Scene( args, this.canvas )
this.requiredGeometries = []
this.requiredOps = []
this.memo = {}
return scene
},
start( fs, width, height, shouldAnimate ) {
if( this.render !== null ) this.render.running = false
this.fs = fs
this.callbacks.length = 0
this.render = this.initWebGL( this.defaultVertexSource, fs, width, height, shouldAnimate )
this.render.running = true
this.camera.init( this.gl, this.program, cb => {
this.callbacks.push( cb )
})
setTimeout( ()=> this.render( 0.0 ), 0 )
},
generateSDF( __scene ) {
let scene = { preface:'' }
/* if there is more than one object in our scene, chain pairs of objects
in Unions. So, given objects a,b,c, and d create:
Union( a, Union( b, Union( c,d ) ) )
... or something like that. If there is only a single object,
use that object as the entire scene.
*/
let objs = __scene.objs
if( objs.length > 1 ) {
// reduce objects to nested Unions
scene.output = objs.reduce( ( current, next ) => this.Union( current, next ) )
}else{
scene.output = objs[0]
}
// create an fancy emit() function that wraps the scene
// with an id #.
scene.output.__emit = scene.output.emit.bind( scene.output )
scene.output.emit = function( ...args ) {
const emitted = scene.output.__emit(...args)
const output = {
out: emitted.out,
preface: emitted.preface || ''
}
return output
}
this.scene = scene.output
let variablesDeclaration = scene.output.emit_decl()
const sceneRendering = scene.output.emit()
let pp = ''
for( let processor of __scene.postprocessing ) {
pp += processor.emit()
variablesDeclaration += processor.emit_decl()
}
this.postprocessing = __scene.postprocessing
return [ variablesDeclaration, sceneRendering, pp ]
},
compile( type, source ) {
const gl = this.gl
const shader = this.shader = gl.createShader( type );
gl.shaderSource( shader, source )
gl.compileShader( shader )
if( gl.getShaderParameter( shader, gl.COMPILE_STATUS) !== true ) {
let log = gl.getShaderInfoLog( shader )
gl.deleteShader( shader )
console.log( source )
console.log( log )
return null
}
return shader
},
createProgram( vs_source, fs_source ) {
const gl = this.gl
const vs = this.compile( gl.VERTEX_SHADER, vs_source )
const fs = this.compile( gl.FRAGMENT_SHADER, fs_source )
if( null === vs || null === fs ) return null
const program = gl.createProgram()
gl.attachShader( program, vs )
gl.attachShader( program, fs )
gl.linkProgram( program )
if( gl.getProgramParameter( program, gl.LINK_STATUS ) !== true ){
const log = gl.getProgramInfoLog( program )
gl.deleteShader(vs)
gl.deleteShader(fs)
gl.deleteProgram(program)
console.error( log )
return null
}
const drawProgram = gl.createProgram()
const fragSource = ` #version 300 es
precision mediump float;
uniform sampler2D uSampler;
uniform vec2 resolution;
out vec4 col;
void main() {
// copy color info from texture
col = vec4( texture( uSampler, gl_FragCoord.xy / resolution ).rgb, 1. );
}`
const fs_draw = this.compile( gl.FRAGMENT_SHADER, fragSource )
const vs_draw = this.compile( gl.VERTEX_SHADER, vs_source )
gl.attachShader( drawProgram, vs_draw )
gl.attachShader( drawProgram, fs_draw )
gl.linkProgram( drawProgram )
return [ program, drawProgram ]
},
clear() {
if( this.callbacks !== undefined ) this.callbacks.length = 0
if( this.postrendercallbacks !== undefined ) this.postrendercallbacks.length = 0
if( this.render !== null ) this.render.running = false
// remove post-processing fx
this.fx.clear()
this.geometries.length = 0
const gl = this.gl
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT )
},
pause() {
this.__isPaused = !this.__isPaused
},
initBuffers( width, height, colorTexture, depthTexture ) {
const gl = this.gl
gl.clearColor( 0.0, 0.0, 0.0, 0.0 )
gl.clear(gl.COLOR_BUFFER_BIT)
const framebuffer = gl.createFramebuffer()
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
const vbo = gl.createBuffer()
const vertices = new Float32Array([
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1
])
// initialize memory for buffer and populate it. Give
// open gl hint contents will not change dynamically.
gl.bindBuffer( gl.ARRAY_BUFFER, vbo )
gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW )
gl.drawBuffers([
gl.COLOR_ATTACHMENT0,
gl.COLOR_ATTACHMENT1
])
return { vbo, vertices, framebuffer }
},
initUniforms( gl, program ) {
const aPos = this.gl.getAttribLocation( this.program, "a_pos" )
const uTime= this.gl.getUniformLocation( this.program, "time" )
const uResolution = this.gl.getUniformLocation( this.program, "resolution" )
return { aPos, uTime, uResolution }
},
initTextures( gl, width, height ) {
gl.getExtension( 'EXT_color_buffer_float' )
const colorTexture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, colorTexture)
// must use linear interpolation for merge-pass integration
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR )
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR )
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE )
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE )
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null)
gl.myColorTexture = colorTexture
// store depth in floating point texture
const depthTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, depthTexture)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, width, height, 0, gl.RGBA, gl.FLOAT, null)
return { colorTexture, depthTexture }
},
updateLocations() {
this.postprocessing.forEach( pp => pp.update_location( this.gl, this.program ) )
this.scene.update_location( this.gl, this.program )
this.textures.update_location( this.gl, this.program )
this.materials.update_location( this.gl, this.program )
this.lighting.update_location( this.gl, this.program )
},
initShaderProgram( vs, fs, gl ) {
const programs = this.createProgram( vs, fs )
this.program = programs[0]
return programs
},
uploadData( gl ) {
this.materials.upload_data( gl )
this.textures.upload_data( gl )
this.scene.upload_data( gl )
this.lighting.upload_data( gl )
this.postprocessing.forEach( pp => pp.upload_data( gl ) )
},
uploadVertices( gl, aPos, vertices ) {
gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW )
gl.enableVertexAttribArray( aPos )
gl.vertexAttribPointer( aPos, 2, gl.FLOAT, false, 0, 0)
},
initWebGL( vs, fs, width, height,shouldAnimate=false ) {
this.canvas.width = width
this.canvas.height = height
this.fx.MP.settings.offset = this.textures.textures.length
const gl = this.gl,
programs = this.initShaderProgram( vs, fs, gl ),
{ colorTexture, depthTexture } = this.initTextures( gl, width, height ),
{ aPos, uTime, uResolution } = this.initUniforms( gl, programs[0] ),
{ vbo, vertices, framebuffer } = this.initBuffers( width, height, colorTexture, depthTexture )
let total_time = 0.0,
frameCount = 0
// only init post-processing if effects have been registered
if( this.fx.chain.length > 0 ) this.fx.init( colorTexture, depthTexture, gl )
gl.useProgram( this.program )
this.updateLocations( gl, this.program )
this.uploadVertices( gl, aPos, vertices )
gl.viewport( 0,0,width,height )
gl.uniform2f( uResolution, width, height )
const render = function( timestamp ){
if( render.running === true && shouldAnimate === true ) {
window.requestAnimationFrame( render )
}else if( render.running === false ) {
gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer )
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT )
return
}
gl.useProgram( this.program )
gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer )
if( this.fx.merger !== null ) {
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.fx.merger.tex.back.tex, 0 )
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.fx.merger.tex.bufTextures[0].tex, 0)
}else{
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTexture, 0)
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, depthTexture, 0)
}
gl.enableVertexAttribArray( aPos )
if( this.__isPaused === false ) {
this.currentTime = timestamp
if( this.delay !== 0 && this.delay >= frameCount ) {
frameCount++
return
}else if( this.delay !== 0 ) {
frameCount = 0
}
total_time = timestamp / 1000.0
gl.uniform1f( uTime, total_time )
this.callbacks.forEach( cb => cb( total_time, this.currentTime ) )
if( typeof window.onframe === 'function' ) window.onframe( total_time )
}
//this.textures.update_location( gl, this.program )
// transfer all data associated with uniforms in marching.js
this.uploadData( gl )
// draw to color and depth texturese
gl.bindBuffer( gl.ARRAY_BUFFER, vbo )
// if post-processing is not being used,
// draw directly to screen
if( this.fx.merger === null ) {
gl.bindFramebuffer( gl.FRAMEBUFFER, null )
}
gl.drawArrays( gl.TRIANGLES, 0, 6 )
/********* UNCOMMENT THIS LINE TO CHECK MARCHING.JS COLOR OUPTUT ***************/
// this.runCopyShader( gl, width, height, aPos, programs, colorTexture, vbo )
/********* UNCOMMENT THIS LINE TO CHECK MARCHING.JS DEPTH OUPTUT ***************/
// this.runCopyShader( gl, width, height, aPos, programs, depthTexture, vbo )
// conditional mergepass render
if( this.fx.merger !== null ) this.fx.merger.draw( total_time )
this.postrendercallbacks.forEach( fnc => fnc( total_time ) )
}.bind( SDF )
render.running = true
return render
},
runCopyShader( gl, width, height, loc_a_pos, programs, colorTexture, vbo ) {
gl.bindFramebuffer(gl.FRAMEBUFFER, null )
gl.bindTexture(gl.TEXTURE_2D, colorTexture)
gl.viewport(0, 0, width, height )
gl.bindBuffer( gl.ARRAY_BUFFER, vbo )
gl.useProgram( programs[1] )
const u_resolution = gl.getUniformLocation(programs[1], "resolution" )
gl.uniform2f( u_resolution, width, height )
gl.drawArrays( gl.TRIANGLES, 0, 6 )
}
}
module.exports = SDF