Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <script id="vertexShader" type="x-shader/x-vertex">
  attribute vec3 center;
  uniform float uTime;
  varying float vDisp;
  varying vec3 vCenter;
  varying vec2 vSceneYZ;
  
  #define PULSE_TIME 1.16

  void main() {
    vCenter = center;
    vDisp = max(
      max(0., 1.-pow(3.*abs(uv.y-fract(-uTime*PULSE_TIME)+0.5),0.5)),
      1.-pow(3.*abs(uv.y-fract(-uTime*PULSE_TIME)-0.5),0.5)
    );
    // FIXME - magic numbers in displacement calculation
    vec4 scenePosition = modelViewMatrix*vec4(position+vec3(0.,1.,0.)*2.5*vDisp,1.);
    vSceneYZ = scenePosition.yz;
    gl_Position = projectionMatrix*scenePosition;
  }
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
  uniform float uResolutionScale;
  varying float vDisp;
  varying vec3 vCenter;
  varying vec2 vSceneYZ;

  #define PI 3.14159265359
  #define WIREFRAME_WIDTH 2.5
  
  // adapted from https://github.com/mrdoob/three.js/blob/dev/examples/webgl_materials_wireframe.html for wireframe effect
  float edgeFactorTri() {
    vec3 a3 = smoothstep(vec3(0.), fwidth(vCenter.xyz)*WIREFRAME_WIDTH/uResolutionScale, vCenter.xyz);
    return min(min(a3.x, a3.y), a3.z);
  }

  void main( void ) {
    if (edgeFactorTri() > 0.98) discard;
    vec3 color = mix(
      mix(
        mix(
          vec3(1.,0.,0.6), // magenta base
          vec3(1., 0.9, .0), min(1.9,vDisp) // yellow pulse
        ),
        vec3(1.), max(0., (vSceneYZ.s-20.) / 120.) // lighter on top; FIXME - magic numbers with Y position
      ),
      vec3(0.), max(0., min(1., (-vSceneYZ.t - 80.) / 80.)) // fade to black; FIXME - magic numbers with Z position
    );
    gl_FragColor = gl_FrontFacing ?
      vec4(color, 1.0) :
      vec4(color, 0.5);
  }
</script>
<div id="container"></div>
<div id="info">Synth Canyon Run - with <a href="https://threejs.org" target="_blank">three.js</a>
</div>
<div id="controls">
  <label for="resolution">resolution: </label>
  <select id="resolution" value="2">
    <option value="0.5">0.5x</option>
    <option value="1" selected>1x</option>
    <option value="2">2x</option>
    <option value="4">4x</option>
    <option value="8">8x</option>
  </select>
  <label for="hide-audio">hide audio: </label>
  <input id="hide-audio" type="checkbox"></input>
  <iframe class="audio-embed" width="350" height="83" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/450662742&color=%23a575d0&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true"></iframe>
</div>

              
            
!

CSS

              
                body {
  color: #ffffff;
  font-family: Monospace;
  font-size: 13px;
  text-align: center;
  font-weight: bold;
  background-color: #000;
  margin: 0px;
  overflow: hidden;
}
#info, #controls {
  position: absolute;
  width: 100%;
  padding: 5px;
  background: rgba(#000,0.4);
}
#info { top: 0; }
#controls { bottom: 0; }
a {
  color: #ffffff;
  transition: 150ms all;
  &:hover, &:focus {
    color: #ffc107;
  }
}

.stats-element {
  position: absolute;
  right: 0;
  top: 0;
}

.audio-embed {
  position: absolute;
  left: 0;
  bottom: 100%;
  border: 0;
  #hide-audio:checked + & {
    opacity: 0;
  }
}

              
            
!

JS

              
                const SHOW_STATS = false;
const CANYON_WIDTH = 400;
const CANYON_LENGTH = 120;
const CANYON_SEGMENTS_W = 27;
const CANYON_SEGMENTS_L = 10;
const CLIFF_BASE = 60;
const CLIFF_VARY = 15;
const FLOOR_VARY = 10;
const CANYON_SPEED = 70;
const CAMERA_DRIFT_DISTANCE = 15;
const CAMERA_DRIFT_SPEED = 0.05;

let lastUpdate;
let camera, scene, renderer, composer;
let cameraBaseX, cameraBaseY;
let uResolutionScale;
let uTime;
let canyonA, canyonB;

