Я недавно пересматривал старые книги на днях. В книге 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 получает четыре параметра:

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.


Новые слова:


https://github.com/grishy/blog/blob/hugo/content/post/3d-shading-with-points.md