Created
May 8, 2015 18:42
-
-
Save heyLu/97e2ce68b55f4adbbf3a to your computer and use it in GitHub Desktop.
Raymarching (in a box)
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
<!doctype html> | |
<!-- | |
The real/complete code is at https://github.com/heyLu/lp/tree/master/glsl | |
--> | |
<html> | |
<head> | |
<title>Raymarching!</title> | |
</head> | |
<body> | |
<script> | |
// findSlider.js | |
function findSlider(input) { | |
var type = "unknown"; | |
var match = input.match(/^(uniform|attribute) (int|float|vec2|vec3) (.*);/); | |
if (!match) { | |
return false; | |
} | |
type = match[2]; | |
var name = match[3]; | |
var numRegexp = /[+-]?(?:\d+(?:\.\d+)?)/; | |
var singleValueRegexp = new RegExp(`\\((${numRegexp.source}),\\s*(${numRegexp.source}),\\s*(${numRegexp.source})\\)`); | |
var vec2Regexp = new RegExp(`^${singleValueRegexp.source},\\s*${singleValueRegexp.source}$`) | |
var vec3Regexp = new RegExp(`^${singleValueRegexp.source},\\s*${singleValueRegexp.source},\\s*${singleValueRegexp.source}$`) | |
var match = input.match(/\/\/#slider\[(.*)\]/); | |
if (!match) { | |
return false; | |
} | |
var rawVal = match[1]; | |
if (type == "int" || type == "float") { | |
var vals = rawVal.split(","); | |
vals = vals.map(function(v) { | |
return Number(v); | |
}); | |
} else if (type == "vec2" || type == "vec3") { | |
var re = type == "vec2" ? vec2Regexp : vec3Regexp; | |
var match = rawVal.match(re); | |
if (!match) { | |
throw new Error("invalid " + type + " slider"); | |
} | |
var vals = []; | |
for (var i = 0; i < Number(type[type.length-1]); i++) { | |
vals.push([match[1+i*3+0], match[1+i*3+1], match[1+i*3+2]].map(Number)); | |
} | |
} else { | |
throw new Error("unknown type"); | |
} | |
return {name: name, type: type, range: vals}; | |
} | |
function findSliders(input) { | |
var vars = []; | |
input.split("\n").forEach(function(line) { | |
var slider = findSlider(line); | |
if (slider) { | |
vars.push(slider); | |
} | |
}); | |
return vars; | |
} | |
// sliders.js | |
//document.body.innerHTML = ""; | |
var styleEl = document.createElement("style"); | |
styleEl.textContent = ` | |
.container .slider { | |
margin-left: 1em; | |
} | |
`; | |
document.head.appendChild(styleEl); | |
function makeSlider(name, range, onChange) { | |
var sliderContainerEl = document.createElement("div"); | |
sliderContainerEl.className = "slider"; | |
if (name) { | |
var sliderLabelEl = document.createElement("label"); | |
sliderLabelEl.textContent = name; | |
sliderContainerEl.appendChild(sliderLabelEl); | |
} | |
var sliderEl = document.createElement("input"); | |
sliderEl.type = "range"; | |
sliderEl.min = range[0]; | |
sliderEl.max = range[2]; | |
sliderEl.step = Math.min(0.01, (range[2] - range[0]) / 100); | |
sliderEl.value = range[1]; | |
sliderEl.style.verticalAlign = "middle"; | |
sliderEl.addEventListener("input", onChange); | |
sliderContainerEl.appendChild(sliderEl); | |
var sliderValueEl = document.createElement("span"); | |
sliderValueEl.style = "display: inline-block; width: 3em;"; | |
sliderValueEl.textContent = sliderEl.value; | |
sliderContainerEl.appendChild(sliderValueEl); | |
sliderEl.addEventListener("input", function() { sliderValueEl.textContent = sliderEl.value }); | |
return sliderContainerEl; | |
} | |
function makeMultiSlider(name, ranges, onChange) { | |
var containerEl = document.createElement("div"); | |
containerEl.className = "container"; | |
document.body.appendChild(containerEl); | |
var labelEl = document.createElement("label"); | |
labelEl.textContent = name; | |
containerEl.appendChild(labelEl); | |
var names = [".x", ".y", ".z", ".y"]; | |
ranges.forEach(function(range, i) { | |
containerEl.appendChild(makeSlider(names[i], range, function(ev) { | |
onChange(ev, i); | |
})); | |
}); | |
return containerEl; | |
} | |
function addSliders(parent, sliders) { | |
sliders.forEach(function(slider) { | |
switch (slider.type) { | |
case "int": | |
case "float": | |
parent.appendChild(makeSlider(slider.name, slider.range, slider.onChange)); | |
break; | |
case "vec2": | |
case "vec3": | |
parent.appendChild(makeMultiSlider(slider.name, slider.range, slider.onChange)); | |
break; | |
default: | |
throw new Error("unknown slider type " + slider.type); | |
} | |
}); | |
} | |
function initSliders(gl, program, sliders, onChange) { | |
sliders.forEach(function(slider) { | |
switch (slider.type) { | |
case "int": | |
slider.uniform = gl.getUniformLocation(program, slider.name); | |
gl.uniform1f(slider.uniform, slider.range[1]); | |
slider.onChange = function(ev) { | |
gl.uniform1i(slider.uniform, parseInt(ev.target.value)); | |
if (onChange) { | |
onChange(ev, slider); | |
} | |
} | |
break; | |
case "float": | |
slider.uniform = gl.getUniformLocation(program, slider.name); | |
gl.uniform1f(slider.uniform, slider.range[1]); | |
slider.onChange = function(ev) { | |
gl.uniform1f(slider.uniform, parseFloat(ev.target.value)); | |
if (onChange) { | |
onChange(ev, slider); | |
} | |
} | |
break; | |
case "vec2": | |
case "vec3": | |
slider.values = slider.range.map(function(r) { return r[1] }); | |
slider.uniform = gl.getUniformLocation(program, slider.name); | |
if (slider.type == "vec2") { | |
gl.uniform2f(slider.uniform, slider.values[0], slider.values[1]); | |
} else if (slider.type == "vec3") { | |
gl.uniform3f(slider.uniform, slider.values[0], slider.values[1], slider.values[2]); | |
} else { | |
throw new Error("unknown slider type " + slider.type); | |
} | |
slider.onChange = function(ev, i) { | |
slider.values[i] = parseFloat(ev.target.value); | |
if (slider.type == "vec2") { | |
gl.uniform2f(slider.uniform, slider.values[0], slider.values[1]); | |
} else if (slider.type == "vec3") { | |
gl.uniform3f(slider.uniform, slider.values[0], slider.values[1], slider.values[2]); | |
} else { | |
throw new Error("unknown slider type " + slider.type); | |
} | |
if (onChange) { | |
onChange(ev, slider, i); | |
} | |
} | |
break; | |
default: | |
throw new Error("unknown slider type " + slider.type); | |
} | |
}); | |
} | |
//var sliders = [{"name":" iPosition","type":"vec3","range":[[0.01,0.52,1.03],[0.04,0.55,1.06],[0.07,0.58,1.09]]},{"name":" iResolution","type":"vec2","range":[[0.01,0.52,1.03],[0.04,0.55,1.06]]},{"name":"fancyness","type":"float","range":[0.01,0.52,1.03]}]; | |
//addSliders(document.body, sliders); | |
// raymarching.js | |
// # Rendering Signed Distance Functions with WebGL | |
// | |
// Yes, you can do this in Shadertoy, but this is how you can do it from scratch. | |
// (It also loads a lot faster.) | |
// | |
// Use it by opening the Scratchpad in Firefox and evaluate the code with `Ctrl-R` | |
// to rerender. Including via `<script>` should also work. | |
// | |
// # Resources | |
// | |
// - [Raw WebGL](http://nickdesaulniers.github.io/RawWebGL) ([video](https://www.youtube.com/watch?v=H4c8t6myAWU)) | |
// - [WebGL Fundamentals](http://webglfundamentals.org/webgl/lessons/webgl-fundamentals.html) | |
try { | |
function compileShader(gl, type, shaderSrc) { | |
var shader = gl.createShader(type); | |
gl.shaderSource(shader, shaderSrc); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
throw new Error(gl.getShaderInfoLog(shader)); | |
} | |
return shader; | |
} | |
function linkShaders(gl, vertexShader, fragmentShader) { | |
var program = gl.createProgram(); | |
gl.attachShader(program, vertexShader); | |
gl.attachShader(program, fragmentShader); | |
gl.linkProgram(program); | |
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
throw new Error(gl.getProgramInfoLog(program)); | |
} | |
return program; | |
} | |
function initBuffer(gl, data, elemPerVertex, attribute) { | |
var buffer = gl.createBuffer(); | |
if (!buffer) { | |
throw new Error('failed to create buffer'); | |
} | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); | |
gl.vertexAttribPointer(attribute, elemPerVertex, gl.FLOAT, false, 0, 0); | |
gl.enableVertexAttribArray(attribute); | |
} | |
var vertexShaderSrc = ` | |
attribute vec4 aPosition; | |
void main() { | |
gl_Position = aPosition; | |
} | |
` | |
var fragmentShaderSrc = ` | |
precision highp float; | |
uniform vec2 iResolution; | |
uniform vec3 iMouse; | |
const int MaximumRaySteps = 150; | |
const float MinimumDistance = 0.0000001; | |
float DistanceEstimator(vec3 pos); | |
float trace(vec3 from, vec3 direction) { | |
float totalDistance = 0.0; | |
int stepsDone = 0; | |
for (int steps = 0; steps < MaximumRaySteps; steps++) { | |
vec3 p = from + totalDistance * direction; | |
float distance = DistanceEstimator(p); | |
totalDistance += distance; | |
stepsDone = steps; | |
if (distance < MinimumDistance) break; | |
} | |
return 1.0-float(stepsDone)/float(MaximumRaySteps); | |
} | |
float DistanceEstimator(vec3 pos) { | |
return length(pos) - 1.0; | |
} | |
/*uniform int MaxIterations; //#slider[1,50,200] | |
const float bailout = 4.0; | |
const float power = 8.0; | |
const float phaseX = 0.0; | |
const float phaseY = 0.0; | |
float DistanceEstimator(vec3 z0) { | |
vec3 c = z0; | |
vec3 z = z0; | |
float pd = power - 1.0; // power for derivative | |
// Convert z to polar coordinates | |
float r = length(z); | |
float th = atan(z.y, z.x); | |
float ph = asin(z.z / r); | |
vec3 dz; | |
float ph_dz = 0.0; | |
float th_dz = 0.0; | |
float r_dz = 1.0; | |
float powR, powRsin; | |
// Iterate to compute the distance estimator. | |
for (int n = 0; n < MaxIterations; n++) { | |
// Calculate derivative of | |
powR = power * pow(r, pd); | |
powRsin = powR * r_dz * sin(ph_dz + pd*ph); | |
dz.x = powRsin * cos(th_dz + pd*th) + 1.0; | |
dz.y = powRsin * sin(th_dz + pd*th); | |
dz.z = powR * r_dz * cos(ph_dz + pd*ph); | |
// polar coordinates of derivative dz | |
r_dz = length(dz); | |
th_dz = atan(dz.y, dz.x); | |
ph_dz = acos(dz.z / r_dz); | |
// z iteration | |
powR = pow(r, power); | |
powRsin = sin(power*ph); | |
z.x = powR * powRsin * cos(power*th); | |
z.y = powR * powRsin * sin(power*th); | |
z.z = powR * cos(power*ph); | |
z += c; | |
r = length(z); | |
if (r > bailout) break; | |
th = atan(z.y, z.x) + phaseX; | |
ph = acos(z.z / r) + phaseY; | |
} | |
// Return the distance estimation value which determines the next raytracing | |
// step size, or if whether we are within the threshold of the surface. | |
return 0.5 * r * log(r)/r_dz; | |
}*/ | |
mat3 setCamera( in vec3 ro, in vec3 ta, float cr ) { | |
vec3 cw = normalize(ta-ro); | |
vec3 cp = vec3(sin(cr), cos(cr),0.0); | |
vec3 cu = normalize( cross(cw,cp) ); | |
vec3 cv = normalize( cross(cu,cw) ); | |
return mat3( cu, cv, cw ); | |
} | |
uniform vec3 origin; //#slider[(-10.0,1.0,10.0),(-10.0,2.0,10.0),(-10.0,-1.0,10.0)] | |
uniform vec3 angle; //#slider[(-3.0,0.0,3.0),(-3.0,0.0,3.0),(-3.0,0.0,3.0)] | |
uniform vec3 color; //#slider[(0.0, 1.0, 1.0),(0.0,0.0,1.0),(0.0,0.0,1.0)] | |
uniform float colorMix; //#slider[0.0,0.9,1.0] | |
void main() { | |
vec2 q = gl_FragCoord.xy / iResolution.xy; | |
vec2 p = -1.0 + 2.0*q; | |
p.x *= iResolution.x / iResolution.y; | |
vec2 mo = iMouse.xy/iResolution.xy; | |
float time = 15.0 + 0.0; // iGlobalTime | |
// camera | |
vec3 ro = origin; //vec3( -0.5+3.2*cos(0.1*time + 6.0*mo.x), 1.0 + 2.0*mo.y, 0.5 + 3.2*sin(0.1*time + 6.0*mo.x) ); | |
vec3 ta = angle; //vec3( -0.5, -0.4, 0.5 ); | |
// camera-to-world transformation | |
mat3 ca = setCamera( ro, ta, 0.0 ); | |
// ray direction | |
vec3 rd = ca * normalize( vec3(p.xy, 2.5) ); | |
// render | |
float dist = trace(ro, rd); | |
vec3 col = vec3(dist, dist, dist); | |
col = mix(color, col, colorMix); | |
//col = pow( col, vec3(0.4545)); | |
gl_FragColor = vec4( col, 1.0 ); | |
} | |
` | |
document.body.style = "margin: 0; overflow: hidden;"; | |
document.body.innerHTML = ""; | |
var styleEl = document.createElement("style"); | |
styleEl.textContent = ` | |
#sidebar { | |
position: absolute; | |
top: 0; | |
right: -250px; | |
padding: 1ex; | |
font-family: monospace; | |
font-weight: bold; | |
background-color: rgba(255, 255, 255, 0.5); | |
} | |
#sidebar:hover { | |
transition: right 0.1s; | |
right: 0; | |
} | |
` | |
document.head.appendChild(styleEl); | |
var canvas = document.createElement("canvas"); | |
var w = canvas.width = window.innerWidth; | |
var h = canvas.height = window.innerHeight; | |
document.body.appendChild(canvas); | |
var gl = canvas.getContext("webgl"); | |
if (!gl) { alert("i think your browser does not support webgl"); } | |
gl.clearColor(0.0, 0.0, 0.0, 1.0); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
var vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSrc); | |
var fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSrc); | |
var program = linkShaders(gl, vertexShader, fragmentShader); | |
gl.useProgram(program); | |
var aPosition = gl.getAttribLocation(program, 'aPosition'); | |
var iResolution = gl.getUniformLocation(program, 'iResolution'); | |
var iMouse = gl.getUniformLocation(program, 'iMouse'); | |
gl.vertexAttrib2f(aPosition, 0.0, 0.0); | |
gl.uniform2f(iResolution, canvas.width, canvas.height); | |
gl.uniform3f(iMouse, 0.0, 0.0, 0.0); | |
// two triangles | |
var positions = new Float32Array([ | |
-1.0, 1.0, | |
-1.0, -1.0, | |
1.0, 1.0, | |
1.0, 1.0, | |
-1.0, -1.0, | |
1.0, -1.0 | |
]); | |
initBuffer(gl, positions, 2, aPosition); | |
function render() { | |
gl.drawArrays(gl.TRIANGLES, 0, 6); | |
} | |
document.body.addEventListener("mousemove", function(ev) { | |
gl.uniform3f(iMouse, ev.mouseX, ev.mouseY, 0.0); | |
}); | |
function draw() { | |
requestAnimationFrame(draw); | |
render(); | |
} | |
window.onresize = function(ev) { | |
w = canvas.width = window.innerWidth; | |
h = canvas.height = window.innerHeight; | |
gl.viewport(0, 0, w, h); | |
gl.uniform2f(iResolution, w, h); | |
render(); | |
}; | |
var sidebarEl = document.createElement("div"); | |
sidebarEl.id = "sidebar"; | |
document.body.appendChild(sidebarEl); | |
var sliders = findSliders(fragmentShaderSrc); | |
initSliders(gl, program, sliders, function(ev) { | |
render(); | |
}); | |
addSliders(sidebarEl, sliders); | |
render(); | |
} catch (e) { | |
window.error = e; | |
var msg = document.createElement("pre"); | |
msg.style = "color: red; position: absolute; right: 0; bottom: 0"; | |
msg.textContent = e; | |
document.body.appendChild(msg); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment