<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>HTML5+WebGL实现3D透明杯子效果</title>
<style>
.dg.ac {
z-index: 9999 !important;
}
.explanation {
font-family: Arial, sans-serif;
font-size: 90%;
position: fixed;
bottom: 6px;
left: 6px;
padding: 10px;
width: 50%;
border-radius: 6px;
background: rgba(255, 255, 255, 0.7);
z-index: 9999;
}
.explanation a {
color: rgb(28, 154, 190);
}
</style>
</head>
<body>
<script id="vertexShader" type="webgl/vertex-shader">
uniform vec3 ray00;
uniform vec3 ray10;
uniform vec3 ray01;
uniform vec3 ray11;
varying vec3 initialRay;
void main() {
vec2 t = gl_Vertex.xy * 0.5 + 0.5;
initialRay = mix(mix(ray00, ray10, t.x), mix(ray01, ray11, t.x), t.y);
gl_Position = gl_Vertex;
}
</script>
<script id="fragmentShader" type="webgl/fragment-shader">
const float INFINITY = 1.0e9;
const float MUG_RADIUS = {{MUG_RADIUS}};
const float SAUCER_RADIUS = {{SAUCER_RADIUS}};
const float MUG_HEIGHT = {{MUG_HEIGHT}};
const float MUG_GAP = {{MUG_GAP}};
// TODO: Do we need to ensure precalculation of all of these or is compiler
// going to do that for us? (cannot define non-constant consts in Firefox)
#define A_SQUARED (MUG_RADIUS * MUG_RADIUS)
#define B_SQUARED (SAUCER_RADIUS * SAUCER_RADIUS - MUG_RADIUS * MUG_RADIUS)
#define HANDLE_RADIUS (MUG_HEIGHT / 4.0)
#define HANDLE_WIDTH (min(MUG_RADIUS / 2.0, 0.2))
#define HANDLE_HEIGHT (MUG_HEIGHT / 2.0 + MUG_GAP)
#define HANDLE_OFFSET (sqrt(A_SQUARED * (1.0 + ((HANDLE_HEIGHT - MUG_GAP) * (HANDLE_HEIGHT - MUG_GAP)) / B_SQUARED)))
#define R_SQUARED (HANDLE_RADIUS * HANDLE_RADIUS)
const int SURFACE_SAUCER = 1;
const int SURFACE_MUG = 2;
const int SURFACE_MUG_INSIDE = 3;
const int SURFACE_HANDLE = 4;
const int SURFACE_HANDLE_INSIDE = 5;
// An intersection is a solution t where origin + t * ray intersects
// one of our surfaces
struct intersection {
float t;
vec3 hit;
int surfaceType;
};
uniform vec3 eye;
varying vec3 initialRay;
intersection intersectMug(vec3 origin, vec3 ray) {
intersection P;
P.t = INFINITY;
float origin_y = origin.y - MUG_GAP;
float a = (ray.x * ray.x + ray.z * ray.z) / A_SQUARED - (ray.y * ray.y) / B_SQUARED;
float b = 2.0 * (origin.x * ray.x + origin.z * ray.z) / A_SQUARED - 2.0 * (origin_y * ray.y) / B_SQUARED;
float c = (origin.x * origin.x + origin.z * origin.z) / A_SQUARED - (origin_y * origin_y) / B_SQUARED - 1.0;
float discriminant = (b * b) - 4.0 * a * c;
float t;
if (discriminant >= 0.0) {
t = (-b - sqrt(discriminant)) / (2.0 * a);
if (t > 0.0) {
P.t = t;
P.hit = origin + ray * t;
P.surfaceType = SURFACE_MUG;
if (P.t < 0.01 || P.hit.y < MUG_GAP || P.hit.y > MUG_HEIGHT + MUG_GAP) {
P.t = (-b + sqrt(discriminant)) / (2.0 * a);
P.hit = origin + ray * P.t;
P.surfaceType = SURFACE_MUG_INSIDE;
if (P.t < 0.01 || P.hit.y < MUG_GAP || P.hit.y > MUG_HEIGHT + MUG_GAP) {
P.t = INFINITY;
}
}
}
}
return P;
}
intersection intersectHandle(vec3 origin, vec3 ray) {
intersection P;
P.t = INFINITY;
float origin_y = origin.y - HANDLE_HEIGHT;
float origin_x = origin.x - HANDLE_OFFSET;
float a = (ray.x * ray.x + ray.y * ray.y) / R_SQUARED;
float b = 2.0 * (origin_x * ray.x + origin_y * ray.y) / R_SQUARED;
float c = (origin_x * origin_x + origin_y * origin_y) / R_SQUARED - 1.0;
float discriminant = (b * b) - 4.0 * a * c;
float t;
if (discriminant > 0.0) {
t = (-b - sqrt(discriminant)) / (2.0 * a);
P.t = t;
P.hit = origin + ray * t;
P.surfaceType = SURFACE_HANDLE;
if (P.t < 0.001 || P.hit.z < - HANDLE_WIDTH || P.hit.z > HANDLE_WIDTH || ((P.hit.x * P.hit.x + P.hit.z * P.hit.z) / A_SQUARED - ((P.hit.y - MUG_GAP) * (P.hit.y - MUG_GAP)) / B_SQUARED < 1.0)) {
P.t = (-b + sqrt(discriminant)) / (2.0 * a);
P.hit = origin + ray * P.t;
P.surfaceType = SURFACE_HANDLE_INSIDE;
if (P.t < 0.001 || P.hit.z < - HANDLE_WIDTH || P.hit.z > HANDLE_WIDTH || ((P.hit.x * P.hit.x + P.hit.z * P.hit.z) / A_SQUARED - ((P.hit.y - MUG_GAP) * (P.hit.y - MUG_GAP)) / B_SQUARED < 1.0)) {
P.t = INFINITY;
}
}
}
return P;
}
intersection intersectSaucer(vec3 origin, vec3 ray) {
intersection P;
float t = -origin.y / ray.y;
vec3 hit = origin + ray * t;
if (t < 0.0 || (hit.x * hit.x + hit.z * hit.z) > SAUCER_RADIUS * SAUCER_RADIUS) {
t = INFINITY;
}
P.t = t;
P.hit = hit;
P.surfaceType = SURFACE_SAUCER;
return P;
}
void main() {
vec3 origin = eye, ray = initialRay, color = vec3(0.0);
vec3 mask = vec3(1.0);
intersection P, saucerP, mugP, handleP;
vec3 hit;
float t;
for (int bounce = 0; bounce < 2; bounce++) {
saucerP = intersectSaucer(origin, ray);
mugP = intersectMug(origin, ray);
{{IFHANDLE}}handleP = intersectHandle(origin, ray);
{{IFHANDLE}}t = min(min(saucerP.t, mugP.t), handleP.t);
{{IFNOTHANDLE}}t = min(saucerP.t, mugP.t);
if (t == INFINITY) {
if (bounce == 0) {
color += vec3(0.109, 0.604, 0.745) + (ray.y + 1.3) / 2.0 * vec3(-0.04, 0.145, -0.04);
} else {
color += mask;
}
break;
}
if (t == mugP.t) {
P = mugP;
} else if (t == handleP.t) {
P = handleP;
} else {
P = saucerP;
}
if (P.surfaceType == SURFACE_SAUCER) {
/* Look up the checkerboard color */
vec3 c = fract(P.hit * 2.0) - 0.5;
color += c.x * c.z > 0.0 ? vec3(0.161, 0.073, 0.065) : vec3(0.968, 0.894, 0.839) * mask;
break;
} else if (P.surfaceType == SURFACE_MUG) {
vec3 normal = vec3(P.hit.x / A_SQUARED, - P.hit.y / B_SQUARED, P.hit.z / A_SQUARED);
normal = normal / sqrt(dot(normal, normal));
ray = reflect(ray, normal);
origin = P.hit;
mask = vec3(0.501, 0.403, 0.385) - ((P.hit.y - MUG_GAP) / MUG_HEIGHT) * vec3(0.27, 0.27, 0.27);
} else if (P.surfaceType == SURFACE_MUG_INSIDE) {
color = vec3(0.468, 0.394, 0.339) + ((P.hit.y - MUG_GAP) / MUG_HEIGHT) * vec3(0.43, 0.43, 0.43);
break;
} else if (P.surfaceType == SURFACE_HANDLE || P.surfaceType == SURFACE_HANDLE_INSIDE) {
vec3 normal = vec3((P.hit.x - HANDLE_OFFSET) / R_SQUARED, (P.hit.y - HANDLE_HEIGHT) / R_SQUARED, 0);
normal = normal / sqrt(dot(normal, normal));
if (P.surfaceType == SURFACE_HANDLE) normal = - normal;
ray = reflect(ray, normal);
origin = P.hit;
mask = vec3(0.501, 0.403, 0.385) - ((P.hit.y - MUG_GAP) / MUG_HEIGHT) * vec3(0.27, 0.27, 0.27);
}
}
gl_FragColor = vec4(color, 1.0);
}
</script>
<script src="js/lightgl.js"></script>
<script src="js/dat.gui.min.js"></script>
<script>
var angleX = 30;
var angleY = 11;
var gl = GL.create();
var mesh = GL.Mesh.plane();
var vertexShaderScript = document.getElementById('vertexShader');
var fragmentShaderScript = document.getElementById('fragmentShader');
var Params = function() {
this.MUG_RADIUS = 0.70;
this.SAUCER_RADIUS = 1.90;
this.MUG_HEIGHT = 2.40;
this.MUG_GAP = 0.00;
this.HANDLE = true;
this.loadNewShader = function() {
var fragmentShader = fragmentShaderScript.innerHTML
.replace(/{{MUG_RADIUS}}/g, this.MUG_R