├── .babelrc ├── .gitignore ├── README.md ├── a-chain ├── aframe-orbit-controls-component.min.js ├── aframe-v0.8.2.min.js ├── index.html └── script.js ├── boilerplate.js ├── docs ├── OrbitControls.js ├── bundle.js ├── glsl-bumpy-sphere │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── glsl-cubes │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── glsl-juliafractal │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── glsl-just-a-cube │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── glsl-kaleidoscope │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── glsl-mandelbrot │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── glsl-random-lines │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── glsl-spinning-color-wheel │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── glsl-squishydonutspin │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── glsl-very-basic │ ├── fragmentshader.glsl │ └── vertexshader.glsl ├── index.html ├── three.min.js └── threejs-icosphere-explode │ └── scene.js ├── fonts ├── fa-brands-400.ttf └── fa-brands.css ├── package.json ├── spinning-color-wheel ├── fragmentshader.glsl ├── index.html └── vertexshader.glsl ├── src ├── app.css ├── app.js ├── button.js ├── code.css ├── code.js ├── codebutton.css ├── codebutton.js ├── index.js ├── menubutton.css ├── menubutton.js ├── select.css ├── select.js ├── shader.css ├── shader.js └── threejs.js ├── style.css ├── webpack.config.js ├── yarn-error.log └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | modules: false 7 | } 8 | ], 9 | '@babel/preset-react' 10 | ], 11 | "plugins": [ 12 | '@babel/plugin-transform-runtime' 13 | ] 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shader Art 2 | 3 | ## Update 12/19/19 4 | I've created a whole new React-based viewer for these shaders. The links below will now take you to the code for each project. 5 | 6 | * 12/29/19 - [Icosphere Explode](https://github.com/captainpainway/shader-art/tree/master/docs/threejs-icosphere-explode): Experimenting with vertex and fragment shaders in Three.js. Click and drag the mouse to explode the icosphere. 7 | * 8/1/18 - [Random Lines](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-random-lines): Random lines. 8 | * 7/30/18 - [Bumpy Sphere](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-bumpy-sphere): Signed distance function ray marching. Time-based displacement and rotation with phong shading. 9 | * 7/29/18 - [Spinning Color Wheel](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-spinning-color-wheel): HSB color wheel, masked into a 2D donut, with time-based rotation. 10 | * 7/27/18 - [Very Basic](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-very-basic): Basic half-screen linear gradients. 11 | * 7/24/18 - [Just A Cube](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-just-a-cube): Just a cube. 12 | * 7/22/18 - [Julia Fractal](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-juliafractal): A time-based Julia fractal based off the Mandelbrot fractal. 13 | * 7/21/18 - [Mandelbrot](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-mandelbrot): A simple Mandelbrot fractal. 14 | * 7/20/18 - [A-Chain](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-a-chain): An experiment in using GLSL shaders with A-Frame/three.js. 15 | * 7/18/18 - [Squishy Donut Spin](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-squishydonutspin): Ray marching with signed distance functions. Twisting and rotating a primitive based on time. 16 | * 7/16/18 - [Kaleidoscope](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-kaleidoscope): Time-based constructive solid geometry and shading. 17 | * 7/15/18 - [Cubes](https://github.com/captainpainway/shader-art/tree/master/docs/glsl-cubes): Experimenting with ray marching, signed distance functions, phong shading, repetition, and camera movement. 18 | -------------------------------------------------------------------------------- /a-chain/aframe-orbit-controls-component.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function e(i){if(s[i])return s[i].exports;var a=s[i]={exports:{},id:i,loaded:!1};return t[i].call(a.exports,a,a.exports,e),a.loaded=!0,a.exports}var s={};return e.m=t,e.c=s,e.p="",e(0)}([function(t,e){if("undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");var s=THREE.Math.radToDeg;AFRAME.registerComponent("orbit-controls",{dependencies:["position","rotation"],schema:{enabled:{default:!0},target:{default:""},distance:{default:1},enableRotate:{default:!0},rotateSpeed:{default:1},enableZoom:{default:!0},zoomSpeed:{default:1},enablePan:{default:!0},keyPanSpeed:{default:7},enableDamping:{default:!1},dampingFactor:{default:.25},autoRotate:{default:!1},autoRotateSpeed:{default:2},enableKeys:{default:!0},minAzimuthAngle:{default:-(1/0)},maxAzimuthAngle:{default:1/0},minPolarAngle:{default:0},maxPolarAngle:{default:Math.PI},minZoom:{default:0},maxZoom:{default:1/0},invertZoom:{default:!1},minDistance:{default:0},maxDistance:{default:1/0},rotateTo:{type:"vec3",default:{x:0,y:0,z:0}},rotateToSpeed:{type:"number",default:.05},logPosition:{type:"boolean",default:!1},autoVRLookCam:{type:"boolean",default:!0}},multiple:!1,init:function(){this.sceneEl=this.el.sceneEl,this.object=this.el.object3D,this.target=this.sceneEl.querySelector(this.data.target).object3D.position,console.log("enabled: ",this.data.enabled),this.isRunning=!1,this.lookControls=null,this.data.autoVRLookCam&&(this.el.components["look-controls"]?this.lookControls=this.el.components["look-controls"]:(this.el.setAttribute("look-controls",""),this.lookControls=this.el.components["look-controls"]),this.lookControls.pause(),this.el.sceneEl.addEventListener("enter-vr",this.onEnterVR.bind(this),!1),this.el.sceneEl.addEventListener("exit-vr",this.onExitVR.bind(this),!1)),this.dolly=new THREE.Object3D,this.dolly.position.copy(this.object.position),this.savedPose=null,this.STATE={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_DOLLY:4,TOUCH_PAN:5,ROTATE_TO:6},this.state=this.STATE.NONE,this.EPS=1e-6,this.lastPosition=new THREE.Vector3,this.lastQuaternion=new THREE.Quaternion,this.spherical=new THREE.Spherical,this.sphericalDelta=new THREE.Spherical,this.scale=1,this.zoomChanged=!1,this.rotateStart=new THREE.Vector2,this.rotateEnd=new THREE.Vector2,this.rotateDelta=new THREE.Vector2,this.panStart=new THREE.Vector2,this.panEnd=new THREE.Vector2,this.panDelta=new THREE.Vector2,this.panOffset=new THREE.Vector3,this.dollyStart=new THREE.Vector2,this.dollyEnd=new THREE.Vector2,this.dollyDelta=new THREE.Vector2,this.vector=new THREE.Vector3,this.desiredPosition=new THREE.Vector3,this.mouseButtons={ORBIT:THREE.MOUSE.LEFT,ZOOM:THREE.MOUSE.MIDDLE,PAN:THREE.MOUSE.RIGHT},this.keys={LEFT:37,UP:38,RIGHT:39,BOTTOM:40},this.bindMethods()},update:function(t){if(console.log("component update"),this.data.rotateTo){var e=new THREE.Vector3(this.data.rotateTo.x,this.data.rotateTo.y,this.data.rotateTo.z);this.desiredPosition.equals(e)||(this.desiredPosition.copy(e),this.rotateTo(this.desiredPosition))}this.dolly.position.copy(this.object.position),this.updateView(!0)},remove:function(){this.isRunning=!1,this.removeEventListeners(),this.el.sceneEl.removeEventListener("enter-vr",this.onEnterVR,!1),this.el.sceneEl.removeEventListener("exit-vr",this.onExitVR,!1)},tick:function(t){var e=!(!this.data.enabled||!this.isRunning)&&this.updateView();e===!0&&this.data.logPosition===!0&&console.log(this.el.object3D.position)},onEnterVR:function(t){this.saveCameraPose(),this.el.setAttribute("position",{x:0,y:2,z:5}),this.el.setAttribute("rotation",{x:0,y:0,z:0}),this.pause(),this.lookControls.play(),this.data.autoRotate&&console.warn("orbit-controls: Sorry, autoRotate is not implemented in VR mode")},onExitVR:function(t){this.lookControls.pause(),this.play(),this.restoreCameraPose(),this.updateView(!0)},pause:function(){this.isRunning=!1,this.removeEventListeners()},play:function(){this.isRunning=!0;var t,e;this.object.traverse(function(s){s instanceof THREE.PerspectiveCamera?(t=s,e="PerspectiveCamera"):s instanceof THREE.OrthographicCamera&&(t=s,e="OrthographicCamera")}),this.camera=t,this.cameraType=e,this.sceneEl.addEventListener("renderstart",this.onRenderTargetLoaded,!1),this.lookControls&&this.lookControls.pause(),this.canvasEl&&this.addEventListeners()},onRenderTargetLoaded:function(){this.sceneEl.removeEventListener("renderstart",this.onRenderTargetLoaded,!1),this.canvasEl=this.sceneEl.canvas,this.addEventListeners()},bindMethods:function(){this.onRenderTargetLoaded=this.onRenderTargetLoaded.bind(this),this.onContextMenu=this.onContextMenu.bind(this),this.onMouseDown=this.onMouseDown.bind(this),this.onMouseWheel=this.onMouseWheel.bind(this),this.onMouseMove=this.onMouseMove.bind(this),this.onMouseUp=this.onMouseUp.bind(this),this.onTouchStart=this.onTouchStart.bind(this),this.onTouchMove=this.onTouchMove.bind(this),this.onTouchEnd=this.onTouchEnd.bind(this),this.onKeyDown=this.onKeyDown.bind(this)},addEventListeners:function(){this.canvasEl.addEventListener("contextmenu",this.onContextMenu,!1),this.canvasEl.addEventListener("mousedown",this.onMouseDown,!1),this.canvasEl.addEventListener("mousewheel",this.onMouseWheel,!1),this.canvasEl.addEventListener("MozMousePixelScroll",this.onMouseWheel,!1),this.canvasEl.addEventListener("touchstart",this.onTouchStart,!1),this.canvasEl.addEventListener("touchend",this.onTouchEnd,!1),this.canvasEl.addEventListener("touchmove",this.onTouchMove,!1),window.addEventListener("keydown",this.onKeyDown,!1)},removeEventListeners:function(){this.canvasEl&&(this.canvasEl.removeEventListener("contextmenu",this.onContextMenu,!1),this.canvasEl.removeEventListener("mousedown",this.onMouseDown,!1),this.canvasEl.removeEventListener("mousewheel",this.onMouseWheel,!1),this.canvasEl.removeEventListener("MozMousePixelScroll",this.onMouseWheel,!1),this.canvasEl.removeEventListener("touchstart",this.onTouchStart,!1),this.canvasEl.removeEventListener("touchend",this.onTouchEnd,!1),this.canvasEl.removeEventListener("touchmove",this.onTouchMove,!1),this.canvasEl.removeEventListener("mousemove",this.onMouseMove,!1),this.canvasEl.removeEventListener("mouseup",this.onMouseUp,!1),this.canvasEl.removeEventListener("mouseout",this.onMouseUp,!1)),window.removeEventListener("keydown",this.onKeyDown,!1)},onContextMenu:function(t){t.preventDefault()},onMouseDown:function(t){if(this.data.enabled&&this.isRunning){if(t.button===this.mouseButtons.ORBIT&&(t.shiftKey||t.ctrlKey)){if(this.data.enablePan===!1)return;this.handleMouseDownPan(t),this.state=this.STATE.PAN}else if(t.button===this.mouseButtons.ORBIT){if(this.panOffset.set(0,0,0),this.data.enableRotate===!1)return;this.handleMouseDownRotate(t),this.state=this.STATE.ROTATE}else if(t.button===this.mouseButtons.ZOOM){if(this.panOffset.set(0,0,0),this.data.enableZoom===!1)return;this.handleMouseDownDolly(t),this.state=this.STATE.DOLLY}else if(t.button===this.mouseButtons.PAN){if(this.data.enablePan===!1)return;this.handleMouseDownPan(t),this.state=this.STATE.PAN}this.state!==this.STATE.NONE&&(this.canvasEl.addEventListener("mousemove",this.onMouseMove,!1),this.canvasEl.addEventListener("mouseup",this.onMouseUp,!1),this.canvasEl.addEventListener("mouseout",this.onMouseUp,!1),this.el.emit("start-drag-orbit-controls",null,!1))}},onMouseMove:function(t){if(this.data.enabled&&this.isRunning)if(t.preventDefault(),this.state===this.STATE.ROTATE){if(this.data.enableRotate===!1)return;this.handleMouseMoveRotate(t)}else if(this.state===this.STATE.DOLLY){if(this.data.enableZoom===!1)return;this.handleMouseMoveDolly(t)}else if(this.state===this.STATE.PAN){if(this.data.enablePan===!1)return;this.handleMouseMovePan(t)}},onMouseUp:function(t){this.data.enabled&&this.isRunning&&this.state!==this.STATE.ROTATE_TO&&(t.preventDefault(),t.stopPropagation(),this.handleMouseUp(t),this.canvasEl.removeEventListener("mousemove",this.onMouseMove,!1),this.canvasEl.removeEventListener("mouseup",this.onMouseUp,!1),this.canvasEl.removeEventListener("mouseout",this.onMouseUp,!1),this.state=this.STATE.NONE,this.el.emit("end-drag-orbit-controls",null,!1))},onMouseWheel:function(t){this.data.enabled&&this.isRunning&&this.data.enableZoom!==!1&&(this.state===this.STATE.NONE||this.state===this.STATE.ROTATE)&&(t.preventDefault(),t.stopPropagation(),this.handleMouseWheel(t))},onTouchStart:function(t){if(this.data.enabled&&this.isRunning){switch(t.touches.length){case 1:if(this.data.enableRotate===!1)return;this.handleTouchStartRotate(t),this.state=this.STATE.TOUCH_ROTATE;break;case 2:if(this.data.enableZoom===!1)return;this.handleTouchStartDolly(t),this.state=this.STATE.TOUCH_DOLLY;break;case 3:if(this.data.enablePan===!1)return;this.handleTouchStartPan(t),this.state=this.STATE.TOUCH_PAN;break;default:this.state=this.STATE.NONE}this.state!==this.STATE.NONE&&this.el.emit("start-drag-orbit-controls",null,!1)}},onTouchMove:function(t){if(this.data.enabled&&this.isRunning)switch(t.preventDefault(),t.stopPropagation(),t.touches.length){case 1:if(this.enableRotate===!1)return;if(this.state!==this.STATE.TOUCH_ROTATE)return;this.handleTouchMoveRotate(t);break;case 2:if(this.data.enableZoom===!1)return;if(this.state!==this.STATE.TOUCH_DOLLY)return;this.handleTouchMoveDolly(t);break;case 3:if(this.data.enablePan===!1)return;if(this.state!==this.STATE.TOUCH_PAN)return;this.handleTouchMovePan(t);break;default:this.state=this.STATE.NONE}},onTouchEnd:function(t){this.data.enabled&&this.isRunning&&(this.handleTouchEnd(t),this.el.emit("end-drag-orbit-controls",null,!1),this.state=this.STATE.NONE)},onKeyDown:function(t){this.data.enabled&&this.isRunning&&this.data.enableKeys!==!1&&this.data.enablePan!==!1&&this.handleKeyDown(t)},handleMouseDownRotate:function(t){this.rotateStart.set(t.clientX,t.clientY)},handleMouseDownDolly:function(t){this.dollyStart.set(t.clientX,t.clientY)},handleMouseDownPan:function(t){this.panStart.set(t.clientX,t.clientY)},handleMouseMoveRotate:function(t){this.rotateEnd.set(t.clientX,t.clientY),this.rotateDelta.subVectors(this.rotateEnd,this.rotateStart);var e=this.canvasEl===document?this.canvasEl.body:this.canvasEl;this.rotateLeft(2*Math.PI*this.rotateDelta.x/e.clientWidth*this.data.rotateSpeed),this.rotateUp(2*Math.PI*this.rotateDelta.y/e.clientHeight*this.data.rotateSpeed),this.rotateStart.copy(this.rotateEnd),this.updateView()},handleMouseMoveDolly:function(t){this.dollyEnd.set(t.clientX,t.clientY),this.dollyDelta.subVectors(this.dollyEnd,this.dollyStart),this.dollyDelta.y>0?this.data.invertZoom?this.dollyOut(this.getZoomScale()):this.dollyIn(this.getZoomScale()):this.dollyDelta.y<0&&(this.data.invertZoom?this.dollyIn(this.getZoomScale()):this.dollyOut(this.getZoomScale())),this.dollyStart.copy(this.dollyEnd),this.updateView()},handleMouseMovePan:function(t){this.panEnd.set(t.clientX,t.clientY),this.panDelta.subVectors(this.panEnd,this.panStart),this.pan(this.panDelta.x,this.panDelta.y),this.panStart.copy(this.panEnd),this.updateView()},handleMouseUp:function(t){},handleMouseWheel:function(t){var e=0;void 0!==t.wheelDelta?e=t.wheelDelta:void 0!==t.detail&&(e=-t.detail),e>0?this.data.invertZoom?this.dollyIn(this.getZoomScale()):this.dollyOut(this.getZoomScale()):e<0&&(this.data.invertZoom?this.dollyOut(this.getZoomScale()):this.dollyIn(this.getZoomScale())),this.updateView()},handleTouchStartRotate:function(t){this.rotateStart.set(t.touches[0].pageX,t.touches[0].pageY)},handleTouchStartDolly:function(t){var e=t.touches[0].pageX-t.touches[1].pageX,s=t.touches[0].pageY-t.touches[1].pageY,i=Math.sqrt(e*e+s*s);this.dollyStart.set(0,i)},handleTouchStartPan:function(t){this.panStart.set(t.touches[0].pageX,t.touches[0].pageY)},handleTouchMoveRotate:function(t){this.rotateEnd.set(t.touches[0].pageX,t.touches[0].pageY),this.rotateDelta.subVectors(this.rotateEnd,this.rotateStart);var e=this.canvasEl===document?this.canvasEl.body:this.canvasEl;this.rotateLeft(2*Math.PI*this.rotateDelta.x/e.clientWidth*this.data.rotateSpeed),this.rotateUp(2*Math.PI*this.rotateDelta.y/e.clientHeight*this.data.rotateSpeed),this.rotateStart.copy(this.rotateEnd),this.updateView()},handleTouchMoveDolly:function(t){var e=t.touches[0].pageX-t.touches[1].pageX,s=t.touches[0].pageY-t.touches[1].pageY,i=Math.sqrt(e*e+s*s);this.dollyEnd.set(0,i),this.dollyDelta.subVectors(this.dollyEnd,this.dollyStart),this.dollyDelta.y>0?this.dollyIn(this.getZoomScale()):this.dollyDelta.y<0&&this.dollyOut(this.getZoomScale()),this.dollyStart.copy(this.dollyEnd),this.updateView()},handleTouchMovePan:function(t){this.panEnd.set(t.touches[0].pageX,t.touches[0].pageY),this.panDelta.subVectors(this.panEnd,this.panStart),this.pan(this.panDelta.x,this.panDelta.y),this.panStart.copy(this.panEnd),this.updateView()},handleTouchEnd:function(t){},handleKeyDown:function(t){switch(t.keyCode){case this.keys.UP:this.pan(0,this.data.keyPanSpeed),this.updateView();break;case this.keys.BOTTOM:this.pan(0,-this.data.keyPanSpeed),this.updateView();break;case this.keys.LEFT:this.pan(this.data.keyPanSpeed,0),this.updateView();break;case this.keys.RIGHT:this.pan(-this.data.keyPanSpeed,0),this.updateView()}},getAutoRotationAngle:function(){return 2*Math.PI/60/60*this.data.autoRotateSpeed},getZoomScale:function(){return Math.pow(.95,this.data.zoomSpeed)},rotateLeft:function(t){this.sphericalDelta.theta-=t},rotateUp:function(t){this.sphericalDelta.phi-=t},rotateTo:function(t){this.state=this.STATE.ROTATE_TO,this.desiredPosition.copy(t)},panHorizontally:function(t,e){var s=new THREE.Vector3;s.setFromMatrixColumn(e,0),s.multiplyScalar(-t),this.panOffset.add(s)},panVertically:function(t,e){var s=new THREE.Vector3;s.setFromMatrixColumn(e,1),s.multiplyScalar(t),this.panOffset.add(s)},pan:function(t,e){var s=new THREE.Vector3,i=this.canvasEl===document?this.canvasEl.body:this.canvasEl;if("PerspectiveCamera"===this.cameraType){var a=this.dolly.position;s.copy(a).sub(this.target);var o=s.length();o*=Math.tan(this.camera.fov/2*Math.PI/180),this.panHorizontally(2*t*o/i.clientHeight,this.object.matrix),this.panVertically(2*e*o/i.clientHeight,this.object.matrix)}else"OrthographicCamera"===this.cameraType?(this.panHorizontally(t*(this.dolly.right-this.dolly.left)/this.camera.zoom/i.clientWidth,this.object.matrix),this.panVertically(e*(this.dolly.top-this.dolly.bottom)/this.camera.zoom/i.clientHeight,this.object.matrix)):(console.warn("Trying to pan: WARNING: Orbit Controls encountered an unknown camera type - pan disabled."),this.data.enablePan=!1)},dollyIn:function(t){"PerspectiveCamera"===this.cameraType?this.scale*=t:"OrthographicCamera"===this.cameraType?(this.camera.zoom=Math.max(this.data.minZoom,Math.min(this.data.maxZoom,this.camera.zoom*t)),this.camera.updateProjectionMatrix(),this.zoomChanged=!0):(console.warn("Trying to dolly in: WARNING: Orbit Controls encountered an unknown camera type - dolly/zoom disabled."),this.data.enableZoom=!1)},dollyOut:function(t){"PerspectiveCamera"===this.cameraType?this.scale/=t:"OrthographicCamera"===this.cameraType?(this.camera.zoom=Math.max(this.data.minZoom,Math.min(this.data.maxZoom,this.camera.zoom/t)),this.camera.updateProjectionMatrix(),this.zoomChanged=!0):(console.warn("Trying to dolly out: WARNING: Orbit Controls encountered an unknown camera type - dolly/zoom disabled."),this.data.enableZoom=!1)},lookAtTarget:function(t,e){var s=new THREE.Vector3;s.subVectors(t.position,e).add(t.position),t.lookAt(s)},saveCameraPose:function(){this.savedPose||(this.savedPose={position:this.dolly.position,rotation:this.dolly.rotation})},restoreCameraPose:function(){this.savedPose&&(this.dolly.position.copy(this.savedPose.position),this.dolly.rotation.copy(this.savedPose.rotation),this.savedPose=null)},updateView:function(t){if(this.desiredPosition&&this.state===this.STATE.ROTATE_TO){var e=new THREE.Spherical;e.setFromVector3(this.desiredPosition);var i=e.phi-this.spherical.phi,a=e.theta-this.spherical.theta;this.sphericalDelta.set(this.spherical.radius,i*this.data.rotateToSpeed,a*this.data.rotateToSpeed)}var o=new THREE.Vector3,n=(new THREE.Quaternion).setFromUnitVectors(this.dolly.up,new THREE.Vector3(0,1,0)),h=n.clone().inverse();if(o.copy(this.dolly.position).sub(this.target),o.applyQuaternion(n),this.spherical.setFromVector3(o),this.data.autoRotate&&this.state===this.STATE.NONE&&this.rotateLeft(this.getAutoRotationAngle()),this.spherical.theta+=this.sphericalDelta.theta,this.spherical.phi+=this.sphericalDelta.phi,this.spherical.theta=Math.max(this.data.minAzimuthAngle,Math.min(this.data.maxAzimuthAngle,this.spherical.theta)),this.spherical.phi=Math.max(this.data.minPolarAngle,Math.min(this.data.maxPolarAngle,this.spherical.phi)),this.spherical.makeSafe(),this.spherical.radius*=this.scale,this.spherical.radius=Math.max(this.data.minDistance,Math.min(this.data.maxDistance,this.spherical.radius)),this.target.add(this.panOffset),o.setFromSpherical(this.spherical),o.applyQuaternion(h),this.dolly.position.copy(this.target).add(o),this.target&&this.lookAtTarget(this.dolly,this.target),this.data.enableDamping===!0?(this.sphericalDelta.theta*=1-this.data.dampingFactor,this.sphericalDelta.phi*=1-this.data.dampingFactor):this.sphericalDelta.set(0,0,0),this.scale=1,this.panOffset.set(0,0,0),t===!0||this.zoomChanged||this.lastPosition.distanceToSquared(this.dolly.position)>this.EPS||8*(1-this.lastQuaternion.dot(this.dolly.quaternion))>this.EPS){var l=this.calculateHMDQuaternion(),r=new THREE.Euler;return r.setFromQuaternion(l,"YXZ"),this.el.setAttribute("position",{x:this.dolly.position.x,y:this.dolly.position.y,z:this.dolly.position.z}),this.el.setAttribute("rotation",{x:s(r.x),y:s(r.y),z:s(r.z)}),this.lastPosition.copy(this.dolly.position),this.lastQuaternion.copy(this.dolly.quaternion),this.zoomChanged=!1,!0}return!1},calculateHMDQuaternion:function(){var t=new THREE.Quaternion;return function(){return t.copy(this.dolly.quaternion),t}}()})}]); -------------------------------------------------------------------------------- /a-chain/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A-Frame WebGL Test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /a-chain/script.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerShader('my-custom', { 2 | schema: { 3 | color: {type: 'color', is: 'uniform'} 4 | }, 5 | raw: false, 6 | vertexShader: 7 | `varying vec2 vUv; 8 | 9 | void main() { 10 | vUv = uv; 11 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 12 | }`, 13 | fragmentShader: 14 | `varying vec2 vUv; 15 | uniform vec3 color; 16 | 17 | void main() { 18 | gl_FragColor = vec4(color, 1.0); 19 | }` 20 | }); 21 | -------------------------------------------------------------------------------- /boilerplate.js: -------------------------------------------------------------------------------- 1 | window.requestAnimationFrame = window.requestAnimationFrame || (() => { 2 | return window.webkitRequestAnimationFrame || 3 | window.mozRequestAnimationFrame || 4 | window.oRequestAnimationFrame || 5 | window.msRequestAnimationFrame || 6 | function (callback) { 7 | window.setTimeout(callback, 1000/60); 8 | } 9 | }); 10 | 11 | let canvas, 12 | gl, 13 | buffer, 14 | vertex_shader, 15 | fragment_shader, 16 | currentProgram, 17 | vertex_position, 18 | timeLocation, 19 | resolutionLocation, 20 | parameters = { 21 | start_time: new Date().getTime(), 22 | time: 0, 23 | screenWidth: 0, 24 | screenHeight: 0 25 | }; 26 | 27 | init(); 28 | 29 | async function importShader(shader) { 30 | let response = await fetch(shader); 31 | let data = await response.text(); 32 | return data; 33 | } 34 | 35 | async function init() { 36 | fragment_shader = await importShader('fragmentshader.glsl'); 37 | vertex_shader = await importShader('vertexshader.glsl'); 38 | 39 | canvas = document.querySelector('canvas'); 40 | 41 | try { 42 | gl = canvas.getContext('experimental-webgl'); 43 | } catch(err) {} 44 | 45 | if(!gl) { 46 | throw "Cannot create WebGL context." 47 | } 48 | 49 | buffer = gl.createBuffer(); 50 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 51 | gl.bufferData(gl.ARRAY_BUFFER, 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]), gl.STATIC_DRAW); 52 | 53 | currentProgram = createProgram(vertex_shader, fragment_shader); 54 | 55 | timeLocation = gl.getUniformLocation(currentProgram, 'time'); 56 | resolutionLocation = gl.getUniformLocation(currentProgram, 'resolution'); 57 | 58 | animate(); 59 | } 60 | 61 | function createProgram(vertex, fragment) { 62 | const program = gl.createProgram(), 63 | vert_shader = createShader(vertex, gl.VERTEX_SHADER), 64 | frag_shader = createShader(fragment, gl.FRAGMENT_SHADER); 65 | 66 | if (vert_shader === null || frag_shader === null) { 67 | return null; 68 | } 69 | 70 | gl.attachShader(program, vert_shader); 71 | gl.attachShader(program, frag_shader); 72 | 73 | gl.deleteShader(vert_shader); 74 | gl.deleteShader(frag_shader); 75 | 76 | gl.linkProgram(program); 77 | 78 | return program; 79 | } 80 | 81 | function createShader(src, type) { 82 | const shader = gl.createShader(type); 83 | gl.shaderSource(shader, src); 84 | gl.compileShader(shader); 85 | 86 | if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 87 | console.log((type == gl.VERTEX_SHADER ? "VERTEX" : "FRAGMENT") + " SHADER:\n" + gl.getShaderInfoLog(shader)); 88 | return null; 89 | } 90 | 91 | return shader; 92 | } 93 | 94 | function resize() { 95 | if(canvas.width != canvas.clientWidth || canvas.height != canvas.clientHeight) { 96 | canvas.width = canvas.clientWidth; 97 | canvas.height = canvas.clientHeight; 98 | parameters.screenWidth = canvas.width; 99 | parameters.screenHeight = canvas.height; 100 | gl.viewport(0, 0, canvas.width, canvas.height); 101 | } 102 | } 103 | 104 | function animate() { 105 | resize(); 106 | render(); 107 | requestAnimationFrame(animate); 108 | } 109 | 110 | function render() { 111 | if(!currentProgram) { 112 | return; 113 | } 114 | 115 | parameters.time = new Date().getTime() - parameters.start_time; 116 | 117 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 118 | gl.useProgram(currentProgram); 119 | gl.uniform1f(timeLocation, parameters.time / 1000); 120 | gl.uniform2f(resolutionLocation, parameters.screenWidth, parameters.screenHeight); 121 | 122 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 123 | gl.vertexAttribPointer(vertex_position, 2, gl.FLOAT, false, 0, 0); 124 | gl.enableVertexAttribArray(vertex_position); 125 | gl.drawArrays(gl.TRIANGLES, 0, 6); 126 | gl.disableVertexAttribArray(vertex_position); 127 | } 128 | -------------------------------------------------------------------------------- /docs/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | * @author ScieCode / http://github.com/sciecode 8 | */ 9 | 10 | // This set of controls performs orbiting, dollying (zooming), and panning. 11 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 12 | // 13 | // Orbit - left mouse / touch: one-finger move 14 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 15 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move 16 | 17 | THREE.OrbitControls = function ( object, domElement ) { 18 | 19 | if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); 20 | if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); 21 | 22 | this.object = object; 23 | this.domElement = domElement; 24 | 25 | // Set to false to disable this control 26 | this.enabled = true; 27 | 28 | // "target" sets the location of focus, where the object orbits around 29 | this.target = new THREE.Vector3(); 30 | 31 | // How far you can dolly in and out ( PerspectiveCamera only ) 32 | this.minDistance = 0; 33 | this.maxDistance = Infinity; 34 | 35 | // How far you can zoom in and out ( OrthographicCamera only ) 36 | this.minZoom = 0; 37 | this.maxZoom = Infinity; 38 | 39 | // How far you can orbit vertically, upper and lower limits. 40 | // Range is 0 to Math.PI radians. 41 | this.minPolarAngle = 0; // radians 42 | this.maxPolarAngle = Math.PI; // radians 43 | 44 | // How far you can orbit horizontally, upper and lower limits. 45 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 46 | this.minAzimuthAngle = - Infinity; // radians 47 | this.maxAzimuthAngle = Infinity; // radians 48 | 49 | // Set to true to enable damping (inertia) 50 | // If damping is enabled, you must call controls.update() in your animation loop 51 | this.enableDamping = false; 52 | this.dampingFactor = 0.05; 53 | 54 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 55 | // Set to false to disable zooming 56 | this.enableZoom = true; 57 | this.zoomSpeed = 1.0; 58 | 59 | // Set to false to disable rotating 60 | this.enableRotate = true; 61 | this.rotateSpeed = 1.0; 62 | 63 | // Set to false to disable panning 64 | this.enablePan = true; 65 | this.panSpeed = 1.0; 66 | this.screenSpacePanning = false; // if true, pan in screen-space 67 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 68 | 69 | // Set to true to automatically rotate around the target 70 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 71 | this.autoRotate = false; 72 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 73 | 74 | // Set to false to disable use of the keys 75 | this.enableKeys = true; 76 | 77 | // The four arrow keys 78 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 79 | 80 | // Mouse buttons 81 | this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; 82 | 83 | // Touch fingers 84 | this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }; 85 | 86 | // for reset 87 | this.target0 = this.target.clone(); 88 | this.position0 = this.object.position.clone(); 89 | this.zoom0 = this.object.zoom; 90 | 91 | // 92 | // public methods 93 | // 94 | 95 | this.getPolarAngle = function () { 96 | 97 | return spherical.phi; 98 | 99 | }; 100 | 101 | this.getAzimuthalAngle = function () { 102 | 103 | return spherical.theta; 104 | 105 | }; 106 | 107 | this.saveState = function () { 108 | 109 | scope.target0.copy( scope.target ); 110 | scope.position0.copy( scope.object.position ); 111 | scope.zoom0 = scope.object.zoom; 112 | 113 | }; 114 | 115 | this.reset = function () { 116 | 117 | scope.target.copy( scope.target0 ); 118 | scope.object.position.copy( scope.position0 ); 119 | scope.object.zoom = scope.zoom0; 120 | 121 | scope.object.updateProjectionMatrix(); 122 | scope.dispatchEvent( changeEvent ); 123 | 124 | scope.update(); 125 | 126 | state = STATE.NONE; 127 | 128 | }; 129 | 130 | // this method is exposed, but perhaps it would be better if we can make it private... 131 | this.update = function () { 132 | 133 | var offset = new THREE.Vector3(); 134 | 135 | // so camera.up is the orbit axis 136 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 137 | var quatInverse = quat.clone().inverse(); 138 | 139 | var lastPosition = new THREE.Vector3(); 140 | var lastQuaternion = new THREE.Quaternion(); 141 | 142 | return function update() { 143 | 144 | var position = scope.object.position; 145 | 146 | offset.copy( position ).sub( scope.target ); 147 | 148 | // rotate offset to "y-axis-is-up" space 149 | offset.applyQuaternion( quat ); 150 | 151 | // angle from z-axis around y-axis 152 | spherical.setFromVector3( offset ); 153 | 154 | if ( scope.autoRotate && state === STATE.NONE ) { 155 | 156 | rotateLeft( getAutoRotationAngle() ); 157 | 158 | } 159 | 160 | if ( scope.enableDamping ) { 161 | 162 | spherical.theta += sphericalDelta.theta * scope.dampingFactor; 163 | spherical.phi += sphericalDelta.phi * scope.dampingFactor; 164 | 165 | } else { 166 | 167 | spherical.theta += sphericalDelta.theta; 168 | spherical.phi += sphericalDelta.phi; 169 | 170 | } 171 | 172 | // restrict theta to be between desired limits 173 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 174 | 175 | // restrict phi to be between desired limits 176 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 177 | 178 | spherical.makeSafe(); 179 | 180 | 181 | spherical.radius *= scale; 182 | 183 | // restrict radius to be between desired limits 184 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 185 | 186 | // move target to panned location 187 | 188 | if ( scope.enableDamping === true ) { 189 | 190 | scope.target.addScaledVector( panOffset, scope.dampingFactor ); 191 | 192 | } else { 193 | 194 | scope.target.add( panOffset ); 195 | 196 | } 197 | 198 | offset.setFromSpherical( spherical ); 199 | 200 | // rotate offset back to "camera-up-vector-is-up" space 201 | offset.applyQuaternion( quatInverse ); 202 | 203 | position.copy( scope.target ).add( offset ); 204 | 205 | scope.object.lookAt( scope.target ); 206 | 207 | if ( scope.enableDamping === true ) { 208 | 209 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 210 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 211 | 212 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 213 | 214 | } else { 215 | 216 | sphericalDelta.set( 0, 0, 0 ); 217 | 218 | panOffset.set( 0, 0, 0 ); 219 | 220 | } 221 | 222 | scale = 1; 223 | 224 | // update condition is: 225 | // min(camera displacement, camera rotation in radians)^2 > EPS 226 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 227 | 228 | if ( zoomChanged || 229 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 230 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 231 | 232 | scope.dispatchEvent( changeEvent ); 233 | 234 | lastPosition.copy( scope.object.position ); 235 | lastQuaternion.copy( scope.object.quaternion ); 236 | zoomChanged = false; 237 | 238 | return true; 239 | 240 | } 241 | 242 | return false; 243 | 244 | }; 245 | 246 | }(); 247 | 248 | this.dispose = function () { 249 | 250 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 251 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 252 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 253 | 254 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 255 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 256 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 257 | 258 | document.removeEventListener( 'mousemove', onMouseMove, false ); 259 | document.removeEventListener( 'mouseup', onMouseUp, false ); 260 | 261 | scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); 262 | 263 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 264 | 265 | }; 266 | 267 | // 268 | // internals 269 | // 270 | 271 | var scope = this; 272 | 273 | var changeEvent = { type: 'change' }; 274 | var startEvent = { type: 'start' }; 275 | var endEvent = { type: 'end' }; 276 | 277 | var STATE = { 278 | NONE: - 1, 279 | ROTATE: 0, 280 | DOLLY: 1, 281 | PAN: 2, 282 | TOUCH_ROTATE: 3, 283 | TOUCH_PAN: 4, 284 | TOUCH_DOLLY_PAN: 5, 285 | TOUCH_DOLLY_ROTATE: 6 286 | }; 287 | 288 | var state = STATE.NONE; 289 | 290 | var EPS = 0.000001; 291 | 292 | // current position in spherical coordinates 293 | var spherical = new THREE.Spherical(); 294 | var sphericalDelta = new THREE.Spherical(); 295 | 296 | var scale = 1; 297 | var panOffset = new THREE.Vector3(); 298 | var zoomChanged = false; 299 | 300 | var rotateStart = new THREE.Vector2(); 301 | var rotateEnd = new THREE.Vector2(); 302 | var rotateDelta = new THREE.Vector2(); 303 | 304 | var panStart = new THREE.Vector2(); 305 | var panEnd = new THREE.Vector2(); 306 | var panDelta = new THREE.Vector2(); 307 | 308 | var dollyStart = new THREE.Vector2(); 309 | var dollyEnd = new THREE.Vector2(); 310 | var dollyDelta = new THREE.Vector2(); 311 | 312 | function getAutoRotationAngle() { 313 | 314 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 315 | 316 | } 317 | 318 | function getZoomScale() { 319 | 320 | return Math.pow( 0.95, scope.zoomSpeed ); 321 | 322 | } 323 | 324 | function rotateLeft( angle ) { 325 | 326 | sphericalDelta.theta -= angle; 327 | 328 | } 329 | 330 | function rotateUp( angle ) { 331 | 332 | sphericalDelta.phi -= angle; 333 | 334 | } 335 | 336 | var panLeft = function () { 337 | 338 | var v = new THREE.Vector3(); 339 | 340 | return function panLeft( distance, objectMatrix ) { 341 | 342 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 343 | v.multiplyScalar( - distance ); 344 | 345 | panOffset.add( v ); 346 | 347 | }; 348 | 349 | }(); 350 | 351 | var panUp = function () { 352 | 353 | var v = new THREE.Vector3(); 354 | 355 | return function panUp( distance, objectMatrix ) { 356 | 357 | if ( scope.screenSpacePanning === true ) { 358 | 359 | v.setFromMatrixColumn( objectMatrix, 1 ); 360 | 361 | } else { 362 | 363 | v.setFromMatrixColumn( objectMatrix, 0 ); 364 | v.crossVectors( scope.object.up, v ); 365 | 366 | } 367 | 368 | v.multiplyScalar( distance ); 369 | 370 | panOffset.add( v ); 371 | 372 | }; 373 | 374 | }(); 375 | 376 | // deltaX and deltaY are in pixels; right and down are positive 377 | var pan = function () { 378 | 379 | var offset = new THREE.Vector3(); 380 | 381 | return function pan( deltaX, deltaY ) { 382 | 383 | var element = scope.domElement; 384 | 385 | if ( scope.object.isPerspectiveCamera ) { 386 | 387 | // perspective 388 | var position = scope.object.position; 389 | offset.copy( position ).sub( scope.target ); 390 | var targetDistance = offset.length(); 391 | 392 | // half of the fov is center to top of screen 393 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 394 | 395 | // we use only clientHeight here so aspect ratio does not distort speed 396 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 397 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 398 | 399 | } else if ( scope.object.isOrthographicCamera ) { 400 | 401 | // orthographic 402 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 403 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 404 | 405 | } else { 406 | 407 | // camera neither orthographic nor perspective 408 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 409 | scope.enablePan = false; 410 | 411 | } 412 | 413 | }; 414 | 415 | }(); 416 | 417 | function dollyIn( dollyScale ) { 418 | 419 | if ( scope.object.isPerspectiveCamera ) { 420 | 421 | scale /= dollyScale; 422 | 423 | } else if ( scope.object.isOrthographicCamera ) { 424 | 425 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 426 | scope.object.updateProjectionMatrix(); 427 | zoomChanged = true; 428 | 429 | } else { 430 | 431 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 432 | scope.enableZoom = false; 433 | 434 | } 435 | 436 | } 437 | 438 | function dollyOut( dollyScale ) { 439 | 440 | if ( scope.object.isPerspectiveCamera ) { 441 | 442 | scale *= dollyScale; 443 | 444 | } else if ( scope.object.isOrthographicCamera ) { 445 | 446 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 447 | scope.object.updateProjectionMatrix(); 448 | zoomChanged = true; 449 | 450 | } else { 451 | 452 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 453 | scope.enableZoom = false; 454 | 455 | } 456 | 457 | } 458 | 459 | // 460 | // event callbacks - update the object state 461 | // 462 | 463 | function handleMouseDownRotate( event ) { 464 | 465 | rotateStart.set( event.clientX, event.clientY ); 466 | 467 | } 468 | 469 | function handleMouseDownDolly( event ) { 470 | 471 | dollyStart.set( event.clientX, event.clientY ); 472 | 473 | } 474 | 475 | function handleMouseDownPan( event ) { 476 | 477 | panStart.set( event.clientX, event.clientY ); 478 | 479 | } 480 | 481 | function handleMouseMoveRotate( event ) { 482 | 483 | rotateEnd.set( event.clientX, event.clientY ); 484 | 485 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 486 | 487 | var element = scope.domElement; 488 | 489 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 490 | 491 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 492 | 493 | rotateStart.copy( rotateEnd ); 494 | 495 | scope.update(); 496 | 497 | } 498 | 499 | function handleMouseMoveDolly( event ) { 500 | 501 | dollyEnd.set( event.clientX, event.clientY ); 502 | 503 | dollyDelta.subVectors( dollyEnd, dollyStart ); 504 | 505 | if ( dollyDelta.y > 0 ) { 506 | 507 | dollyIn( getZoomScale() ); 508 | 509 | } else if ( dollyDelta.y < 0 ) { 510 | 511 | dollyOut( getZoomScale() ); 512 | 513 | } 514 | 515 | dollyStart.copy( dollyEnd ); 516 | 517 | scope.update(); 518 | 519 | } 520 | 521 | function handleMouseMovePan( event ) { 522 | 523 | panEnd.set( event.clientX, event.clientY ); 524 | 525 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 526 | 527 | pan( panDelta.x, panDelta.y ); 528 | 529 | panStart.copy( panEnd ); 530 | 531 | scope.update(); 532 | 533 | } 534 | 535 | function handleMouseUp( /*event*/ ) { 536 | 537 | // no-op 538 | 539 | } 540 | 541 | function handleMouseWheel( event ) { 542 | 543 | if ( event.deltaY < 0 ) { 544 | 545 | dollyOut( getZoomScale() ); 546 | 547 | } else if ( event.deltaY > 0 ) { 548 | 549 | dollyIn( getZoomScale() ); 550 | 551 | } 552 | 553 | scope.update(); 554 | 555 | } 556 | 557 | function handleKeyDown( event ) { 558 | 559 | var needsUpdate = false; 560 | 561 | switch ( event.keyCode ) { 562 | 563 | case scope.keys.UP: 564 | pan( 0, scope.keyPanSpeed ); 565 | needsUpdate = true; 566 | break; 567 | 568 | case scope.keys.BOTTOM: 569 | pan( 0, - scope.keyPanSpeed ); 570 | needsUpdate = true; 571 | break; 572 | 573 | case scope.keys.LEFT: 574 | pan( scope.keyPanSpeed, 0 ); 575 | needsUpdate = true; 576 | break; 577 | 578 | case scope.keys.RIGHT: 579 | pan( - scope.keyPanSpeed, 0 ); 580 | needsUpdate = true; 581 | break; 582 | 583 | } 584 | 585 | if ( needsUpdate ) { 586 | 587 | // prevent the browser from scrolling on cursor keys 588 | event.preventDefault(); 589 | 590 | scope.update(); 591 | 592 | } 593 | 594 | 595 | } 596 | 597 | function handleTouchStartRotate( event ) { 598 | 599 | if ( event.touches.length == 1 ) { 600 | 601 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 602 | 603 | } else { 604 | 605 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 606 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 607 | 608 | rotateStart.set( x, y ); 609 | 610 | } 611 | 612 | } 613 | 614 | function handleTouchStartPan( event ) { 615 | 616 | if ( event.touches.length == 1 ) { 617 | 618 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 619 | 620 | } else { 621 | 622 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 623 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 624 | 625 | panStart.set( x, y ); 626 | 627 | } 628 | 629 | } 630 | 631 | function handleTouchStartDolly( event ) { 632 | 633 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 634 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 635 | 636 | var distance = Math.sqrt( dx * dx + dy * dy ); 637 | 638 | dollyStart.set( 0, distance ); 639 | 640 | } 641 | 642 | function handleTouchStartDollyPan( event ) { 643 | 644 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 645 | 646 | if ( scope.enablePan ) handleTouchStartPan( event ); 647 | 648 | } 649 | 650 | function handleTouchStartDollyRotate( event ) { 651 | 652 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 653 | 654 | if ( scope.enableRotate ) handleTouchStartRotate( event ); 655 | 656 | } 657 | 658 | function handleTouchMoveRotate( event ) { 659 | 660 | if ( event.touches.length == 1 ) { 661 | 662 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 663 | 664 | } else { 665 | 666 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 667 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 668 | 669 | rotateEnd.set( x, y ); 670 | 671 | } 672 | 673 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 674 | 675 | var element = scope.domElement; 676 | 677 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 678 | 679 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 680 | 681 | rotateStart.copy( rotateEnd ); 682 | 683 | } 684 | 685 | function handleTouchMovePan( event ) { 686 | 687 | if ( event.touches.length == 1 ) { 688 | 689 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 690 | 691 | } else { 692 | 693 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 694 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 695 | 696 | panEnd.set( x, y ); 697 | 698 | } 699 | 700 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 701 | 702 | pan( panDelta.x, panDelta.y ); 703 | 704 | panStart.copy( panEnd ); 705 | 706 | } 707 | 708 | function handleTouchMoveDolly( event ) { 709 | 710 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 711 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 712 | 713 | var distance = Math.sqrt( dx * dx + dy * dy ); 714 | 715 | dollyEnd.set( 0, distance ); 716 | 717 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 718 | 719 | dollyIn( dollyDelta.y ); 720 | 721 | dollyStart.copy( dollyEnd ); 722 | 723 | } 724 | 725 | function handleTouchMoveDollyPan( event ) { 726 | 727 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 728 | 729 | if ( scope.enablePan ) handleTouchMovePan( event ); 730 | 731 | } 732 | 733 | function handleTouchMoveDollyRotate( event ) { 734 | 735 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 736 | 737 | if ( scope.enableRotate ) handleTouchMoveRotate( event ); 738 | 739 | } 740 | 741 | function handleTouchEnd( /*event*/ ) { 742 | 743 | // no-op 744 | 745 | } 746 | 747 | // 748 | // event handlers - FSM: listen for events and reset state 749 | // 750 | 751 | function onMouseDown( event ) { 752 | 753 | if ( scope.enabled === false ) return; 754 | 755 | // Prevent the browser from scrolling. 756 | 757 | event.preventDefault(); 758 | 759 | // Manually set the focus since calling preventDefault above 760 | // prevents the browser from setting it automatically. 761 | 762 | scope.domElement.focus ? scope.domElement.focus() : window.focus(); 763 | 764 | switch ( event.button ) { 765 | 766 | case 0: 767 | 768 | switch ( scope.mouseButtons.LEFT ) { 769 | 770 | case THREE.MOUSE.ROTATE: 771 | 772 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 773 | 774 | if ( scope.enablePan === false ) return; 775 | 776 | handleMouseDownPan( event ); 777 | 778 | state = STATE.PAN; 779 | 780 | } else { 781 | 782 | if ( scope.enableRotate === false ) return; 783 | 784 | handleMouseDownRotate( event ); 785 | 786 | state = STATE.ROTATE; 787 | 788 | } 789 | 790 | break; 791 | 792 | case THREE.MOUSE.PAN: 793 | 794 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 795 | 796 | if ( scope.enableRotate === false ) return; 797 | 798 | handleMouseDownRotate( event ); 799 | 800 | state = STATE.ROTATE; 801 | 802 | } else { 803 | 804 | if ( scope.enablePan === false ) return; 805 | 806 | handleMouseDownPan( event ); 807 | 808 | state = STATE.PAN; 809 | 810 | } 811 | 812 | break; 813 | 814 | default: 815 | 816 | state = STATE.NONE; 817 | 818 | } 819 | 820 | break; 821 | 822 | 823 | case 1: 824 | 825 | switch ( scope.mouseButtons.MIDDLE ) { 826 | 827 | case THREE.MOUSE.DOLLY: 828 | 829 | if ( scope.enableZoom === false ) return; 830 | 831 | handleMouseDownDolly( event ); 832 | 833 | state = STATE.DOLLY; 834 | 835 | break; 836 | 837 | 838 | default: 839 | 840 | state = STATE.NONE; 841 | 842 | } 843 | 844 | break; 845 | 846 | case 2: 847 | 848 | switch ( scope.mouseButtons.RIGHT ) { 849 | 850 | case THREE.MOUSE.ROTATE: 851 | 852 | if ( scope.enableRotate === false ) return; 853 | 854 | handleMouseDownRotate( event ); 855 | 856 | state = STATE.ROTATE; 857 | 858 | break; 859 | 860 | case THREE.MOUSE.PAN: 861 | 862 | if ( scope.enablePan === false ) return; 863 | 864 | handleMouseDownPan( event ); 865 | 866 | state = STATE.PAN; 867 | 868 | break; 869 | 870 | default: 871 | 872 | state = STATE.NONE; 873 | 874 | } 875 | 876 | break; 877 | 878 | } 879 | 880 | if ( state !== STATE.NONE ) { 881 | 882 | document.addEventListener( 'mousemove', onMouseMove, false ); 883 | document.addEventListener( 'mouseup', onMouseUp, false ); 884 | 885 | scope.dispatchEvent( startEvent ); 886 | 887 | } 888 | 889 | } 890 | 891 | function onMouseMove( event ) { 892 | 893 | if ( scope.enabled === false ) return; 894 | 895 | event.preventDefault(); 896 | 897 | switch ( state ) { 898 | 899 | case STATE.ROTATE: 900 | 901 | if ( scope.enableRotate === false ) return; 902 | 903 | handleMouseMoveRotate( event ); 904 | 905 | break; 906 | 907 | case STATE.DOLLY: 908 | 909 | if ( scope.enableZoom === false ) return; 910 | 911 | handleMouseMoveDolly( event ); 912 | 913 | break; 914 | 915 | case STATE.PAN: 916 | 917 | if ( scope.enablePan === false ) return; 918 | 919 | handleMouseMovePan( event ); 920 | 921 | break; 922 | 923 | } 924 | 925 | } 926 | 927 | function onMouseUp( event ) { 928 | 929 | if ( scope.enabled === false ) return; 930 | 931 | handleMouseUp( event ); 932 | 933 | document.removeEventListener( 'mousemove', onMouseMove, false ); 934 | document.removeEventListener( 'mouseup', onMouseUp, false ); 935 | 936 | scope.dispatchEvent( endEvent ); 937 | 938 | state = STATE.NONE; 939 | 940 | } 941 | 942 | function onMouseWheel( event ) { 943 | 944 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 945 | 946 | event.preventDefault(); 947 | event.stopPropagation(); 948 | 949 | scope.dispatchEvent( startEvent ); 950 | 951 | handleMouseWheel( event ); 952 | 953 | scope.dispatchEvent( endEvent ); 954 | 955 | } 956 | 957 | function onKeyDown( event ) { 958 | 959 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 960 | 961 | handleKeyDown( event ); 962 | 963 | } 964 | 965 | function onTouchStart( event ) { 966 | 967 | if ( scope.enabled === false ) return; 968 | 969 | event.preventDefault(); 970 | 971 | switch ( event.touches.length ) { 972 | 973 | case 1: 974 | 975 | switch ( scope.touches.ONE ) { 976 | 977 | case THREE.TOUCH.ROTATE: 978 | 979 | if ( scope.enableRotate === false ) return; 980 | 981 | handleTouchStartRotate( event ); 982 | 983 | state = STATE.TOUCH_ROTATE; 984 | 985 | break; 986 | 987 | case THREE.TOUCH.PAN: 988 | 989 | if ( scope.enablePan === false ) return; 990 | 991 | handleTouchStartPan( event ); 992 | 993 | state = STATE.TOUCH_PAN; 994 | 995 | break; 996 | 997 | default: 998 | 999 | state = STATE.NONE; 1000 | 1001 | } 1002 | 1003 | break; 1004 | 1005 | case 2: 1006 | 1007 | switch ( scope.touches.TWO ) { 1008 | 1009 | case THREE.TOUCH.DOLLY_PAN: 1010 | 1011 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1012 | 1013 | handleTouchStartDollyPan( event ); 1014 | 1015 | state = STATE.TOUCH_DOLLY_PAN; 1016 | 1017 | break; 1018 | 1019 | case THREE.TOUCH.DOLLY_ROTATE: 1020 | 1021 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1022 | 1023 | handleTouchStartDollyRotate( event ); 1024 | 1025 | state = STATE.TOUCH_DOLLY_ROTATE; 1026 | 1027 | break; 1028 | 1029 | default: 1030 | 1031 | state = STATE.NONE; 1032 | 1033 | } 1034 | 1035 | break; 1036 | 1037 | default: 1038 | 1039 | state = STATE.NONE; 1040 | 1041 | } 1042 | 1043 | if ( state !== STATE.NONE ) { 1044 | 1045 | scope.dispatchEvent( startEvent ); 1046 | 1047 | } 1048 | 1049 | } 1050 | 1051 | function onTouchMove( event ) { 1052 | 1053 | if ( scope.enabled === false ) return; 1054 | 1055 | event.preventDefault(); 1056 | event.stopPropagation(); 1057 | 1058 | switch ( state ) { 1059 | 1060 | case STATE.TOUCH_ROTATE: 1061 | 1062 | if ( scope.enableRotate === false ) return; 1063 | 1064 | handleTouchMoveRotate( event ); 1065 | 1066 | scope.update(); 1067 | 1068 | break; 1069 | 1070 | case STATE.TOUCH_PAN: 1071 | 1072 | if ( scope.enablePan === false ) return; 1073 | 1074 | handleTouchMovePan( event ); 1075 | 1076 | scope.update(); 1077 | 1078 | break; 1079 | 1080 | case STATE.TOUCH_DOLLY_PAN: 1081 | 1082 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1083 | 1084 | handleTouchMoveDollyPan( event ); 1085 | 1086 | scope.update(); 1087 | 1088 | break; 1089 | 1090 | case STATE.TOUCH_DOLLY_ROTATE: 1091 | 1092 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1093 | 1094 | handleTouchMoveDollyRotate( event ); 1095 | 1096 | scope.update(); 1097 | 1098 | break; 1099 | 1100 | default: 1101 | 1102 | state = STATE.NONE; 1103 | 1104 | } 1105 | 1106 | } 1107 | 1108 | function onTouchEnd( event ) { 1109 | 1110 | if ( scope.enabled === false ) return; 1111 | 1112 | handleTouchEnd( event ); 1113 | 1114 | scope.dispatchEvent( endEvent ); 1115 | 1116 | state = STATE.NONE; 1117 | 1118 | } 1119 | 1120 | function onContextMenu( event ) { 1121 | 1122 | if ( scope.enabled === false ) return; 1123 | 1124 | event.preventDefault(); 1125 | 1126 | } 1127 | 1128 | // 1129 | 1130 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 1131 | 1132 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 1133 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 1134 | 1135 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 1136 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 1137 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 1138 | 1139 | scope.domElement.addEventListener( 'keydown', onKeyDown, false ); 1140 | 1141 | // make sure element can receive keys. 1142 | 1143 | if ( scope.domElement.tabIndex === - 1 ) { 1144 | 1145 | scope.domElement.tabIndex = 0; 1146 | 1147 | } 1148 | 1149 | // force an update at start 1150 | 1151 | this.update(); 1152 | 1153 | }; 1154 | 1155 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1156 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 1157 | 1158 | 1159 | // This set of controls performs orbiting, dollying (zooming), and panning. 1160 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 1161 | // This is very similar to OrbitControls, another set of touch behavior 1162 | // 1163 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate 1164 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 1165 | // Pan - left mouse, or arrow keys / touch: one-finger move 1166 | 1167 | THREE.MapControls = function ( object, domElement ) { 1168 | 1169 | THREE.OrbitControls.call( this, object, domElement ); 1170 | 1171 | this.mouseButtons.LEFT = THREE.MOUSE.PAN; 1172 | this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; 1173 | 1174 | this.touches.ONE = THREE.TOUCH.PAN; 1175 | this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; 1176 | 1177 | }; 1178 | 1179 | THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1180 | THREE.MapControls.prototype.constructor = THREE.MapControls; 1181 | -------------------------------------------------------------------------------- /docs/glsl-bumpy-sphere/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | uniform float time; 6 | uniform vec2 resolution; 7 | 8 | const int MAX_MARCHING_STEPS = 255; 9 | const float MIN_DIST = 0.0; 10 | const float MAX_DIST = 100.0; 11 | const float EPSILON = 0.0001; 12 | const vec3 AMBIENT_LIGHT = vec3(0.5); 13 | 14 | // Rotation function from https://www.shadertoy.com/view/4tcGDr 15 | mat3 rotateY(float theta) { 16 | float c = cos(theta); 17 | float s = sin(theta); 18 | return mat3( 19 | vec3(c, 0.0, s), 20 | vec3(0.0, 1.0, 0.0), 21 | vec3(-s, 0.0, c) 22 | ); 23 | } 24 | 25 | float sphere(vec3 p, float s) { 26 | return length(p) - s; 27 | } 28 | 29 | float displace(vec3 p) { 30 | p = rotateY(time / 2.0) * p; 31 | float d1 = sphere(p, 8.0); 32 | float d2 = sin(sin(time / 5.0) * 1.2 * p.x) * sin(sin(time / 5.0) * 1.2 * p.y) * sin(sin(time / 5.0) * 1.2 * p.z); 33 | return d1 + d2; 34 | } 35 | 36 | float scene(vec3 p) { 37 | return displace(p); 38 | } 39 | 40 | /* 41 | https://www.shadertoy.com/view/llt3R4 42 | Return the shortest distance from the camera to the scene surface. 43 | 44 | cam: The ray origin, the scene camera. 45 | dir: The normalized direction in which to march the ray. 46 | start: The starting distance from the camera. 47 | end: The maximum distance from the camera before giving up. 48 | */ 49 | float raymarch(vec3 cam, vec3 dir, float start, float end) { 50 | float depth = start; // Depth begins at start point. 51 | for (int i = 0; i < MAX_MARCHING_STEPS; i++) { 52 | float dist = scene(cam + depth * dir); 53 | if (dist < EPSILON) { 54 | return depth; // If we hit the sphere, return that measurement. 55 | } 56 | depth += dist; // If we haven't hit the sphere yet, increment the depth. 57 | if (depth >= end) { 58 | return end; // If we make it to the end, we haven't hit the sphere. 59 | } 60 | 61 | } 62 | return end; 63 | } 64 | 65 | /* 66 | Return the normalized direction of the ray. 67 | 68 | fov: Vertical field of view in degrees. 69 | size: Resolution of the output image. 70 | fc: The x,y coordinate of the pixel in the output image. 71 | */ 72 | vec3 rayDir(float fov, vec2 size, vec2 fc) { 73 | vec2 xy = fc - size / 2.0; 74 | float z = size.y / tan(radians(fov) / 2.0); 75 | return normalize(vec3(xy, -z)); 76 | } 77 | 78 | /* 79 | Functions for phong shading. 80 | https://www.shadertoy.com/view/lt33z7 81 | Estimate the normal of the surface of the sphere at point p. 82 | */ 83 | vec3 estNormal(vec3 p) { 84 | return normalize(vec3( 85 | scene(vec3(p.x + EPSILON, p.y, p.z)) - scene(vec3(p.x - EPSILON, p.y, p.z)), 86 | scene(vec3(p.x, p.y + EPSILON, p.z)) - scene(vec3(p.x, p.y - EPSILON, p.z)), 87 | scene(vec3(p.x, p.y, p.z + EPSILON)) - scene(vec3(p.x, p.y, p.z - EPSILON)) 88 | )); 89 | } 90 | 91 | /* 92 | A light source for phong shading. 93 | 94 | d: Diffuse color. 95 | s: Specular color. 96 | alpha: Shininess coefficient. 97 | p: Position of the point being lit. 98 | cam: Camera position. 99 | pos: Position of the light. 100 | intensity: Color/intensity of the light. 101 | */ 102 | vec3 light(vec3 d, vec3 s, float alpha, vec3 p, vec3 cam, vec3 pos, vec3 intensity) { 103 | vec3 N = estNormal(p); 104 | vec3 L = normalize(pos - p); 105 | vec3 V = normalize(cam - p); 106 | vec3 R = normalize(reflect(-L, N)); 107 | 108 | float dotLN = dot(L, N); 109 | float dotRV = dot(R, V); 110 | 111 | if(dotLN < 0.0) { 112 | // The light is not visible from this point on the surface. 113 | return vec3(0.0); 114 | } 115 | 116 | if(dotRV < 0.0) { 117 | // Light reflection in the opposite direction of the camera, apply only diffuse. 118 | return intensity * (d * dotLN); 119 | } 120 | 121 | return intensity * (d * dotLN + s * pow(dotRV, alpha)); 122 | } 123 | 124 | /* 125 | Apply phong shading with lights. 126 | 127 | a: Ambient color. 128 | d: Diffuse color. 129 | s: Specular color. 130 | alpha: Shininess coefficient. 131 | p: Position of the point being lit. 132 | cam: Camera position. 133 | */ 134 | vec3 phongShading(vec3 a, vec3 d, vec3 s, float alpha, vec3 p, vec3 cam) { 135 | vec3 color = AMBIENT_LIGHT * a; 136 | vec3 lightPos = vec3(20.0, 20.0, 40.0); 137 | vec3 lightIntensity = vec3(0.4); 138 | 139 | color += light(d, s, alpha, p, cam, lightPos, lightIntensity); 140 | 141 | return color; 142 | } 143 | 144 | void main() { 145 | vec3 dir = rayDir(45.0, resolution, gl_FragCoord.xy); 146 | vec3 cam = vec3(0.0, 0.0, 50.0); 147 | float dist = raymarch(cam, dir, MIN_DIST, MAX_DIST); 148 | 149 | if(dist > MAX_DIST - EPSILON) { 150 | // Didn't hit anything. 151 | gl_FragColor = vec4(vec3(0.0), 1.0); 152 | return; 153 | } 154 | 155 | vec3 p = cam + dist * dir; 156 | vec3 a = vec3(0.0); 157 | vec3 d = vec3(0.95, 0.53, 0.7); 158 | vec3 s = vec3(1.0); 159 | float alpha = 100.0; 160 | 161 | vec3 color = phongShading(a, d, s, alpha, p, cam); 162 | 163 | gl_FragColor = vec4(color, 1.0); 164 | } -------------------------------------------------------------------------------- /docs/glsl-bumpy-sphere/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } -------------------------------------------------------------------------------- /docs/glsl-cubes/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | uniform float time; 6 | uniform vec2 resolution; 7 | 8 | const int MAX_MARCHING_STEPS = 1000; 9 | const float MIN_DISTANCE = 0.0; 10 | const float MAX_DISTANCE = 100.0; 11 | const float EPSILON = 0.0001; 12 | 13 | float roundBox(vec3 p, vec3 b, float r) { 14 | return length(max(abs(p) - b, 0.0)) - r; 15 | } 16 | 17 | float repeat(vec3 p, vec3 c) { 18 | vec3 q = mod(p, c) - 0.5 * c; 19 | return roundBox(q, vec3(0.2, 0.2, 0.2), 0.5); 20 | } 21 | 22 | float scene(vec3 p) { 23 | return repeat(p, vec3(3.0, 3.0, 3.0)); 24 | } 25 | 26 | float raymarch(vec3 eye, vec3 direction, float start, float end) { 27 | float depth = start; 28 | for (int i = 0; i < MAX_MARCHING_STEPS; i++) { 29 | float dist = scene(eye + depth * direction); 30 | if (dist < EPSILON) { 31 | return depth; 32 | } 33 | depth += dist; 34 | if (depth >= end) { 35 | return end; 36 | } 37 | } 38 | return end; 39 | } 40 | 41 | vec3 rayDirection(float fov, vec2 size, vec2 fragCoord) { 42 | vec2 xy = fragCoord - size / 2.0; 43 | float z = size.y / tan(radians(fov) / 2.0); 44 | return normalize(vec3(xy, -z)); 45 | } 46 | 47 | vec3 estimateNormal(vec3 p) { 48 | return normalize(vec3( 49 | scene(vec3(p.x + EPSILON, p.y, p.z)) - scene(vec3(p.x - EPSILON, p.y, p.z)), 50 | scene(vec3(p.x, p.y + EPSILON, p.z)) - scene(vec3(p.x, p.y - EPSILON, p.z)), 51 | scene(vec3(p.x, p.y, p.z + EPSILON)) - scene(vec3(p.x, p.y, p.z - EPSILON)) 52 | )); 53 | } 54 | 55 | vec3 phongLight(vec3 diffuse, vec3 specular, float alpha, vec3 p, vec3 eye, vec3 lightPos, vec3 lightIntensity) { 56 | vec3 N = estimateNormal(p); 57 | vec3 L = normalize(lightPos - p); 58 | vec3 V = normalize(eye - p); 59 | vec3 R = normalize(reflect(-L, N)); 60 | 61 | float dotLN = dot(L, N); 62 | float dotRV = dot(R, V); 63 | 64 | if (dotLN < 0.0) { 65 | return vec3(0.0, 0.0, 0.0); 66 | } 67 | 68 | if (dotRV < 0.0) { 69 | return lightIntensity * (diffuse * dotLN); 70 | } 71 | return lightIntensity * (diffuse * dotLN + specular * pow(dotRV, alpha)); 72 | } 73 | 74 | vec3 phongIllumination(vec3 ambient, vec3 diffuse, vec3 specular, float alpha, vec3 p, vec3 eye) { 75 | const vec3 ambientLight = 0.5 * vec3(1.0, 1.0, 1.0); 76 | vec3 color = ambientLight * ambient; 77 | vec3 light1Pos = vec3(4.0, 5.0, -time * 10.0); 78 | vec3 light1Intensity = vec3(0.4, 0.4, 0.4); 79 | color += phongLight(diffuse, specular, alpha, p, eye, light1Pos, light1Intensity); 80 | return color; 81 | } 82 | 83 | mat4 viewMatrix(vec3 eye, vec3 center, vec3 up) { 84 | vec3 f = normalize(center - eye); 85 | vec3 s = normalize(cross(f, up)); 86 | vec3 u = cross(s, f); 87 | return mat4( 88 | vec4(s, 0.0), 89 | vec4(u, 0.0), 90 | vec4(-f, 0.0), 91 | vec4(0.0, 0.0, 0.0, 1) 92 | ); 93 | } 94 | 95 | void main() { 96 | vec3 dir = rayDirection(50.0, resolution.xy, gl_FragCoord.xy); 97 | vec3 eye = vec3(3.0, 0.0, -time * 10.0); 98 | mat4 viewToWorld = viewMatrix(eye, vec3(0.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)); 99 | vec3 worldDir = (viewToWorld * vec4(dir, 0.0)).xyz; 100 | float dist = raymarch(eye, dir, MIN_DISTANCE, MAX_DISTANCE); 101 | 102 | if (dist > MAX_DISTANCE - EPSILON) { 103 | vec2 st = gl_FragCoord.xy/resolution.xy; 104 | float pct = 0.6 - distance(st, vec2(0.5)); 105 | vec3 color = vec3(0.0 * pct, 0.8 * pct, 1.0 * pct); 106 | gl_FragColor = vec4(color, 1.0); 107 | return; 108 | } 109 | 110 | vec3 p = eye + dist * dir; 111 | vec3 ambient = vec3(0.0, 0.6, 0.8); 112 | vec3 diffuse = vec3(0.0, 0.6, 0.8); 113 | vec3 specular = vec3(1.0, 1.0, 1.0); 114 | float shininess = 50.0; 115 | 116 | vec3 color = phongIllumination(ambient, diffuse, specular, shininess, p, eye); 117 | 118 | gl_FragColor = vec4(color, 1.0); 119 | } -------------------------------------------------------------------------------- /docs/glsl-cubes/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } -------------------------------------------------------------------------------- /docs/glsl-juliafractal/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | uniform vec2 resolution; 6 | uniform float time; 7 | 8 | const int ITER = 15; 9 | float SCALE = (resolution.x / resolution.y) * 0.002; 10 | vec2 c = vec2(sin(time * 0.5) + 0.5, sin(time * 0.5) + 0.3); 11 | 12 | vec3 julia(vec2 z) { 13 | for (int i = 0; i < ITER; i++) { 14 | float x = (z.x * z.x - z.y * z.y) + c.x; 15 | float y = (z.y * z.x + z.x * z.y) + c.y; 16 | 17 | if ((x * x + y * y) > 10.0) return vec3(0.0, 0.0, 0.0); 18 | z.x = x; 19 | z.y = y; 20 | 21 | } 22 | return vec3(0.0, z.x, z.x); 23 | } 24 | 25 | void main() { 26 | vec2 z; 27 | 28 | z.x = (gl_FragCoord.x) * SCALE - resolution.x * (SCALE / 2.0); 29 | z.y = (gl_FragCoord.y) * SCALE - resolution.y * (SCALE / 2.0); 30 | 31 | vec3 color = julia(z); 32 | gl_FragColor = vec4(color, 1.0); 33 | } -------------------------------------------------------------------------------- /docs/glsl-juliafractal/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } -------------------------------------------------------------------------------- /docs/glsl-just-a-cube/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | uniform float time; 6 | uniform vec2 resolution; 7 | 8 | const int MAX_MARCHING_STEPS = 1000; 9 | const float MIN_DISTANCE = 0.0; 10 | const float MAX_DISTANCE = 100.0; 11 | const float EPSILON = 0.0001; 12 | 13 | float cube(vec3 p, vec3 b) { 14 | return length(max(abs(p) - b, 0.0)); 15 | } 16 | 17 | float scene(vec3 p) { 18 | return cube(p, vec3(1.0, 1.0, 1.0)); 19 | } 20 | 21 | float raymarch(vec3 eye, vec3 direction, float start, float end) { 22 | float depth = start; 23 | for (int i = 0; i < MAX_MARCHING_STEPS; i++) { 24 | float dist = scene(eye + depth * direction); 25 | if (dist < EPSILON) { 26 | return depth; 27 | } 28 | depth += dist; 29 | if (depth >= end) { 30 | return end; 31 | } 32 | } 33 | return end; 34 | } 35 | 36 | vec3 rayDirection(float fov, vec2 size, vec2 fragCoord) { 37 | vec2 xy = fragCoord - size / 2.0; 38 | float z = size.y / tan(radians(fov) / 2.0); 39 | return normalize(vec3(xy, -z)); 40 | } 41 | 42 | vec3 estimateNormal(vec3 p) { 43 | return normalize(vec3( 44 | scene(vec3(p.x + EPSILON, p.y, p.z)) - scene(vec3(p.x - EPSILON, p.y, p.z)), 45 | scene(vec3(p.x, p.y + EPSILON, p.z)) - scene(vec3(p.x, p.y - EPSILON, p.z)), 46 | scene(vec3(p.x, p.y, p.z + EPSILON)) - scene(vec3(p.x, p.y, p.z - EPSILON)) 47 | )); 48 | } 49 | 50 | vec3 phongLight(vec3 diffuse, vec3 specular, float alpha, vec3 p, vec3 eye, vec3 lightPos, vec3 lightIntensity) { 51 | vec3 N = estimateNormal(p); 52 | vec3 L = normalize(lightPos - p); 53 | vec3 V = normalize(eye - p); 54 | vec3 R = normalize(reflect(-L, N)); 55 | 56 | float dotLN = dot(L, N); 57 | float dotRV = dot(R, V); 58 | 59 | if (dotLN < 0.0) { 60 | return vec3(0.0, 0.0, 0.0); 61 | } 62 | 63 | if (dotRV < 0.0) { 64 | return lightIntensity * (diffuse * dotLN); 65 | } 66 | return lightIntensity * (diffuse * dotLN + specular * pow(dotRV, alpha)); 67 | } 68 | 69 | vec3 phongIllumination(vec3 ambient, vec3 diffuse, vec3 specular, float alpha, vec3 p, vec3 eye) { 70 | const vec3 ambientLight = 0.5 * vec3(1.0, 1.0, 1.0); 71 | vec3 color = ambientLight * ambient; 72 | vec3 light1Pos = vec3(4.0, 5.0, 20.0); 73 | vec3 light1Intensity = vec3(0.4, 0.4, 0.4); 74 | color += phongLight(diffuse, specular, alpha, p, eye, light1Pos, light1Intensity); 75 | return color; 76 | } 77 | 78 | mat4 viewMatrix(vec3 eye, vec3 center, vec3 up) { 79 | vec3 f = normalize(center - eye); 80 | vec3 s = normalize(cross(f, up)); 81 | vec3 u = cross(s, f); 82 | return mat4( 83 | vec4(s, 0.0), 84 | vec4(u, 0.0), 85 | vec4(-f, 0.0), 86 | vec4(0.0, 0.0, 0.0, 1) 87 | ); 88 | } 89 | 90 | void main() { 91 | vec3 dir = rayDirection(50.0, resolution.xy, gl_FragCoord.xy); 92 | vec3 eye = vec3(0.0, 0.0, 10.0); 93 | mat4 viewToWorld = viewMatrix(eye, vec3(0.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)); 94 | vec3 worldDir = (viewToWorld * vec4(dir, 0.0)).xyz; 95 | float dist = raymarch(eye, dir, MIN_DISTANCE, MAX_DISTANCE); 96 | 97 | if (dist > MAX_DISTANCE - EPSILON) { 98 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 99 | return; 100 | } 101 | 102 | vec3 p = eye + dist * dir; 103 | vec3 ambient = vec3(sin(time), 0.9, cos(time)); 104 | vec3 diffuse = vec3(0.0, 0.6, 0.8); 105 | vec3 specular = vec3(1.0, 1.0, 1.0); 106 | float shininess = 10.0; 107 | 108 | vec3 color = phongIllumination(ambient, diffuse, specular, shininess, p, eye); 109 | 110 | gl_FragColor = vec4(color, 1.0); 111 | } -------------------------------------------------------------------------------- /docs/glsl-just-a-cube/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } -------------------------------------------------------------------------------- /docs/glsl-kaleidoscope/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | uniform float time; 6 | uniform vec2 resolution; 7 | 8 | const int MAX_MARCHING_STEPS = 255; 9 | const float MIN_DIST = 0.0; 10 | const float MAX_DIST = 50.0; 11 | const float EPSILON = 0.0001; 12 | 13 | float box(vec3 p, vec3 b, float r, vec3 c) { 14 | vec3 q = mod(p, c) - 0.5 * c; 15 | return length(max(abs(q) - b, 0.0)) - r; 16 | } 17 | 18 | float sphere(vec3 p, float s, vec3 c) { 19 | vec3 q = mod(p, c) - 0.5 * c; 20 | return length(q) - s; 21 | } 22 | 23 | float boxMinusSphere(float d1, float d2) { 24 | return max(-d1, d2); 25 | } 26 | 27 | float scene(vec3 p) { 28 | vec3 c = vec3(5.0, 5.0, 5.0); 29 | float b = box(p, vec3(0.4, 0.4, 0.4), 0.1, c); 30 | float s = sphere(p, abs(cos(time)) * 0.19 + 0.6, c); 31 | float u = boxMinusSphere(s, b); 32 | return u; 33 | } 34 | 35 | float distanceCalc(vec3 eye, vec3 direction, float start, float end) { 36 | float depth = start; 37 | for (int i = 0; i < MAX_MARCHING_STEPS; i++) { 38 | float dist = scene(eye + depth * direction); 39 | if (dist < EPSILON) { 40 | return depth; 41 | } 42 | depth += dist; 43 | if (depth >= end) { 44 | return end; 45 | } 46 | } 47 | return end; 48 | } 49 | 50 | vec3 rayDirection(float fov, vec2 size, vec2 fragCoord) { 51 | vec2 xy = fragCoord - size / 2.0; 52 | float z = size.y / tan(radians(fov) / 2.0); 53 | return normalize(vec3(xy, -z)); 54 | } 55 | 56 | vec3 estimateNormal(vec3 p) { 57 | return normalize(vec3( 58 | scene(vec3(p.x + EPSILON, p.y, p.z)) - scene(vec3(p.x - EPSILON, p.y, p.z)), 59 | scene(vec3(p.x, p.y + EPSILON, p.z)) - scene(vec3(p.x, p.y - EPSILON, p.z)), 60 | scene(vec3(p.x, p.y, p.z + EPSILON)) - scene(vec3(p.x, p.y, p.z - EPSILON)) 61 | )); 62 | } 63 | 64 | mat4 viewMatrix(vec3 eye, vec3 center, vec3 up) { 65 | vec3 f = normalize(center - eye); 66 | vec3 s = normalize(cross(f, up)); 67 | vec3 u = cross(s, f); 68 | return mat4( 69 | vec4(s, 0.0), 70 | vec4(u, 0.0), 71 | vec4(-f, 0.0), 72 | vec4(0.0, 0.0, 0.0, 1) 73 | ); 74 | } 75 | 76 | void main() { 77 | vec3 dir = rayDirection(45.0, resolution.xy, gl_FragCoord.xy); 78 | vec3 eye = vec3(sin(time / 5.0), abs(sin(time / 5.0)), sin(time / 5.0)); 79 | 80 | mat4 viewToWorld = viewMatrix(eye, vec3(0.0, 0.0, 0.0), vec3(0.0, sin(time), cos(time))); 81 | vec3 worldDir = (viewToWorld * vec4(dir, 0.0)).xyz; 82 | float dist = distanceCalc(eye, worldDir, MIN_DIST, MAX_DIST); 83 | 84 | if (dist > MAX_DIST - EPSILON) { 85 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 86 | return; 87 | } 88 | 89 | vec3 p = abs(eye + (sin(time / 2.0) * dist) * dir); 90 | vec3 color = estimateNormal(p); 91 | 92 | gl_FragColor = vec4(color, 1.0); 93 | } -------------------------------------------------------------------------------- /docs/glsl-kaleidoscope/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } -------------------------------------------------------------------------------- /docs/glsl-mandelbrot/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | uniform vec2 resolution; 6 | 7 | const int ITER = 15; 8 | float SCALE = (resolution.x / resolution.y) * 0.002; 9 | 10 | float brot(vec2 c) { 11 | vec2 z = c; 12 | for (int i = 0; i < ITER; i++) { 13 | float x = (z.x * z.x - z.y * z.y) + c.x; 14 | float y = (z.y * z.x + z.x * z.y) + c.y; 15 | 16 | if ((x * x + y * y) > 40.0) return 0.0; 17 | z.x = x; 18 | z.y = y; 19 | 20 | } 21 | return abs(z.x) + abs(z.y); 22 | } 23 | 24 | void main() { 25 | vec2 z, c; 26 | 27 | c.x = (gl_FragCoord.x) * SCALE - resolution.x * (SCALE / 2.0) - 0.7; 28 | c.y = (gl_FragCoord.y) * SCALE - resolution.y * (SCALE / 2.0); 29 | 30 | vec3 color = vec3(0.03, brot(c) * 0.75, brot(c) * 0.7); 31 | gl_FragColor = vec4(color, 1.0); 32 | } -------------------------------------------------------------------------------- /docs/glsl-mandelbrot/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } -------------------------------------------------------------------------------- /docs/glsl-random-lines/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | // https://thebookofshaders.com/10/ 6 | 7 | uniform vec2 resolution; 8 | uniform float time; 9 | 10 | float random(vec2 s, float t) { 11 | return fract(sin(dot(s.xy, vec2(0.0, t))) * (time + 10.0)); 12 | } 13 | 14 | void main() { 15 | vec2 s = gl_FragCoord.xy / resolution; 16 | gl_FragColor = vec4(vec3(random(s, 83.8), random(s, 14.3), random(s, 94.6)), 1.0); 17 | } 18 | -------------------------------------------------------------------------------- /docs/glsl-random-lines/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } 6 | -------------------------------------------------------------------------------- /docs/glsl-spinning-color-wheel/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | // Inspiration from The Book of Shaders. 6 | // https://thebookofshaders.com/06/ 7 | 8 | #define TWO_PI 6.28318530718 9 | 10 | uniform vec2 resolution; 11 | uniform float time; 12 | 13 | // https://www.shadertoy.com/view/MsS3Wc 14 | vec3 hsb2rgb(vec3 c) { 15 | vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 16 | 0.0, 17 | 1.0 18 | ); 19 | rgb = rgb * rgb * (3.0 - 2.0 * rgb); 20 | return c.z * mix(vec3(1.0), rgb, c.y); 21 | } 22 | 23 | float circle(vec2 s, float rad) { 24 | // This calculation will make an oval at full screen size, 25 | // so I'm cheating and making the canvas a perfect square. 26 | vec2 dist = s - vec2(0.5); 27 | return 1.0 - smoothstep(rad - (rad * 0.01), rad + (rad * 0.01), dot(dist, dist) * 8.0); 28 | } 29 | 30 | void main() { 31 | // Screen dimensions and set the base color. 32 | vec2 s = gl_FragCoord.xy / resolution; 33 | vec3 color = vec3(0.0); 34 | 35 | // Calculate the color part of the wheel. 36 | vec2 toCenter = vec2(0.5) - s; 37 | float angle = atan(toCenter.y, toCenter.x); 38 | float radius = length(toCenter) * 4.0; 39 | // Adding the time variable to the angle calc makes the spinning happen. 40 | vec3 colors = hsb2rgb(vec3((angle / TWO_PI) + time, radius, 1.0)); 41 | 42 | // Create the inner and outer circles. 43 | vec3 outerCircle = vec3(circle(s, 0.5)); 44 | vec3 innerCircle = vec3(circle(s, 0.2)); 45 | 46 | // Color the circle with the rgb colors, then subtract the inner circle. 47 | color = outerCircle * colors - innerCircle; 48 | 49 | // Output. 50 | gl_FragColor = vec4(color, 1.0); 51 | } 52 | -------------------------------------------------------------------------------- /docs/glsl-spinning-color-wheel/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } -------------------------------------------------------------------------------- /docs/glsl-squishydonutspin/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | uniform float time; 6 | uniform vec2 resolution; 7 | 8 | const int MAX_MARCHING_STEPS = 1000; 9 | const float MIN_DISTANCE = 0.0; 10 | const float MAX_DISTANCE = 100.0; 11 | const float EPSILON = 0.0001; 12 | 13 | // Distance functions from http://iquilezles.org/www/articles/distfunctions/distfunctions.htm 14 | float tor(vec3 p, vec2 t) { 15 | vec2 q = vec2(length(p.xz) - t.x, p.y); 16 | return length(q) - t.y; 17 | } 18 | 19 | vec3 twist(vec3 p) { 20 | float c = cos(sin(time * 2.0) * 6.0 * p.y); 21 | float s = sin(sin(time * 2.0) * 6.0 * p.y); 22 | mat2 m = mat2(c, -s, s, c); 23 | return vec3(m * p.xz, p.y); 24 | } 25 | 26 | // Rotation matrix function from http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/ 27 | mat4 rotationMatrix(vec3 axis, float angle) { 28 | vec3 a = normalize(axis); 29 | float s = sin(angle) * -1.0; 30 | float c = cos(angle); 31 | float oc = 1.0 - c; 32 | return mat4( 33 | oc * a.x * a.x + c, oc * a.x * a.y - a.z * s, oc * a.z * a.x + a.y * s, 0.0, 34 | oc * a.x * a.y + a.z * s, oc * a.y * a.y + c, oc * a.y * a.z - a.x * s, 0.0, 35 | oc * a.z * a.x - a.y * s, oc * a.y * a.z + a.x * s, oc * a.z * a.z + c, 0.0, 36 | 0.0, 0.0, 0.0, 0.0 37 | ); 38 | } 39 | 40 | vec3 rotator(vec3 p, mat4 m) { 41 | vec3 q = (m * vec4(p, 0.0)).xyz; 42 | return q; 43 | } 44 | 45 | float scene(vec3 p) { 46 | vec3 t = twist(p); 47 | mat4 matrix = rotationMatrix(vec3(0.0, 1.0, 1.0), time); 48 | vec3 r = rotator(t, matrix); 49 | return tor(r, vec2(0.6, 0.3)); 50 | } 51 | 52 | // Raymarching and phong lighting equations from https://www.shadertoy.com/view/lt33z7 53 | float raymarch(vec3 eye, vec3 direction, float start, float end) { 54 | float depth = start; 55 | for (int i = 0; i < MAX_MARCHING_STEPS; i++) { 56 | float dist = scene(eye + depth * direction); 57 | if (dist < EPSILON) { 58 | return depth; 59 | } 60 | depth += dist; 61 | if (depth >= end) { 62 | return end; 63 | } 64 | } 65 | return end; 66 | } 67 | 68 | vec3 rayDirection(float fov, vec2 size, vec2 fragCoord) { 69 | vec2 xy = fragCoord - size / 2.0; 70 | float z = size.y / tan(radians(fov) / 2.0); 71 | return normalize(vec3(xy, -z)); 72 | } 73 | 74 | vec3 estimateNormal(vec3 p) { 75 | return normalize(vec3( 76 | scene(vec3(p.x + EPSILON, p.y, p.z)) - scene(vec3(p.x - EPSILON, p.y, p.z)), 77 | scene(vec3(p.x, p.y + EPSILON, p.z)) - scene(vec3(p.x, p.y - EPSILON, p.z)), 78 | scene(vec3(p.x, p.y, p.z + EPSILON)) - scene(vec3(p.x, p.y, p.z - EPSILON)) 79 | )); 80 | } 81 | 82 | vec3 phongLight(vec3 diffuse, vec3 specular, float alpha, vec3 p, vec3 eye, vec3 lightPos, vec3 lightIntensity) { 83 | vec3 N = estimateNormal(p); 84 | vec3 L = normalize(lightPos - p); 85 | vec3 V = normalize(eye - p); 86 | vec3 R = normalize(reflect(-L, N)); 87 | 88 | float dotLN = dot(L, N); 89 | float dotRV = dot(R, V); 90 | 91 | if (dotLN < 0.0) { 92 | return vec3(0.0, 0.0, 0.0); 93 | } 94 | 95 | if (dotRV < 0.0) { 96 | return lightIntensity * (diffuse * dotLN); 97 | } 98 | return lightIntensity * (diffuse * dotLN + specular * pow(dotRV, alpha)); 99 | } 100 | 101 | vec3 phongIllumination(vec3 ambient, vec3 diffuse, vec3 specular, float alpha, vec3 p, vec3 eye) { 102 | const vec3 ambientLight = 0.5 * vec3(1.0, 1.0, 1.0); 103 | vec3 color = ambientLight * ambient; 104 | vec3 light1Pos = vec3(4.0, 5.0, 5.0); 105 | vec3 light1Intensity = vec3(0.4, 0.4, 0.4); 106 | color += phongLight(diffuse, specular, alpha, p, eye, light1Pos, light1Intensity); 107 | return color; 108 | } 109 | 110 | mat4 viewMatrix(vec3 eye, vec3 center, vec3 up) { 111 | vec3 f = normalize(center - eye); 112 | vec3 s = normalize(cross(f, up)); 113 | vec3 u = cross(s, f); 114 | return mat4( 115 | vec4(s, 0.0), 116 | vec4(u, 0.0), 117 | vec4(-f, 0.0), 118 | vec4(0.0, 0.0, 0.0, 1) 119 | ); 120 | } 121 | 122 | void main() { 123 | vec3 dir = rayDirection(50.0, resolution.xy, gl_FragCoord.xy); 124 | vec3 eye = vec3(0.0, 0.0, 5.0); 125 | mat4 viewToWorld = viewMatrix(eye, vec3(0.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)); 126 | vec3 worldDir = (viewToWorld * vec4(dir, 0.0)).xyz; 127 | float dist = raymarch(eye, dir, MIN_DISTANCE, MAX_DISTANCE); 128 | 129 | if (dist > MAX_DISTANCE - EPSILON) { 130 | gl_FragColor = vec4(0.1, 0.1, 0.1, 1.0); 131 | return; 132 | } 133 | 134 | vec3 p = eye + dist * dir; 135 | vec3 ambient = vec3(0.5, 0.6, 0.8); 136 | vec3 diffuse = vec3(0.5, 0.6, 0.8); 137 | vec3 specular = vec3(1.0, 1.0, 1.0); 138 | float shininess = 10.0; 139 | 140 | vec3 color = phongIllumination(ambient, diffuse, specular, shininess, p, eye); 141 | 142 | gl_FragColor = vec4(color, 1.0); 143 | } -------------------------------------------------------------------------------- /docs/glsl-squishydonutspin/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } -------------------------------------------------------------------------------- /docs/glsl-very-basic/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | // This is the canvas width and height from boilerplate.js. 6 | uniform vec2 resolution; 7 | 8 | void main() { 9 | // Get the coordinates of each fragment shader pixel. 10 | vec2 s = gl_FragCoord.xy / resolution; 11 | 12 | vec3 color; 13 | if (gl_FragCoord.x < resolution.x / 2.0) { 14 | // Half the screen gets one horizontal gradient... 15 | color = vec3(1.0 * s.y, 0.7, 0.3); 16 | } else { 17 | // ...The other half of the screen gets another. 18 | color = vec3(0.3, 0.75 * s.y, 0.7); 19 | } 20 | 21 | // Put the colors on the screen. 22 | gl_FragColor = vec4(color, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /docs/glsl-very-basic/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } 6 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Shader Art 6 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/threejs-icosphere-explode/scene.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let camera, scene, renderer, controls; 3 | let material, mesh, materialShader; 4 | let particleMaterial, particleMaterialShader; 5 | 6 | init(); 7 | animate(); 8 | 9 | function init() { 10 | 11 | camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 ); 12 | camera.position.set(-1, 0, 1); 13 | 14 | scene = new THREE.Scene(); 15 | 16 | var light = new THREE.PointLight( 0xffffff, 1, 10 ); 17 | light.position.set(0, 1, 1) 18 | camera.add(light); 19 | scene.add(camera); 20 | 21 | var ambient = new THREE.AmbientLight(0xffffff, 0.5); 22 | scene.add(ambient); 23 | 24 | createOuterIco(); 25 | createSparkles(); 26 | 27 | renderer = new THREE.WebGLRenderer( { antialias: true } ); 28 | renderer.setSize( window.innerWidth, window.innerHeight ); 29 | let appendLocation = document.getElementById('threejs'); 30 | appendLocation.appendChild( renderer.domElement ); 31 | 32 | controls = new THREE.OrbitControls(camera, renderer.domElement); 33 | controls.autoRotate = true; 34 | controls.enableDamping = true; 35 | 36 | } 37 | 38 | function createOuterIco() { 39 | let geometry = new THREE.IcosahedronBufferGeometry( 0.5, 0 ); 40 | material = new THREE.MeshPhongMaterial(); 41 | material.color.setRGB(0.7, 0.9, 0.95); 42 | material.opacity = 1.0; 43 | material.transparent = true; 44 | material.side = THREE.DoubleSide; 45 | 46 | let position = geometry.attributes.position; 47 | let numfaces = position.count / 3; 48 | let displacement = new Float32Array(position.count * 3); 49 | 50 | for (let f = 0; f < numfaces; f++) { 51 | let index = 9*f; 52 | 53 | for (let i = 0; i < 3; i++) { 54 | displacement[index + (3 * i)] = 1.0; 55 | displacement[index + (3 * i) + 1] = 1.0; 56 | displacement[index + (3 * i) + 2] = 1.0; 57 | } 58 | } 59 | 60 | geometry.setAttribute('displacement', new THREE.BufferAttribute(displacement, 3)); 61 | 62 | material.onBeforeCompile = (shader) => { 63 | const token = '#include '; 64 | shader.uniforms.amount = {value: 0.0}; 65 | shader.vertexShader = ` 66 | attribute vec3 displacement; 67 | uniform float amount; 68 | ` + shader.vertexShader; 69 | 70 | let shaderText = ` 71 | vec3 newPosition = position + normal * displacement * amount; 72 | gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 ); 73 | `; 74 | 75 | shader.vertexShader = shader.vertexShader.replace(token, shaderText); 76 | materialShader = shader; 77 | }; 78 | 79 | mesh = new THREE.Mesh( geometry, material ); 80 | scene.add( mesh ); 81 | } 82 | 83 | function createSparkles() { 84 | let particles = new THREE.IcosahedronBufferGeometry(0.01, 4); 85 | particleMaterial = new THREE.PointsMaterial(); 86 | let particleSystem = new THREE.Points(particles, particleMaterial); 87 | 88 | let position = particles.attributes.position; 89 | let numfaces = position.count / 3; 90 | let displacement = new Float32Array(position.count * 3); 91 | 92 | 93 | for (let f = 0; f < numfaces; f++) { 94 | let index = 9*f; 95 | 96 | for (let i = 0; i < 3; i++) { 97 | displacement[index + (3 * i)] = 1.0; 98 | displacement[index + (3 * i) + 1] = 1.0; 99 | displacement[index + (3 * i) + 2] = 1.0; 100 | } 101 | } 102 | particles.setAttribute('displacement', new THREE.BufferAttribute(displacement, 3, true)); 103 | 104 | particleMaterial.onBeforeCompile = (shader) => { 105 | const token = '#include '; 106 | shader.uniforms.amount = {value: 0.0}; 107 | shader.uniforms.u_resolution = {type: 'v2', value: new THREE.Vector2(renderer.domElement.width, renderer.domElement.height)}; 108 | shader.vertexShader = ` 109 | attribute vec3 displacement; 110 | uniform float amount; 111 | ` + shader.vertexShader; 112 | 113 | let shaderText = ` 114 | gl_PointSize = 2.0; 115 | vec3 newPosition = position + normal * displacement * amount; 116 | gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 ); 117 | `; 118 | 119 | shader.vertexShader = shader.vertexShader.replace(token, shaderText); 120 | 121 | shader.fragmentShader = ` 122 | uniform vec2 u_resolution; 123 | ` + shader.fragmentShader; 124 | 125 | let fragShaderNoise = ` 126 | float random (in vec2 st) { 127 | return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123); 128 | } 129 | float noise (in vec2 st) { 130 | vec2 i = floor(st); 131 | vec2 f = fract(st); 132 | 133 | // Four corners in 2D of a tile 134 | float a = random(i); 135 | float b = random(i + vec2(1.0, 0.0)); 136 | float c = random(i + vec2(0.0, 1.0)); 137 | float d = random(i + vec2(1.0, 1.0)); 138 | 139 | // Smooth Interpolation 140 | 141 | // Cubic Hermine Curve. Same as SmoothStep() 142 | vec2 u = f*f*(3.0-2.0*f); 143 | // u = smoothstep(0.,1.,f); 144 | 145 | // Mix 4 corners percentages 146 | return mix(a, b, u.x) + 147 | (c - a)* u.y * (1.0 - u.x) + 148 | (d - b) * u.x * u.y; 149 | } 150 | `; 151 | let fragShaderMain = ` 152 | vec2 st = gl_FragCoord.xy; 153 | 154 | // Scale the coordinate system to see 155 | // some noise in action 156 | vec2 pos = vec2(st / 8.0); 157 | 158 | // Use the noise function 159 | float n = noise(pos); 160 | 161 | gl_FragColor = vec4(vec3(n), 1.0); 162 | `; 163 | shader.fragmentShader = shader.fragmentShader.replace('#include ', fragShaderNoise); 164 | shader.fragmentShader = shader.fragmentShader.replace('#include ', fragShaderMain); 165 | particleMaterialShader = shader; 166 | }; 167 | 168 | scene.add(particleSystem); 169 | } 170 | 171 | function animate() { 172 | 173 | requestAnimationFrame( animate ); 174 | 175 | if (materialShader) { 176 | materialShader.uniforms.amount.value = Math.abs((controls.getPolarAngle() - Math.PI / 2)) * 0.2; 177 | material.opacity = 1.0 - Math.abs((controls.getPolarAngle() - Math.PI / 2)) * 0.1; 178 | } 179 | if (particleMaterialShader) { 180 | particleMaterialShader.uniforms.amount.value = Math.abs((controls.getPolarAngle() - Math.PI / 2)) * 0.8; 181 | } 182 | 183 | controls.update(); 184 | 185 | renderer.render( scene, camera ); 186 | 187 | } 188 | })() 189 | -------------------------------------------------------------------------------- /fonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captainpainway/shader-art/f554420e3f5443b6307afd81016fbb1eb5042666/fonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /fonts/fa-brands.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.0.2 by @fontawesome - http://fontawesome.com 3 | * License - http://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face { 6 | font-family: 'Font Awesome 5 Brands'; 7 | font-style: normal; 8 | font-weight: normal; 9 | src: url("./fa-brands-400.ttf"); 10 | } 11 | 12 | .fab { 13 | font-family: 'Font Awesome 5 Brands'; } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shader-art", 3 | "version": "1.0.0", 4 | "main": "src/index.js", 5 | "scripts": { 6 | "clean": "rm dist/bundle.js", 7 | "build-dev": "webpack -d --mode development", 8 | "build-prod": "webpack -p --mode production", 9 | "start": "webpack-dev-server --mode development" 10 | }, 11 | "repository": "https://github.com/captainpainway/shader-art.git", 12 | "author": "Mary Knize ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "react": "^16.12.0", 16 | "react-dom": "^16.12.0" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.7.2", 20 | "@babel/plugin-transform-runtime": "^7.6.2", 21 | "@babel/preset-env": "^7.7.1", 22 | "@babel/preset-react": "^7.7.0", 23 | "@babel/runtime": "^7.7.2", 24 | "@hot-loader/react-dom": "^16.11.0", 25 | "babel-loader": "^8.0.6", 26 | "css-loader": "^3.2.0", 27 | "style-loader": "^1.0.0", 28 | "webpack": "^4.41.2", 29 | "webpack-cli": "^3.3.10", 30 | "webpack-dev-server": "^3.9.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spinning-color-wheel/fragmentshader.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | // Inspiration from The Book of Shaders. 6 | // https://thebookofshaders.com/06/ 7 | 8 | #define TWO_PI 6.28318530718 9 | 10 | uniform vec2 resolution; 11 | uniform float time; 12 | 13 | // https://www.shadertoy.com/view/MsS3Wc 14 | vec3 hsb2rgb(vec3 c) { 15 | vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 16 | 0.0, 17 | 1.0 18 | ); 19 | rgb = rgb * rgb * (3.0 - 2.0 * rgb); 20 | return c.z * mix(vec3(1.0), rgb, c.y); 21 | } 22 | 23 | float circle(vec2 s, float rad) { 24 | // This calculation will make an oval at full screen size, 25 | // so I'm cheating and making the canvas a perfect square. 26 | vec2 dist = s - vec2(0.5); 27 | return 1.0 - smoothstep(rad - (rad * 0.01), rad + (rad * 0.01), dot(dist, dist) * 8.0); 28 | } 29 | 30 | void main() { 31 | // Screen dimensions and set the base color. 32 | vec2 s = gl_FragCoord.xy / resolution; 33 | vec3 color = vec3(0.0); 34 | 35 | // Calculate the color part of the wheel. 36 | vec2 toCenter = vec2(0.5) - s; 37 | float angle = atan(toCenter.y, toCenter.x); 38 | float radius = length(toCenter) * 4.0; 39 | // Adding the time variable to the angle calc makes the spinning happen. 40 | vec3 colors = hsb2rgb(vec3((angle / TWO_PI) + time, radius, 1.0)); 41 | 42 | // Create the inner and outer circles. 43 | vec3 outerCircle = vec3(circle(s, 0.5)); 44 | vec3 innerCircle = vec3(circle(s, 0.2)); 45 | 46 | // Color the circle with the rgb colors, then subtract the inner circle. 47 | color = outerCircle * colors - innerCircle; 48 | 49 | // Output. 50 | gl_FragColor = vec4(color, 1.0); 51 | } 52 | -------------------------------------------------------------------------------- /spinning-color-wheel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spinning Color Wheel -- GLSL Shader Art -- Mary Knize 6 | 7 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /spinning-color-wheel/vertexshader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0); 5 | } -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #222222; 3 | } 4 | 5 | .button { 6 | position: absolute; 7 | width: 40px; 8 | height: 40px; 9 | font-size: 32px; 10 | text-align: center; 11 | padding: 5px; 12 | box-sizing: border-box; 13 | cursor: pointer; 14 | } 15 | 16 | .select { 17 | background-color: #222; 18 | height: 100%; 19 | padding: 20px 0; 20 | box-sizing: border-box; 21 | font-family: sans-serif; 22 | -webkit-box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75); 23 | -moz-box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75); 24 | box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75); 25 | } 26 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Shader from './shader'; 3 | import {Select} from './select'; 4 | import {Three} from './threejs'; 5 | import './app.css'; 6 | 7 | class App extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.shaders = [ 11 | 'glsl-squishydonutspin', 12 | 'glsl-bumpy-sphere', 13 | 'glsl-kaleidoscope', 14 | 'glsl-mandelbrot', 15 | 'glsl-juliafractal', 16 | 'glsl-cubes', 17 | 'glsl-random-lines', 18 | 'glsl-spinning-color-wheel', 19 | 'glsl-just-a-cube', 20 | 'glsl-very-basic', 21 | 'threejs-icosphere-explode', 22 | ]; 23 | this.state = { 24 | shader: this.shaders[0], 25 | openpopin: null 26 | }; 27 | this.selectShader = this.selectShader.bind(this); 28 | this.vertex_shader = null; 29 | this.fragment_shader = null; 30 | } 31 | 32 | selectShader(value) { 33 | this.setState({shader: value}); 34 | } 35 | 36 | render() { 37 | if (document.getElementById('three_script')) { 38 | document.body.removeChild(document.getElementById('three_script')); 39 | } 40 | let canvas; 41 | if (this.state.shader.search(/^threejs/) !== -1) { 42 | canvas = 43 | } else { 44 | canvas = 45 | } 46 | return ( 47 |
48 |