Няма описание

raymarching.js 10KB

    // # 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 { document.body.style = "margin: 0; overflow: hidden;"; document.body.innerHTML = ""; var errorEl = document.createElement("pre"); errorEl.style = "color: red; position: absolute; right: 0; bottom: 0; background-color: black; z-index: 4"; document.body.appendChild(errorEl); function displayError(e) { window.error = e; errorEl.textContent = e; console.error(e); } function clearError() { errorEl.textContent = ""; } 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 styleEl = document.createElement("style"); styleEl.textContent = ` #sidebar { position: absolute; top: 0; right: -250px; z-index: 1; padding: 1ex; font-family: monospace; font-weight: bold; background-color: rgba(255, 255, 255, 0.5); } #sidebar:hover { transition: right 0.1s; right: 0; } #editor { position: absolute; top: 0; left: 0; height: 100%; } #editor textarea { display: block; width: 72ex; height: calc(100% - 2em + 1px); /* TODO: fix this */ resize: horizontal; padding: 1ex; box-sizing: border-box; } #editor textarea, #editor input { border: none; background-color: rgba(255, 255, 255, 0.8); } #editor-changed { position: absolute; right: 0; top: 1.5em; } ` document.head.appendChild(styleEl); function TwoTriangles(canvas, fragmentShaderSrc, options) { var options = options || {}; var tt = {}; tt.canvas = canvas; tt.fragmentShaderSrc = transformShader(fragmentShaderSrc); tt.w = canvas.width = window.innerWidth; tt.h = canvas.height = window.innerHeight; var gl = tt.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, tt.fragmentShaderSrc); var program = tt.program = linkShaders(gl, vertexShader, fragmentShader); gl.useProgram(program); var aPosition = gl.getAttribLocation(program, 'aPosition'); var iResolution = gl.getUniformLocation(program, 'iResolution'); var iGlobalTime = gl.getUniformLocation(program, 'iGlobalTime'); var iMouse = gl.getUniformLocation(program, 'iMouse'); gl.vertexAttrib2f(aPosition, 0.0, 0.0); gl.uniform2f(iResolution, canvas.width, canvas.height); gl.uniform1f(iGlobalTime, 0.0); gl.uniform3f(iMouse, 0.0, 0.0, 0.0); tt.origin = gl.getUniformLocation(program, 'origin'); // 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); tt.render = function() { gl.drawArrays(gl.TRIANGLES, 0, 6); }; tt.playing = options.playing || false; tt.draw = function(timestamp) { if (tt.playing) { requestAnimationFrame(tt.draw); } gl.uniform1f(iGlobalTime, timestamp / 1000); tt.render(); } tt.playingControls = document.createElement("div"); var playPauseButton = document.createElement("button"); playPauseButton.textContent = "Play"; playPauseButton.onclick = function(ev) { tt.togglePlaying(); } tt.playingControls.appendChild(playPauseButton); tt.togglePlaying = function() { if (!tt.playing) { tt.playing = true; tt.draw(); } else { tt.playing = false; } playPauseButton.textContent = tt.playing ? "Pause" : "Play"; } window.addEventListener('keydown', function(ev) { if (ev.keyCode == 32 && document.activeElement != editor.el) { // Space ev.preventDefault(); tt.togglePlaying(); } }); tt.direction = [0, 0, 1]; tt.right = [-1, 0, 0]; tt.angle = {horizontal: 0.0, vertical: 0.0}; var iDirection = gl.getUniformLocation(program, 'iDirection'); gl.uniform3f(iDirection, tt.direction[0], tt.direction[1], tt.direction[2]); var mousePressed = false; tt.canvas.addEventListener("mousedown", function(ev) { if (ev.button == 0) { mousePressed = true; }}); tt.canvas.addEventListener("mouseup", function(ev) { mousePressed = false; }); tt.canvas.addEventListener("mousemove", function(ev) { gl.uniform3f(iMouse, ev.clientX, canvas.clientHeight - ev.clientY, 0.0); if (!mousePressed || !iDirection) { return; } tt.angle.horizontal += 0.0001 * (tt.w/2 - ev.clientX); tt.angle.vertical += 0.0001 * (tt.h/2 - ev.clientY); tt.direction = [Math.cos(tt.angle.vertical) * Math.sin(tt.angle.horizontal), Math.sin(tt.angle.vertical), Math.cos(tt.angle.vertical) * Math.cos(tt.angle.horizontal)]; tt.right = [Math.sin(tt.angle.horizontal - Math.PI/2), 0, Math.cos(tt.angle.horizontal - Math.PI/2)]; gl.uniform3f(iDirection, tt.direction[0], tt.direction[1], tt.direction[2]); requestAnimationFrame(tt.render); }); window.addEventListener('keydown', function(ev) { if (document.activeElement != document.body || ev.ctrlKey || ev.altKey) { return; } var origin = tt.gl.getUniform(tt.program, tt.origin); switch (ev.keyCode) { case 38: // Up case 87: // W origin = [origin[0]+tt.direction[0], origin[1]+tt.direction[1], origin[2]+tt.direction[2]]; break; case 37: // Left case 65: // A origin = [origin[0]-tt.right[0], origin[1]-tt.right[1], origin[2]-tt.right[2]]; break; case 40: // Down case 83: // S origin = [origin[0]-tt.direction[0], origin[1]-tt.direction[1], origin[2]-tt.direction[2]]; break; case 39: // Right case 68: // D origin = [origin[0]+tt.right[0], origin[1]+tt.right[1], origin[2]+tt.right[2]]; break; default: return } ev.preventDefault(); gl.uniform3f(tt.origin, origin[0], origin[1], origin[2]); requestAnimationFrame(tt.render); }); window.onresize = function(ev) { tt.w = tt.canvas.width = window.innerWidth; tt.h = tt.canvas.height = window.innerHeight; gl.viewport(0, 0, tt.w, tt.h); gl.uniform2f(iResolution, tt.w, tt.h); tt.render(); }; tt.render(); if (tt.playing) { tt.draw(); } return tt; } var canvas = document.createElement("canvas"); document.body.appendChild(canvas); var tt = TwoTriangles(canvas, files.open('default.frag').content); var sidebarEl = document.createElement("div"); sidebarEl.id = "sidebar"; document.body.appendChild(sidebarEl); var sliders = findSliders(tt.fragmentShaderSrc); initSliders(tt.gl, tt.program, sliders, function(ev) { requestAnimationFrame(tt.render); }); addSliders(sidebarEl, sliders); sidebarEl.appendChild(tt.playingControls); tt.render(); var editor = {}; editor.el = document.createElement("textarea"); editor.el.value = tt.fragmentShaderSrc; editor.el.onkeydown = function(ev) { try { if (ev.ctrlKey && ev.keyCode == 13) { tt = TwoTriangles(canvas, editor.el.value, {playing: tt.playing}); sidebarEl.innerHTML = ""; sliders = findSliders(tt.fragmentShaderSrc); initSliders(tt.gl, tt.program, sliders, function(ev) { // rerender if not in animation mode (otherwise `tt.draw` will already do that) if (!tt.playing) { requestAnimationFrame(tt.render); } }); addSliders(sidebarEl, sliders); sidebarEl.appendChild(tt.playingControls); tt.render(); clearError(); } } catch (e) { displayError(e); } } editor.toggle = function() { if (editor.visible) { editor.container.style.display = "none"; } else { editor.container.style.display = "inherit"; editor.el.focus(); } editor.visible = !editor.visible; } window.addEventListener('keydown', function(ev) { if (ev.ctrlKey && ev.keyCode == 69) { // Ctrl + e ev.preventDefault(); editor.toggle(); } }); editor.ui = files.setupUI(editor.el); editor.container = document.createElement("div"); editor.container.id = "editor"; editor.visible = false; editor.container.style.display = "none"; editor.container.appendChild(editor.ui); editor.container.appendChild(editor.el); document.body.appendChild(editor.container); } catch (e) { displayError(e); }