drawMesh = regl({
vert: `
precision highp float;
attribute vec2 uv;
uniform mat4 projection, view;
varying vec3 vPosition, vNormal;
varying vec2 vUV;
uniform float time;
// Our function!
vec3 f(vec2 uv) {
float r2 = dot(uv, uv);
float s = 12.0 * sqrt(r2);
float t = time * 4.0;
return vec3(
uv.x * 2.0,
cos(s - t) / sqrt(1.0 + s * s),
uv.y * 2.0
);
}
void main () {
vUV = uv.x * vec2(cos(uv.y), sin(uv.y));
vPosition = f(vUV);
// We differentiate the surface numerically to get the partial
// derivative wrt u and v, then take the cross product to get
// the surface normal.
float dx = 1e-2;
vec3 dpdu = f(vUV + vec2(dx, 0)) - vPosition;
vec3 dpdv = f(vUV + vec2(0, dx)) - vPosition;
vNormal = normalize(cross(dpdu, dpdv));
gl_Position = projection * view * vec4(vPosition, 1);
}
`,
frag: `
#extension GL_OES_standard_derivatives : enable
precision highp float;
varying vec3 vPosition, vNormal;
uniform vec3 eye;
uniform bool solidPass;
varying vec2 vUV;
uniform float pixelRatio, opacity, cartoonEdgeWidth, gridOpacity, specular, cartoonEdgeOpacity, gridWidth;
// This function implements a constant-width grid as a function of
// a two-dimensional input. This makes it possible to draw a grid
// which does not line up with the triangle edges.
// from: https://github.com/rreusser/glsl-solid-wireframe
float gridFactor (vec2 parameter, float width, float feather) {
float w1 = width - feather * 0.5;
vec2 d = fwidth(parameter);
vec2 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5);
vec2 a2 = smoothstep(d * w1, d * (w1 + feather), looped);
return min(a2.x, a2.y);
}
void main () {
vec3 normal = normalize(vNormal);
// The dot product of the view direction and surface normal.
float vDotN = abs(dot(normal, normalize(vPosition - eye)));
// We divide vDotN by its gradient magnitude in screen space to
// give a function which goes roughly from 0 to 1 over a single
// pixel right at glancing angles. i.e. cartoon edges!
float cartoonEdge = smoothstep(0.75, 1.25, vDotN / fwidth(vDotN) / (cartoonEdgeWidth * pixelRatio));
// Combine the gridlines and cartoon edges
float grid = gridFactor(vUV * 10.0, 0.5 * gridWidth * pixelRatio, 0.5);
float combinedGrid = max(cartoonEdgeOpacity * (1.0 - cartoonEdge), gridOpacity * (1.0 - grid));
if (solidPass) {
// If the surface pass, we compute some shading
float shade = 0.2 + mix(1.2, specular * pow(vDotN, 3.0), 0.5);
vec3 colorFromNormal = (0.5 - (gl_FrontFacing ? 1.0 : -1.0) * 0.5 * normal);
vec3 baseColor = gl_FrontFacing ? vec3(0.1, 0.4, 0.8) : vec3(0.9, 0.2, 0.1);
vec3 color = shade * mix(
baseColor,
colorFromNormal,
0.4
);
// Apply the gridlines
color = mix(color, vec3(0), opacity * combinedGrid);
gl_FragColor = vec4(pow(color, vec3(0.454)), 1.0);
} else {
// If the wireframe pass, we just draw black lines with some alpha
gl_FragColor = vec4(
// To get the opacity to mix ~correctly, we use reverse-add blending mode
// so that white here shows up as black gridlines. This could be simplified
// by doing a bit more math to get the mixing right with just additive blending.
vec3(1),
(1.0 - opacity) * combinedGrid
);
if (gl_FragColor.a < 1e-3) discard;
}
}
`,
primitive: 'triangles',
uniforms: {
solidPass: regl.prop('solidPass'),
opacity: regl.prop('opacity'),
cartoonEdgeWidth: regl.prop('cartoonEdgeWidth'),
cartoonEdgeOpacity: regl.prop('cartoonEdgeOpacity'),
gridOpacity: regl.prop('gridOpacity'),
gridWidth: regl.prop('gridWidth'),
specular: regl.prop('specular')
},
attributes: {
uv: regl.prop('buffers.uv')
},
depth: {
enable: regl.prop('solidPass')
},
blend: {
enable: (ctx, props) => !props.solidPass,
func: {
srcRGB: 'src alpha',
srcAlpha: 1,
dstRGB: 1,
dstAlpha: 1
},
equation: {
rgb: 'reverse subtract',
alpha: 'add'
}
},
elements: regl.prop('buffers.elements')
})