This is probably not how you’re supposed to implement a skybox… but it appears to work. Texture by Keith Lantz.
Last active
February 9, 2016 02:06
-
-
Save mbostock/5448729 to your computer and use it in GitHub Desktop.
Skybox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment