您可以使用一些三角函数来模拟带有光源的 3D 点。
这是一个在线演示
这是您可以做到的一种方式,当然还有其他方式(这是第一个想到的):
- 在主画布上用一些点绘制网格
- 将径向渐变渲染到屏幕外画布
- 更改合成模式,以便在现有像素上绘制任何内容
- 计算到光源的距离和角度,并将点绘制到每个网格点偏移角度/距离。
这是演示中的一些代码。
用点绘制网格
我们跳过一个网格点,因为稍后我们将用渐变点填充每个点,否则会在相邻点上绘制。
/// draw a grid of dots:
for (y = 0; y < ez.width; y += gridSize * 2) {
for (x = 0; x < ez.height; x += gridSize * 2) {
ctx.beginPath();
ctx.arc(x + offset, y + offset, radius, 0, arcStop);
ctx.closePath();
ctx.fill();
}
}
创造光“反射”
将渐变点准备到屏幕外的画布(dctx
= dot-context)。我正在使用easyCanvas进行设置,并给我一个已经计算出中心点的屏幕外画布,但当然也可以手动设置:
grd = dctx.createRadialGradient(dot.centerX, dot.centerY, 0,
dot.centerX, dot.centerY, gridSize);
grd.addColorStop(0, '#fff');
grd.addColorStop(0.2, '#777'); // thighten up
grd.addColorStop(1, '#000');
dctx.fillStyle = grd;
dctx.fillRect(0, 0, gridSize, gridSize);
算一算
然后我们进行所有的计算和偏移:
/// change composition mode
ctx.globalCompositeOperation = 'source-atop';
/// calc angle and distance to light source and draw each
/// dot gradient offset in relation to this
for (y = 0; y < ez.width; y += gridSize) {
for (x = 0; x < ez.height; x += gridSize) {
/// angle
angle = Math.atan2(lightY - y, lightX - x);
//if (angle < 0) angle += 2;
/// distance
dx = lightX - x;
dy = lightY - y;
dist = Math.sqrt(dx * dx + dy * dy);
/// map distance to our max offset
od = dist / maxLength * maxOffset * 2;
if (od > maxOffset * 2) od = maxOffset * 2;
/// now get new x and y position based on angle and offset
offsetX = x + od * Math.cos(angle) - maxOffset * 0.5;
offsetY = y + od * Math.sin(angle) - maxOffset * 0.5;
/// draw the gradient dot at offset
ctx.drawImage(dot.canvas, x + offsetX, y + offsetY);
}
}
阴影
destination-over
对于阴影,您只需在使用将在已绘制像素之外绘制的合成模式时反转偏移量:
/// Shadow, same as offsetting light, but in opposite
/// direction and with a different composite mode
ctx.globalCompositeOperation = 'destination-over';
for (y = 0; y < ez.width; y += gridSize) {
for (x = 0; x < ez.height; x += gridSize) {
/// angle
angle = Math.atan2(lightY - y, lightX - x);
//if (angle < 0) angle += 2;
/// distance
dx = lightX - x;
dy = lightY - y;
dist = Math.sqrt(dx * dx + dy * dy);
/// map distance to our max offset
od = dist / maxLength * maxOffset * 2;
if (od > maxOffset * 4) od = maxOffset * 4;
/// now get new x and y position based on angle and offset
offsetX = x - od * Math.cos(angle) + gridSize * 0.5;
offsetY = y - od * Math.sin(angle) + gridSize * 0.5;
ctx.beginPath();
ctx.arc(x + offsetX, y + offsetY, radius, 0, arcStop);
ctx.fill();
}
}
当然,这都可以优化为单个循环对,但为了概述,代码是分开的。
额外的
在演示中,我添加了鼠标跟踪,因此鼠标成为光源,您可以在移动鼠标时看到点反射的变化。为了获得最佳性能,请使用 Chrome。
为了满足您的需要,只需缩小我正在使用的值 - 或 - 绘制到一个大的屏幕外画布并使用drawImage
将其缩小到主画布。