function init() {
  // stats
  if (SHOW_STATS) {
    const stats = new Stats();
    stats.domElement.classList.add('stats-element');
    document.body.appendChild(stats.domElement);
    requestAnimationFrame(function updateStats(){
      stats.update();
      requestAnimationFrame(updateStats);
    });
  }
  
  // basic setup
  const container = document.getElementById('container');
  camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 300);
  const cameraDistance = 70;
  const cameraAngle = .05*Math.PI;
  camera.position.z = cameraDistance;
  cameraBaseX = 0;
  cameraBaseY = 0.3 * (CLIFF_BASE + CLIFF_VARY + FLOOR_VARY);
  camera.position.y = cameraBaseY;
  camera.rotation.x = -cameraAngle;
  scene = new THREE.Scene();
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);

  // shader setup
  lastUpdate = new Date().getTime();
  const vertexShader = document.getElementById( 'vertexShader' ).textContent;
  const fragmentShader = document.getElementById( 'fragmentShader' ).textContent;
  uTime = { type: 'f', value: 1.0 };
  uResolutionScale = { type: 'f', value: 1.0 };
  
  // add objects
  const canyonGeometry = new THREE.PlaneGeometry(CANYON_WIDTH, CANYON_LENGTH, CANYON_SEGMENTS_W, CANYON_SEGMENTS_L);
  canyonGeometry.rotateX(-0.5 * Math.PI);
  const reverseGeometry = canyonGeometry.clone();
  const simplexA = new SimplexNoise(Math.floor(0xffff*Math.random()));
  const simplexB = new SimplexNoise(Math.floor(0xffff*Math.random()));
  for (let i = 0, l = canyonGeometry.vertices.length; i < l; i++) {
    const { x, z } = canyonGeometry.vertices[i];
    canyonGeometry.vertices[i].y =
      Math.min(1.0, Math.pow(x/50, 4)) * Math.round(CLIFF_BASE + simplexA.noise2D(x,z) * CLIFF_VARY) + Math.round(simplexB.noise2D(x,z) * FLOOR_VARY);
    reverseGeometry.vertices[i].y = 
      Math.min(1.0, Math.pow(x/50, 4)) * Math.round(CLIFF_BASE + simplexA.noise2D(x,-z) * CLIFF_VARY) + Math.round(simplexB.noise2D(x,-z) * FLOOR_VARY);
  }
  const canyonMaterial = new THREE.ShaderMaterial({
    transparent: true,
    side: THREE.DoubleSide,
    uniforms: { uTime, uResolutionScale },
    vertexShader,
    fragmentShader
  });
  canyonMaterial.extensions.derivatives = true;
  canyonA = new THREE.Mesh(geomToBufferGeomWithCenters(canyonGeometry), canyonMaterial);
  scene.add(canyonA);
  canyonB = new THREE.Mesh(geomToBufferGeomWithCenters(reverseGeometry), canyonMaterial);
  canyonB.position.z -= CANYON_LENGTH;
  scene.add(canyonB);
  container.appendChild(renderer.domElement);
  
  // effect composition
  const renderScene = new THREE.RenderPass(scene, camera);
  const bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.86);
  bloomPass.threshold = 0.3;
  bloomPass.strength = 2.5 * uResolutionScale.value;
  bloomPass.radius = 0.3 * uResolutionScale.value;
  composer = new THREE.EffectComposer(renderer);
  composer.addPass(renderScene);
  composer.addPass(bloomPass);
  
  // event listeners
  onWindowResize();
  window.addEventListener( 'resize', onWindowResize, false);
  document.getElementById('resolution').addEventListener('change', onResolutionChange, false);
}

// events
function onWindowResize(evt) {
  camera.aspect =  window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  composer.setSize(window.innerWidth, window.innerHeight);
}
function onResolutionChange(evt) {
  uResolutionScale.value = parseFloat(evt.target.value);
  bloomPass.strength = 2.5 * uResolutionScale.value;
  bloomPass.radius = 0.3 * uResolutionScale.value;
  renderer.setPixelRatio( window.devicePixelRatio / uResolutionScale.value );
}
function animate() {
  const currentTime = new Date().getTime();
  const timeSinceLastUpdate = currentTime - lastUpdate;
  lastUpdate = currentTime;
  const deltaTime = timeSinceLastUpdate / 1000;
  uTime.value += deltaTime;
  // move canyons
  canyonA.position.z += deltaTime * CANYON_SPEED;
  canyonB.position.z += deltaTime * CANYON_SPEED;
  if (canyonA.position.z > CANYON_LENGTH) {
    canyonA.position.z -= 2*CANYON_LENGTH;
  }
  if (canyonB.position.z > CANYON_LENGTH) {
    canyonB.position.z -= 2*CANYON_LENGTH;
  }
  // drift camera (simple lissajous)
  camera.position.x = cameraBaseX + CAMERA_DRIFT_DISTANCE*Math.sin(7*CAMERA_DRIFT_SPEED*Math.PI*uTime.value);
  camera.position.y = cameraBaseY + CAMERA_DRIFT_DISTANCE*Math.sin(5*CAMERA_DRIFT_SPEED*Math.PI*uTime.value);
  // render
  // renderer.render( scene, camera );
  composer.render();
  requestAnimationFrame( animate );
}

// boot
init();
animate();

// utils
// adapted from https://github.com/mrdoob/three.js/blob/dev/examples/webgl_materials_wireframe.html for wireframe effect
function geomToBufferGeomWithCenters(geom) {
  const buffGeom = new THREE.BufferGeometry().fromGeometry(geom);
  const vectors = [new THREE.Vector3(1,0,0), new THREE.Vector3(0,1,0), new THREE.Vector3(0,0,1)];
  const { position } = buffGeom.attributes;
  const centers = new Float32Array(position.count*3);
  for (let i=0, l=position.count; i<l; i++) {
    vectors[i%3].toArray(centers,i*3);
  }
  buffGeom.setAttribute('center', new THREE.BufferAttribute(centers,3));
  return buffGeom;
}

              
            
!
999px

Console