Я недавно пересматривал старые книги на днях. В книге Commodore Step by Step Graphics Vol 3 я наткнулся на интересно выглядящий скриншот с небольшим по размеру кодом. На нём была показана затенённая сфера полностью сделанная из точек.
Я подумал, что это будет выглядеть круто, если нарисовать их поверх другу друга, чтобы они выглядели как планеты и луны. Поэтому я перевёл Basic код в JavaScript и использовал canvas для воспроизведения эффекта.
Вот как выглядит результат:
И так, как достичь подобного эффекта. Вот функция для создания готовых планет:
function drawPlanet(rad, xc, yc, color) {
ctx.fillStyle = "#000";
ctx.beginPath();
ctx.arc(xc, yc, rad, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
ctx.fillStyle = "rgb(" + color.r + "," + color.g + "," + color.b + ")";
var x1 = parseInt(Math.sqrt(rad * rad - y * y));
for (var x = -x1; x < x1; x++) {
var n = parseInt(Math.random() * x1) / 1.5;
if (n > x1 + x) {
ctx.fillRect(x + xc, y + yc, 1, 1);
}
}
}
Функция drawPlaten
получает четыре параметра:
- Радиус
- Цвет планеты (RGB по каналам),
- Координат центра -- x
- Координат центра -- y
ctx.beginPath();
ctx.arc(xc, yc, rad, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
В начале мы рисуем сплошной круг для достижения правильного эффекта перекрытия, в противном случае планеты неправильно перекрывали друг друга. Можете посмотреть на скриншот выше и представить, что точки красной планеты были на жёлтой справа.
Цикле от минус радианы до радианы - это место где и происходит вся магия. На каждой итерации цикла, когда мы идём по кругу, мы вычисляем как далеко строка от центра в x и y до края круга.
У нас есть две точки: радиус (центральная точка) и Y. Таким образом, используя теорему Пифагора ({{< tex >}}a^2 + b^2 = c^2{{< /tex >}}). Мы знаем a, c и можем найти b.
Посмотреть на код, как это работает.
Мы имеем точку, которая находится на краях круга, от (radius, radius+y). Сейчас мы можем проитерироваться от -x
до x
и получить каждый пиксель между краями круга, на этом строки.
Теперь, когда у нас есть ширина строки, на которой мы находимся, мы можем перебрать каждый пиксель. Следующий шаг, определить какие пиксели надо отросовать, а какие нет. Это делается дальше по коду функции drawPlanet
.
for (var x = -x1; x < x1; x++) {
var n = parseInt(Math.random() * x1) / 1.5;
if (n > x1 + x) {
ctx.fillRect(x + xc, y + yc, 1, 1);
}
}
Переменная n
хранит случайное значение между 0 и x1. Чтобы определить нужно ли нам нарисовать точку, мы проверяем n
больше x1 + x
. Точка будет нарисована только если n > x +x1, что означает x преодолел барьер. Ближе к концу цикла вероятность нарисовать точку увеличивается. Если больше ничего не добавлять, получим прикольный эффект. Пиксели случайно закрашены для половины планеты.
На этом моменте пример из книжки закончился. Однако я хотел добавить больше сферического эффекта. Все что нам нужно - это немного разделить значение.
var n = parseInt(Math.random() * x1) / 1.5;
Я использовал значение 1.5, что уменьшает вероятность нарисовать точку и сдвигает их больше к краю. Так как значение x1 следует за шириной круга, мы получаем крутой эффект сферического затенения.
Ниже примеры затенения, которые можно получить изменяя значение делителя.
И это все, что нам нужно. Довольно простой эффект, использующий немного математики и случайных значений.
See the Pen Planets with points by Loktar (@loktar00) on CodePen.
Новые слова:
- entirely -- полностью
- exactly -- в точку
- solid -- сплошной, твёрдый
- otherwise -- иначе, в противном случае
- able -- способны, в состоянии
https://github.com/grishy/blog/blob/hugo/content/post/3d-shading-with-points.md