marching
Version:
Marching.js is a JavaScript library that compiles GLSL ray marchers.
308 lines (244 loc) • 8.18 kB
JavaScript
const getMainContinuous = function( steps, minDistance, maxDistance, postprocessing, bg='vec4(0.,0.,0.,1.)' ) {
const out = `
// adapted from https://www.shadertoy.com/view/ldfSWs
vec3 calcNormal(vec3 pos, float eps) {
const vec3 v1 = vec3( 1.0,-1.0,-1.0);
const vec3 v2 = vec3(-1.0,-1.0, 1.0);
const vec3 v3 = vec3(-1.0, 1.0,-1.0);
const vec3 v4 = vec3( 1.0, 1.0, 1.0);
return normalize( v1 * scene ( pos + v1*eps ).x+
v2 * scene ( pos + v2*eps ).x+
v3 * scene ( pos + v3*eps ).x+
v4 * scene ( pos + v4*eps ).x);
}
vec3 calcNormal(vec3 pos) {
return calcNormal(pos, 0.002);
}
// Adapted from from https://www.shadertoy.com/view/ldfSWs
vec2 calcRayIntersection( vec3 rayOrigin, vec3 rayDir, float maxd, float precis ) {
float latest = precis * 2.0;
float dist = +0.0;
float type = -1.0;
vec2 result;
vec2 res = vec2(-50000., -1.);;
for (int i = 0; i < ${steps} ; i++) {
if (latest < precis || dist > maxd) break;
result = scene(rayOrigin + rayDir * dist);
latest = result.x;
dist += latest;
}
if( dist < maxd ) {
result.x = dist;
res = result;
}
return res;
}
layout(location = 0) out vec4 col;
layout(location = 1) out vec4 depth;
void main() {
vec2 uv = gl_FragCoord.xy / resolution;
vec2 pos = uv * 2.0 - 1.0;
// not sure why I need the -y axis but without it
// everything is flipped using perspective-camera
pos.x *= ( resolution.x / -resolution.y );
vec4 color = bg;
vec3 ro = camera_pos;
vec3 rd = normalize( mat3(camera) * vec3( pos, 2. ) );
vec2 t = calcRayIntersection( ro, rd, ${maxDistance}, ${minDistance} );
vec3 samplePos = vec3(100.f);
if( t.x > -0.5 ) {
samplePos = ro + rd * t.x;
vec3 nor = calcNormal( samplePos );
color = vec4( lighting( samplePos, nor, ro, rd, t.y, true ), 1. );
${postprocessing}
}
col = clamp( vec4( color ), 0., 1. );
float normalizedDepth = t.x / ${maxDistance};
depth = abs(samplePos.z - ro.z ) < ${maxDistance} ? vec4( vec3( 1.-normalizedDepth ), 1. ) : vec4(0.);
}`
return out
}
const getMainVoxels = function( steps, postprocessing, voxelSize = .1 ) {
const out = `
struct VoxelDistance {
bvec3 mask;
vec3 distance;
float fogCoeff;
int id;
};
VoxelDistance calcRayIntersection( vec3 rayOrigin, vec3 rayDir ) {
vec2 result;
float m = ${voxelSize};
rayOrigin *= 1./m;
vec3 mapPos = vec3(floor(rayOrigin));
vec3 diff = mapPos - rayOrigin;
vec3 deltaDist = abs(vec3(length(rayDir)) / rayDir);
vec3 rayStep = vec3(sign(rayDir));
vec3 sideDist = (sign(rayDir) * diff + (sign(rayDir) * 0.5) + 0.5) * deltaDist;
bvec3 mask;
vec3 d = vec3(-100000.);
float fogCoeff = 0.;
for (int i = 0; i < ${Math.round(steps*1/voxelSize)} ; i++) {
result = scene(mapPos*m);
if( result.x <= 0. ) {
d = mapPos*m+result.x;
break;
}
mask = bvec3( lessThanEqual(sideDist.xyz, min(sideDist.yzx, sideDist.zxy)) );
sideDist += vec3( mask ) * deltaDist;
mapPos += vec3(mask) * rayStep;
fogCoeff += result.x * m;
}
VoxelDistance vd = VoxelDistance( mask, d, fogCoeff, int(result.y) );
return vd;
}
layout(location = 0) out vec4 col;
layout(location = 1) out vec4 depth;
void main() {
vec2 uv = gl_FragCoord.xy / resolution;
vec2 pos = uv * 2.0 - 1.0;
// not sure why I need the -y axis but without it
// everything is flipped using perspective-camera
pos.x *= ( resolution.x / -resolution.y );
vec4 color = bg;
vec3 ro = camera_pos;
vec3 rd = normalize( mat3(camera) * vec3( pos, 2. ) );
VoxelDistance vd = calcRayIntersection( ro, rd );
bvec3 mask = vd.mask;
vec3 nor;
if (mask.x) {
color = vec4(vec3(0.5), 1.);
nor = vec3(1.,0.,0.);
}
if (mask.y) {
color = vec4( vec3(1.0), 1. );
nor = vec3(0.,1.,0.);
}
if (mask.z) {
color = vec4( vec3(0.75), 1. );
nor = vec3(0.,0.,1.);
}
if( vd.distance.x == -100000. ) {
color = bg;
}
float modAmount = ${(1./voxelSize).toFixed(1)};
bool hit = false;
vec3 t = vec3( length(vd.distance-ro) );
if( color != bg ) {
vec3 pos = vd.distance;
color.xyz *= lighting( pos * modAmount, nor, ro, rd, float(vd.id), false );
hit = true;
${postprocessing};
}
col = color;//vec4( color, 1. );
float normalizedDepth = length( (vd.distance-ro) * ${voxelSize.toFixed(1)} );
depth = hit == true ? vec4( vec3(1.-normalizedDepth), 1. ) : vec4(0.);
}`
return out
}
module.exports = function( variables, scene, preface, geometries, lighting, postprocessing, steps=90, minDistance=.001, maxDistance=20, ops, voxelSize=0 ) {
const main = voxelSize === 0
? getMainContinuous( steps, minDistance, maxDistance, postprocessing )
: getMainVoxels( steps, postprocessing, voxelSize )
const fs_source = `
precision mediump float;
float PI = 3.141592653589793;
struct Light {
vec3 position;
vec3 color;
float attenuation;
};
int rotationCount = 1;
mat4 rotations[4] = mat4[4](
mat4(0.), mat4(0.), mat4(0.), mat4(0.)
);
struct Material {
int mode;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
vec3 fresnel;
int textureID;
};
struct SDF {
int materialID;
mat4 transform;
int textureID;
vec3 repeat;
mat4 repeatTransform;
};
uniform float time;
uniform vec2 resolution;
uniform vec3 camera_pos;
uniform vec3 camera_normal;
uniform float camera_rot;
uniform mat4 camera;
${variables}
// must be before geometries!
float length8( vec2 p ) {
return float( pow( pow(p.x,8.)+pow(p.y,8.), 1./8. ) );
}
vec4 opElongate( in vec3 p, in vec3 h ) {
//return vec4( p-clamp(p,-h,h), 0.0 ); // faster, but produces zero in the interior elongated box
vec3 q = abs(p)-h;
return vec4( max(q,0.0), min(max(q.x,max(q.y,q.z)),0.0) );
}
${ops}
/* GEOMETRIES */
${geometries}
vec2 scene(vec3 p);
// XXX todo put this in domainOperations.js
vec3 polarRepeat(vec3 p, float repetitions) {
float angle = 2.*PI/repetitions;
float a = atan(p.z, p.x) + angle/2.;
float r = length(p.xz);
float c = floor(a/angle);
a = mod(a,angle) - angle/2.;
vec3 _p = vec3( cos(a) * r, p.y, sin(a) * r );
// For an odd number of repetitions, fix cell index of the cell in -x direction
// (cell index would be e.g. -5 and 5 in the two halves of the cell):
if (abs(c) >= (repetitions/2.)) c = abs(c);
return _p;
}
// XXX this shouldn't be here...
float opHalve( in float sdf, vec4 p, in int dir ){
float _out = 0.;
switch( dir ) {
case 0:
_out = max( sdf, p.y );
break;
case 1:
_out = max( sdf, -p.y );
break;
case 2:
_out = max( sdf, p.x );
break;
case 3:
_out = max( sdf, -p.x );
break;
}
return _out;
}
// added k value to glsl-sdf-ops/soft-shadow
float softshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax, in float k ){
float res = 1.0;
float t = mint;
for( int i = 0; i < 12; i++ ) {
float h = scene( ro + rd * t ).x;
res = min( res, k * h / t );
t += clamp( h, 0.02, 0.10 );
if( h<0.001 || t>tmax ) break;
}
return clamp( res, 0.0, 1.0 );
}
${lighting}
vec2 scene(vec3 _p ) {
vec4 p = vec4( _p, 1. );
${preface}
return ${scene};
}
${main}
`
return fs_source
}