block by mbostock 5448729

Skybox

Full Screen

This is probably not how you’re supposed to implement a skybox… but it appears to work. Texture by Keith Lantz.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  margin: 0;
  overflow: hidden;
}

</style>
<canvas></canvas>
<script id="vertex-shader" type="x-shader/x-vertex">

attribute vec2 a_position;

void main(void) {
  gl_Position = vec4(a_position, 0.0, 1.0);
}

</script>
<script id="fragment-shader" type="x-shader/x-fragment">

precision mediump float;

uniform samplerCube u_image;
uniform vec2 u_translate;
uniform float u_scale;
uniform vec2 u_rotate;

const float c_pi = 3.14159265358979323846264;
const float c_halfPi = c_pi * 0.5;
const float c_twoPi = c_pi * 2.0;

float cosphi0 = cos(u_rotate.y);
float sinphi0 = sin(u_rotate.y);

void main(void) {
  float x = (gl_FragCoord.x - u_translate.x) / u_scale;
  float y = (u_translate.y - gl_FragCoord.y) / u_scale;

  // inverse stereographic projection
  float rho = sqrt(x * x + y * y);
  float c = 2.0 * atan(rho);
  float sinc = sin(c);
  float cosc = cos(c);
  float lambda = atan(x * sinc, rho * cosc);
  float phi = asin(y * sinc / rho);

  // inverse rotation
  float cosphi = cos(phi);
  float x1 = cos(lambda) * cosphi;
  float y1 = sin(lambda) * cosphi;
  float z1 = sin(phi);
  lambda = atan(y1, x1 * cosphi0 + z1 * sinphi0) + u_rotate.x;
  phi = asin(z1 * cosphi0 - x1 * sinphi0);

  // convert to cartesian coordinates
  float cosphi2 = cos(phi);
  float x2 = -cosphi2 * sin(lambda);
  float y2 = -sin(phi);
  float z2 = cosphi2 * cos(lambda);

  gl_FragColor = textureCube(u_image, vec3(x2, y2, z2), -0.5);
}

</script>
<script src="//d3js.org/queue.v1.min.js"></script>
<script>

// Select the canvas from the document.
var canvas = document.querySelector("canvas");

// Create the WebGL context, with fallback for experimental support.
var context = canvas.getContext("webgl")
    || canvas.getContext("experimental-webgl");

// Compile the vertex shader.
var vertexShader = context.createShader(context.VERTEX_SHADER);
context.shaderSource(vertexShader, document.querySelector("#vertex-shader").textContent);
context.compileShader(vertexShader);
if (!context.getShaderParameter(vertexShader, context.COMPILE_STATUS)) throw new Error(context.getShaderInfoLog(vertexShader));

// Compile the fragment shader.
var fragmentShader = context.createShader(context.FRAGMENT_SHADER);
context.shaderSource(fragmentShader, document.querySelector("#fragment-shader").textContent);
context.compileShader(fragmentShader);
if (!context.getShaderParameter(fragmentShader, context.COMPILE_STATUS)) throw new Error(context.getShaderInfoLog(fragmentShader));

// Link and use the program.
var program = context.createProgram();
context.attachShader(program, vertexShader);
context.attachShader(program, fragmentShader);
context.linkProgram(program);
if (!context.getProgramParameter(program, context.LINK_STATUS)) throw new Error(context.getProgramInfoLog(program));
context.useProgram(program);

// Define the positions (as vec2) of the square that covers the canvas.
var positionBuffer = context.createBuffer();
context.bindBuffer(context.ARRAY_BUFFER, positionBuffer);
context.bufferData(context.ARRAY_BUFFER, new Float32Array([
    -1.0, -1.0,
    +1.0, -1.0,
    +1.0, +1.0,
    -1.0, +1.0
  ]), context.STATIC_DRAW);

// Bind the position buffer to the position attribute.
var positionAttribute = context.getAttribLocation(program, "a_position");
context.enableVertexAttribArray(positionAttribute);
context.vertexAttribPointer(positionAttribute, 2, context.FLOAT, false, 0, 0);

// Extract the projection parameters.
var translateUniform = context.getUniformLocation(program, "u_translate"),
    scaleUniform = context.getUniformLocation(program, "u_scale"),
    rotateUniform = context.getUniformLocation(program, "u_rotate");

// Load the cubemap texture.
queue()
    .defer(image, "xpos.png")
    .defer(image, "xneg.png")
    .defer(image, "ypos.png")
    .defer(image, "yneg.png")
    .defer(image, "zpos.png")
    .defer(image, "zneg.png")
    .await(texturize);

self.onresize = resize;

function image(path, callback) {
  var image = new Image;
  image.src = path;
  image.onload = function() { callback(null, image); };
}

function texturize(error, xpos, xneg, ypos, yneg, zpos, zneg) {
  var texture = context.createTexture();
  context.bindTexture(context.TEXTURE_CUBE_MAP, texture);
  context.texParameteri(context.TEXTURE_CUBE_MAP, context.TEXTURE_WRAP_S, context.CLAMP_TO_EDGE);
  context.texParameteri(context.TEXTURE_CUBE_MAP, context.TEXTURE_WRAP_T, context.CLAMP_TO_EDGE);
  context.texParameteri(context.TEXTURE_CUBE_MAP, context.TEXTURE_MAG_FILTER, context.LINEAR);
  context.texParameteri(context.TEXTURE_CUBE_MAP, context.TEXTURE_MIN_FILTER, context.LINEAR_MIPMAP_LINEAR);
  context.texImage2D(context.TEXTURE_CUBE_MAP_POSITIVE_X, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, xpos);
  context.texImage2D(context.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, xneg);
  context.texImage2D(context.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, ypos);
  context.texImage2D(context.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, yneg);
  context.texImage2D(context.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, zpos);
  context.texImage2D(context.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, zneg);
  context.generateMipmap(context.TEXTURE_CUBE_MAP);

  // Hack to ensure correct inference of window dimensions.
  setTimeout(function() {
    resize();
    animate();
  }, 10);
}

function resize() {
  var width = Math.max(960, self.innerWidth),
      height = Math.max(500, self.innerHeight);
  canvas.setAttribute("width", width);
  canvas.setAttribute("height", height);
  context.uniform2f(translateUniform, width / 2, height / 2);
  context.uniform1f(scaleUniform, 500);
  context.viewport(0, 0, width, height);
}

function animate() {
  // The current rotation and speed.
  var rotate = [0, 1],
      speed = [-.001, -.0004];

  redraw();

  // Rotate and redraw!
  function redraw() {
    rotate[0] += speed[0], rotate[1] += speed[1];
    context.uniform2fv(rotateUniform, rotate);
    context.drawArrays(context.TRIANGLE_FAN, 0, 4);
    requestAnimationFrame(redraw);
  }
}

// A polyfill for requestAnimationFrame.
if (!self.requestAnimationFrame) requestAnimationFrame =
    self.webkitRequestAnimationFrame
    || self.mozRequestAnimationFrame
    || self.msRequestAnimationFrame
    || self.oRequestAnimationFrame
    || function(f) { setTimeout(f, 17); };

</script>