glsl ball test

by manland

HTML

<script src="https://raw.github.com/gre/glsl.js/master/glsl.min.js"></script>
<canvas id="viewport" width="600" height="400"></canvas>

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

#ifdef GL_ES
precision mediump float;
#endif

uniform float time;
uniform vec3 camera;
uniform vec2 mouse;
uniform vec2 resolution;

float deSphere(vec3 p, float r)
{
	return length(p) - r;
}

float de(vec3 p) 
{
	return deSphere(p, 1.0);
}

vec3 normal(in vec3 pos)
{
	float f = de(pos);
	vec3 e = vec3(0.001, 0.0, 0.0);
	vec3 n;
		
	n.x = de(pos + e.xyy) - de(pos - e.xyy);
	n.y = de(pos + e.yxy) - de(pos - e.yxy);
	n.z = de(pos + e.yyx) - de(pos - e.yyx);  
	
	return normalize(n);
}
void main( void ) 
{
	const float epsilon = 0.01;
	vec2 position = ((( gl_FragCoord.xy / resolution.xy ) * 2.0) - 1.0) * vec2(resolution.x / resolution.y, 1.0);
	vec3 ray = vec3(0.0, 0.0, -2.0);
	vec3 cam = vec3(position.x + camera.x, position.y + camera.y, camera.z);
	vec3 direction = normalize(cam);
	vec4 color = vec4(0.0);
	vec2 m = (mouse * 2.0) - 1.0;
	vec3 light = vec3(m.x, m.y, -2.5) * vec3(2.0, 2.0, 1.0);
	
	for(int i = 0; i < 10; i++) {
		float dist = de(ray);
		if(dist < epsilon) {
			vec3 n = normal(ray);
			vec3 l = normalize(light - ray);
			
			// diffuse
			float lt = clamp(dot(l, n), 0.0, 1.0);
			color = lt * vec4(1.0);
			
			// light attenuation
			float atte = 1.0 / length(light - ray);
			color *= atte;
			
			// specular
			vec3 e = -direction;
			vec3 h = normalize(l + e);
			float s = pow(dot(h, n), 20.0) * 0.5;
			color += vec4(s);
			
			break;
		}		
		
		ray += 0.75 * direction * dist;
	}
	
	gl_FragColor = vec4(color.xyz, 1.0);
}

</script>

CSS

body {
    background-color:black;
}

canvas {
    border-radius:50px;
    margin:auto;
    display:block;
}

JavaScript

var mouse = {"x":0.0,"y":0.0};
var camera = {"x":0.0,"y":0.0,"z":1.0};
var viewport = document.getElementById("viewport");

viewport.onmousemove = function(evt) {
    console.log(evt);
    mouse.x = (evt.clientX - viewport.offsetLeft) / viewport.clientWidth;
    mouse.y = 1 - (evt.clientY / viewport.clientHeight);
}

document.onkeypress = function(e) {
    e = e || window.event;
    if (e.keyCode == 37) {
        camera.x = camera.x + 0.01;
    } 
    if(e.keyCode == 39) {
        camera.x = camera.x - 0.01;
    }
    if(e.keyCode == 38) {
        camera.y = camera.y - 0.01;
    }
    if(e.keyCode == 40) {
        camera.y = camera.y + 0.01;
    }
    //alert("Character typed: " + e.keyCode);
};

viewport.addEventListener('DOMMouseScroll', function(evt) {
    var delta=evt.detail? evt.detail*(-120) : evt.wheelDelta;
    if(delta > 0) {
        camera.z = camera.z + 0.01;
    } else {
        camera.z = camera.z - 0.01;
    }
}, false);

var glsl = Glsl({
    canvas: viewport,
    fragment: document.getElementById("fragment").innerHTML,
    variables: {
        time: 0, // The time in ms
        mouse: mouse,
        camera: camera
    },
    update: function (time) {
        this.sync("mouse");
        this.sync("camera");
    }
  }).start();