# \[Перевод] Трёхмерные планеты из точек

> Нашел старый код на Commodore BASIC и решил переписать его на JavaScript. Результат? Стильные 3D-планеты из тысяч точек на Canvas с процедурным затенением. Показываю алгоритм.

**Published:** 2018-04-22

**Canonical:** <https://grishy.dev/ru/posts/3d-shading-with-points/>

**Translations:** [Русский](https://grishy.dev/ru/posts/3d-shading-with-points.md)

![\[Перевод\] Трёхмерные планеты из точек](https://grishy.dev/_astro/cover.D7P6PcY6.avif)

---

Я недавно пересматривал старые книги на днях. В книге *Commodore Step by Step Graphics Vol 3* я наткнулся на интересно выглядящий скриншот с небольшим по размеру кодом. На нём была показана затенённая сфера полностью сделанная из точек.

Я подумал, что это будет выглядеть круто, если нарисовать их поверх друг друга, чтобы они выглядели как планеты и луны. Поэтому я перевёл Basic код в JavaScript и использовал canvas для воспроизведения эффекта.

Вот как выглядит результат:\
![3D планеты из точек на canvas](https://grishy.dev/_astro/result.utQ-Jfbv.avif)

И так, как достичь подобного эффекта. Вот функция для создания готовых планет:

```js
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);
    }
  }
}
```

Функция `drawPlanet` получает четыре параметра:

- Радиус
- Цвет планеты (RGB по каналам),
- Координат центра -- **x**
- Координат центра -- **y**

```js
ctx.beginPath();
ctx.arc(xc, yc, rad, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
```

В начале мы рисуем сплошной круг для достижения правильного эффекта перекрытия, в противном случае планеты неправильно перекрывали друг друга. Можете посмотреть на скриншот выше и представить, что точки красной планеты были на жёлтой справа.

Цикл от минус радианы до радианы - это место, где и происходит вся магия. На каждой итерации цикла, когда мы идём по кругу, мы вычисляем, как далеко строка от центра в *x* и *y* до края круга.

У нас есть две точки: радиус (центральная точка) и Y. Таким образом, используя теорему Пифагора ($a^2 + b^2 = c^2$). Мы знаем *a*, *c* и можем найти *b*.

![Теорема Пифагора для вычисления края круга](https://grishy.dev/_astro/radfig1.CKsUxtoX.avif)

[Посмотреть на код, как это работает.](https://jsfiddle.net/loktar/yJhLu/)

Мы имеем точку, которая находится на краях круга, от *(radius, radius+y)*. Сейчас мы можем проитерироваться от `-x` до `x` и получить каждый пиксель между краями круга, на этом строки.

Теперь, когда у нас есть ширина строки, на которой мы находимся, мы можем перебрать каждый пиксель. Следующий шаг, определить какие пиксели надо отрисовать, а какие нет. Это делается дальше по коду функции `drawPlanet`.

```js
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 преодолел барьер. Ближе к концу цикла вероятность нарисовать точку увеличивается. Если больше ничего не добавлять, получим прикольный эффект. Пиксели случайно закрашены для половины планеты.

![Случайное рисование точек на полусфере](https://grishy.dev/_astro/example.D_ILjjus.avif)

На этом моменте пример из книжки закончился. Однако я хотел добавить больше сферического эффекта. Все что нам нужно - это немного разделить значение.

```js
var n = parseInt(Math.random() * x1) / 1.5;
```

Я использовал значение 1.5, что уменьшает вероятность нарисовать точку и сдвигает их больше к краю. Так как значение x1 следует за шириной круга, мы получаем крутой эффект сферического затенения.

Ниже примеры затенения, которые можно получить изменяя значение делителя.
![Варианты затенения планет при разных делителях](https://grishy.dev/_astro/planet-phases.BjjSmH_N.avif)

И это все, что нам нужно. Довольно простой эффект, использующий немного математики и случайных значений.

See the Pen [Planets with points](https://codepen.io/loktar00/pen/zEhDB/) by Loktar (
[@loktar00](https://codepen.io/loktar00)) on [CodePen](https://codepen.io).

---

**Thumbnail:** Photo by [Vero Photoart](https://unsplash.com/@verophotoart) on [Unsplash](https://unsplash.com/@verophotoart).
