Published
Edited
Mar 29, 2021
2 forks
30 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
camera.taint();
const meshProps = {
buffers,
opacity,
specular,
cartoonEdgeWidth,
cartoonEdgeOpacity,
gridWidth,
gridOpacity
};
const frame = regl.frame(() => {
camera(({ dirty }) => {
if (!dirty && !drawContinuously) return;
setUniforms(() => {
regl.clear({ color: [1, 1, 1, 1] });

// Draw the solid surface
if (~passes.indexOf('Solid surface pass')) {
drawMesh({ ...meshProps, solidPass: true });
}

// The second pass with only the wireframe and edges
if (~passes.indexOf('Fake transparency pass')) {
drawMesh({ ...meshProps, solidPass: false });
}
});
});
});
invalidation.then(() => frame.cancel());
}
Insert cell
createREGL = require('regl')
Insert cell
mesh = meshFromFunction((u, v) => [u, v], {
resolution: [151, 151],
uDomain: [0, 1.5],
vDomain: [-Math.PI, Math.PI],
uPeriodic: false,
vPeriodic: true
})
Insert cell
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')
})
Insert cell
setUniforms = regl({
uniforms: {
projection: regl.context('projection'),
view: regl.context('view'),
eye: regl.context('eye'),
pixelRatio: regl.context('pixelRatio'),
time: (ctx, context) => (drawContinuously ? ctx.time : 0)
}
})
Insert cell
buffers = {
const uv = regl.buffer(mesh.positions.flat());
const elements = regl.elements(mesh.cells.flat());
invalidation.then(() => {
uv.destroy();
elements.destroy();
});
return { uv, elements };
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more