
Учась в школе, я обнаружил очень простую математическую формулу, о которой не перестаю думать и сегодня. Смысл её в следующем: представьте, что у вас есть 3D-точка в воображаемом 3D-пространстве за экраном. Для проецирования этой 3D-точки на экран нужно взять её координату X, поделённую на Z, и аналогично её Y / Z. И в результате вы получите проекцию точки на экран: и
. А если у вас есть множество точек в этом 3D-пространстве за экраном, и вы начнёте их анимировать и вращать их, а потом воспользуетесь этой формулой для рендеринга всех точек на экране, то это будет выглядеть, как 3D-сцена или 3D-объект. Давайте попробуем эту формулу в деле.
HTML Canvas
Для этого мы воспользуемся веб-технологиями. Создадим очень простую HTML-страницу. Я помещу на неё canvas с ID, например, game. Также мы загрузим отдельный скрипт index.js.
<canvas id="game"></canvas> <script src="index.js"></script>
В index.js мы просто будем выводить canvas. Особенность ID HTML-элементов заключается в том, что это валидные имена переменных в JavaScript, поэтому можно просто использовать их непосредственно, не нужно применять document.getElementById.
Дальше нужно создать 2D-контекст, который позволит нам выполнять рендеринг на этом canvas. Теперь попробуем отрисовать что-нибудь на нём, допустим, зелёный квадрат, а фон сделаем серым, и эти цвета запишем в отдельные константы. Код очистки canvas выделим в функцию clear. Теперь каждый раз, когда мне нужно будет очищать экран, я буду вызывать эту функцию. Чтобы размещать точки на экране, тоже создадим функцию point. Она будет принимать значения X и Y, а затем отрисовывать точку размера s.
const BACKGROUND = "#101010" const FOREGROUND = "#50FF50" console.log(game) game.width = 800 game.height = 800 const ctx = game.getContext("2d") console.log(ctx) function clear() { ctx.fillStyle = BACKGROUND ctx.fillRect(0, 0, game.width, game.height) } function point({x, y}) { const s = 20; ctx.fillStyle = FOREGROUND ctx.fillRect(x - s/2, y - s/2, s, s) }
Экранные координаты
Формула подразумевает использование на экране конкретной системы координат: точка начала координат (0, 0, 0) находится в центре экрана. Ось Y идёт вертикально, ось X — горизонтально. С левого края экрана находится X = -1, с правого — X = 1. Наверху находится Y = 1, внизу — Y = -1.

Но в HTML canvas система координат работает иначе: точка начала координат находится в левом верхнем углу, X увеличивается вправо, а Y вниз. В этой системе координат наша простая формула работать не будет.

То есть нам нужна некая функция, которая будет брать точку в системе координат формулы и преобразовывать её в экранные координаты. Давайте назовём её screen. Она будет принимать точку в виде объекта P, и этот объект будет иметь два поля, X и Y. Это будет наш формат векторов. На данный момент X и Y нашей точки могут иметь значения от -1 до 1, но нам нужно преобразовать их в значения от 0 до width или height. Как это сделать? Возьмём x и прибавим к нему единицу, теперь диапазон её значений будет от 0 до 2. Теперь его можно легко нормализовать, поделив на 2, по сути, получив диапазон от 0 до 1. Потом мы умножаем этот диапазон на width или height, получив точку в экранных координатах. Так как Y увеличивается вниз, нужно сначала перевернуть эту координату, вычтя её из единицы.
function screen(p) { // -1..1 => 0..2 => 0..1 => 0..w return { x: (p.x + 1)/2*game.width, y: (1 - (p.y + 1)/2)*game.height, } }
Давайте теперь создадим точку с координатами (0, 0), спроецируем её на экран и отрендерим.

3D-проецирование
А теперь давайте реализуем нашу формулу. Создадим функцию project, которая будет принимать трёхмерную точку p. И создадим двухмерную точку, координата X которой равна x / z, а координата Y равна y / z. Возьмём точку (0, 0, 0), спроецируем её на 2D-дисплей, а затем спроецируем её на экран.
function project({x, y, z}) { return { x: x/z, y: y/z, } } clear() point(screen(project({x: 0, y: 0, z: 0})))
При этом мы ничего не увидим, и это логично, потому что z равно нулю, то есть у нас получается деление на ноль. Эта формула подразумевает, что наш глаз находится в нуле. z = z означает, что объект (точка) находится точно в нашем глазу, то есть её некуда проецировать. Значит, она должна быть на расстоянии от нас, предпочтительно за экраном. Присвоим z какое-то другое значение, например, 1. И теперь мы увидим точку. При этом вот, что интересно: если сделать z равной, например, 2, то ничего не поменяется, потому что точка смотрит прямо на нас. Даже если сдвинуть её, допустим, по оси X, это всё равно не покажет нам полной картины.
Сдвиг по оси Z
Чтобы увидеть её, нам нужно анимировать точку. Пусть она сдвигается от нас по оси Z. Обернём наш код в функцию frame и будем выполнять её с заданным таймаутом. Пусть она анимируется с частотой примерно 60 fps, для этого нужно 1000 миллисекунд поделить на FPS. Добавим переменную dz, которая будет отслеживать смещение точки по оси Z. Точка будет находиться в координате 1 плюс смещение. В каждом кадре мы будем увеличивать это смещение, допустим, на 1. Также будет здорово синхронизировать это с таймингом, поэтому выполним умножение на дельту времени, равную 1 / FPS. Здесь 1 — это одна секунда, которую мы делим на частоту кадров, и это, по сути, разность времени между кадрами.
const FPS = 60; let dz = 0; function frame() { const dt = 1/FPS; dz += 1*dt; clear() point(screen(project({x: 0.5, y: 0, z: 1 + dz}))) setTimeout(frame, 1000/FPS); } setTimeout(frame, 1000/FPS);
Если мы запустим код, то точка будет двигаться справа налево. И это вполне логично: когда мы смотрим в реальном мире на движущиеся объекты, то чем дальше они от нас становятся, тем ближе к центру, к так называемой точке схода. И именно это мы видим у нас. Но на примере одной точки мы снова не можем увидеть картину по-настоящему. Давайте добавим ещё одну точку, которая смещена от центра влево.
const FPS = 60; let dz = 0; function frame() { const dt = 1/FPS; dz += 1*dt; clear() point(screen(project({x: 0.5, y: 0, z: 1 + dz}))) point(screen(project({x: -0.5, y: 0, z: 1 + dz}))) setTimeout(frame, 1000/FPS); } setTimeout(frame, 1000/FPS);
Теперь мы видим, что они приближаются к центру с обеих сторон.
Если сдвинуть их немного выше, то они будут приближаться к точке схода сверху.
Давайте создадим ещё пару точек, которые движутся со всех сторон.
Можно воспринимать эти четыре точки, как удаляющуюся от нас поверхность. Именно это здесь и происходит.
Давайте возьмём все эти точки и сохраним их в каком-нибудь массиве. Назовём его vs (сокращённо от vertices, «вершины»), потому что в будущем мы будем добавлять всё больше точек.
const vs = [ {x: 0.5, y: 0.5, z: 1}, {x: -0.5, y: 0.5, z: 1}, {x: 0.5, y: -0.5, z: 1}, {x: -0.5, y: -0.5, z: 1}, ]
То есть, по сути, мы будем выполнять итерацию по каждому отдельному v из vs, но нам нужно смещать эту v на dz. Введём для этого функцию translate_z, принимающую x, y, z и возвращающую точку с изменившейся на dz координатой z.
function translate_z({x, y, z}, dz) { return {x, y, z: z + dz}; } function frame() { const dt = 1/FPS; dz += 1*dt; clear() for (const v of vs) { point(screen(project(translate_z(v, dz)))) } setTimeout(frame, 1000/FPS); } setTimeout(frame, 1000/FPS);
Итак, в каждом кадре мы итеративно обходим массив точек, меняя их координаты z на переменную, далее проецируем эту 3D-точку на 2D-дисплей, затем этот 2D-дисплей проецируем на экран и помещаем всё это на экран.
Теперь давайте сместим ближе к экрану наши точки и добавим в массив ещё одну плоскость, находящуюся перед экраном.
const vs = [ {x: 0.5, y: 0.5, z: 0.5}, {x: -0.5, y: 0.5, z: 0.5}, {x: 0.5, y: -0.5, z: 0.5}, {x: -0.5, y: -0.5, z: 0.5}, {x: 0.5, y: 0.5, z: -0.5}, {x: -0.5, y: 0.5, z: -0.5}, {x: 0.5, y: -0.5, z: -0.5}, {x: -0.5, y: -0.5, z: -0.5} ]
Мы как будто находимся внутри этого куба. Если анимировать dz, то мы увидим куб целиком, он будет удаляться от нас
Вращение в плоскости XZ
Но пока всё ещё не совсем очевидно, что это куб. Давайте сделаем с ним что-нибудь более интересное. Будем вращать его вместо перемещения. Я хочу, чтобы вращение происходило по оси Y, то есть в плоскости XZ. Функция rotate_xz будет принимать координаты точки и угол. Как нам выполнить вращение вектора? Функция вращения выглядит так:

Она довольно запутанная, потому что это одна из тех формул, которые нужно запомнить и не пытаться понять. Можно легко доказать, что 2D-точка будет выглядеть именно так при повороте на угол φ (фи), но даже если вы это докажете, то это всё равно не объяснит, почему дело обстоит именно так. Это один из тех моментов, когда нужно просто взять формулу и выполнять по ней вычисления. Если хотите, можете посмотреть короткое пятиминутное видео с доказательством, я крайне рекомендую его.
А пока мы просто воспользуемся формулой и напишем нашу функцию, заменив при этом Y на Z, потому что мы выбрали для вращения плоскость XZ:
function rotate_xz({x, y, z}, angle) { const c = Math.cos(angle); const s = Math.sin(angle); return { x: x*c-z*s, y, z: x*s+z*c, }; }
В функции frame введём переменную angle для отслеживания угла. Изначально она будет равна нулю. Возьмём полную окружность, то есть 2*pi, и умножим её на dt, то есть один полный поворот будет выполняться за одну секунду.
function frame() { const dt = 1/FPS; angle += 2*Math.PI*dt; clear() for (const v of vs) { point(screen(project(translate_z(rotate_xz(v, angle), dz)))) } setTimeout(frame, 1000/FPS); } setTimeout(frame, 1000/FPS);
Куб слишком близко к нам, давайте сделаем его вдвое меньше и вдвое уменьшим скорость вращения
const vs = [ {x: 0.25, y: 0.25, z: 0.25}, {x: -0.25, y: 0.25, z: 0.25}, {x: 0.25, y: -0.25, z: 0.25}, {x: -0.25, y: -0.25, z: 0.25}, {x: 0.25, y: 0.25, z: -0.25}, {x: -0.25, y: 0.25, z: -0.25}, {x: 0.25, y: -0.25, z: -0.25}, {x: -0.25, y: -0.25, z: -0.25} ]
angle += Math.PI*dt;
И теперь это действительно выглядит, как вращающийся куб, но ещё не совсем.
Каркасная модель
Нам стоит соединить все эти вершины линиями. Давайте добавим функцию line, рисующую отрезки, она будет получать точки концов отрезка p1 и p2. Отрезки в HTML рисуются при помощи контуров (path). Это чем-то похоже на черепашью графику. Сначала мы перемещаем черепашку в точку p1, потом создаём отрезок из неё в точку p2, но этого недостаточно, нужно создать ещё и штрих (stroke).
function line(p1, p2) { ctx.strokeStyle = FOREGROUND ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke(); }
Теперь нам нужно задать способ соединения этих вершин. Добавим ещё один массив, который назовём fs (сокращение от faces, то есть «грани»). Каждая отдельная грань в нём сама по себе будет массивом индексов, соединяемых в виде полигона.
const fs = [ [0, 1, 2, 3], [4, 5, 6, 7], ]
Рендерить всё это мы будем, итеративно обходя все грани. И для каждой грани мы смотрим на текущий индекс. Нам нужно соединять текущую вершину и следующую, допустим, 0 и 1, 1 и 2 и так далее. Когда я дойду до конца, мне нужно будет вернуться в начало, то есть соединить 3 и 0. То есть это будет своего рода замкнутый контур. Итерации будут выполняться до размера массива + 1. Но тут нужно быть аккуратными, если индекс будет последним, то произойдёт переполнение буфера. Что же нам делать. Нужно вернуться назад при помощи модульной арифметики. Если индекс последний, мы возвращаемся и снова указываем на ноль. Чтобы отрезки были видны, нужно задать стиль штриха, потому что пока он чёрный. А чтобы отрезки были не такими тонкими, их можно сделать потолще с помощью ctx.lineWidth в функции line.
function frame() { const dt = 1/FPS; // dz += 1*dt; angle += Math.PI*dt; clear() // for (const v of vs) { // point(screen(project(translate_z(rotate_xz(v, angle), dz)))) // } for (const f of fs) { for (let i = 0; i < f.length; ++i) { const a = vs[f[i]]; const b = vs[f[(i+1)%f.length]]; line(screen(project(translate_z(rotate_xz(a, angle), dz))), screen(project(translate_z(rotate_xz(b, angle), dz)))) } } setTimeout(frame, 1000/FPS); } setTimeout(frame, 1000/FPS);
Всё стало красивее, но выглядит не очень правильно. Мы соединяем вершины не в том порядке. Поменяем его в массиве вершин.
const vs = [ {x: 0.25, y: 0.25, z: 0.25}, {x: -0.25, y: 0.25, z: 0.25}, {x: -0.25, y: -0.25, z: 0.25}, {x: 0.25, y: -0.25, z: 0.25}, {x: 0.25, y: 0.25, z: -0.25}, {x: -0.25, y: 0.25, z: -0.25}, {x: -0.25, y: -0.25, z: -0.25}, {x: 0.25, y: -0.25, z: -0.25} ]
Теперь нам нужно соединить пары вершин двух плоскостей, чтобы это действительно выглядело, как куб.
const fs = [ [0, 1, 2, 3], [4, 5, 6, 7], [0, 4], [1, 5], [2, 6], [3, 7] ]
Можно сделать его ещё лучше, отказавшись от рисования вершин.
function frame() { const dt = 1/FPS; // dz += 1*dt; angle += Math.PI*dt; clear() // for (const v of vs) { // point(screen(project(translate_z(rotate_xz(v, angle), dz)))) // } for (const f of fs) { for (let i = 0; i < f.length; ++i) { const a = vs[f[i]]; const b = vs[f[(i+1)%f.length]]; line(screen(project(translate_z(rotate_xz(a, angle), dz))), screen(project(translate_z(rotate_xz(b, angle), dz)))) } } setTimeout(frame, 1000/FPS); } setTimeout(frame, 1000/FPS);
Итак, мы получили каркасную модель куба. Замечательная формула, правда?
Почему это работает?
Но возникает вопрос: почему эта формула вообще работает? Давайте рассмотрим сцену, которую моделирует формула. Наш глаз расположен в нуле, а экран в единице. Нам нужно взять точку P и направить луч из этой точки в глаз, в результате получив точку P' на экране. Можно заметить, что у нас получилось два треугольника, большой и малый. Как видите, они имеют одинаковые углы, то есть эти треугольники подобны. Определим точку P, как (X, Y, Z), а P', как (X', Y'). Поскольку эти треугольники подобны, отношение между 1 (расстоянием до экрана) и значением Z точки P равно отношению X' или Y' (в зависимости от того, что мы хотим найти) точки P' к X или Y точки P.

Немного преобразовав эти выражения, получим:
Penger
Интересно во всём этом то, что мы получили очень простой 3D-движок, способный рендерить модели произвольной сложности. Достаточно лишь найти подходящие вершины и грани. Я подготовил более сложную модель, состоящую приблизительно из 326 вершин и 626 граней. Если вставить всю модель вместо куба, то мы получим следующее.
В заключение
Стоит отметить, что мы не пользовались здесь OpenGL, WebGL, WebGPU или чем-то подобным. Нам понадобился лишь 2D-контекст HTML и очень простая формула.
Комментарии (41)

DimPal
04.03.2026 14:10Формула позволяет понять не 3D-графику, а расчёт перспективной проекции. А дальше ещё надо накинуть курсы линейной алгебры: матрицы, вектора, их перемножение. Потом разобраться с API для рисования, организовать анимацию, что тоже накидывает объём кода. Для новичка с нуля по любому получается мем "Как нарисовать сову". По шагам всё просто, и целеустремлённый человек разберётся, но обычный человек быстро устанет и потеряет интерес. В любом случае помочь людям разобраться - идея благородная. Пишите ещё.

riky
04.03.2026 14:10Зря вы так, смотрел это видео и потом встречал на Ютюбе как другие блогеры вдохновлялись и ссылались на него.

Chaos_Optima
04.03.2026 14:10А ещё шейдеры (это прям целый мир), организовать саму отрисовку (инстансы, батчинг, прозрачность, тени, отложенное освещение, куча оптимизаций и ещё миллион других нюансов), управление памятью, дескрипторы, многопоточность (командные листы, очереди, фэнсы). Короче статья вообще никаких образом не даёт понять 3D графику, лишь немного по то как работает перспектива.

goldexer
04.03.2026 14:10Когда-то давно на форумах по VB встречал демки (автор был отмечен как «pilot») псевдо-3D: хвост, шар и галактика. Это было офигенно и именно эти демки сподвигли меня разбираться, а как же это все работает на низком уровне. В поисках я даже сорцы квейка второго скачал и штурмовал сишный код. Разумеется, сначала разобрался с геометрией, то-есть мне нужна была наглядность, почему так, а не этак. А уж затем пошли упрощения (точнее, усложнения ради оптимизации) в виде матриц, векторов, их перемножений... Было это лет более чем 20 назад. И тогдашнего школьника в моём лице просто поразила мысль, что не нужно перемножать каждую точку на поворот, перемещение и масштаб, а достаточно перемножить между собой матрицы этих и других преобразований и вот уже на этот результат всего один раз применить к точкам. В общем хотелось бы продолжение

da-nie
04.03.2026 14:10Нате вам мой программный растеризатор. Есть настройка матриц проецирования, матриц вращения и сдвигов. Есть Z-буффер, текстурирование и освещение.


soulYangor
04.03.2026 14:10Расчёт проекции не самая сложная задача, это детский сад, который и школьник освоит. Первая сложность начинается, когда мы начинаем закрашивать модель. Самая простая история: 2 пересекающиеся грани разных цветов, как отрисовать на экране? И это статика, без теней, отражений, текстур и шейдеров. До понимания 3D-графики как до Луны

Tiriet
04.03.2026 14:10Это вы перепрыгнули слегонца. Чтоб их закрасить нужно сначала определить, а плоскости ли они вообще, потом определить контур пересечения, разбить сечения на треугольники, _удалить невидимые_ и вот потом уже думать, как их, треугольники эти получившиеся, отрисовывать на экране.

soulYangor
04.03.2026 14:10Я имел ввиду грани с математической точки зрения(мы ж тут про формулы говорим), а там грани - по определению, это плоскости. Пересечение математических граней это отрезок.
Если говорить про грани-полигоны, то тут да, нужно ещё понять а плоскость ли тут задана. Собственно отрисовать правильно пересечение треугольников(3х точечных математических граней) та ещё история, в рамках которой и рассказывают про Z-буфферизацию, BSP-деревья и прочие прелести. Именно на этой задаче впервые понимаешь, что 3D-графика это куча математики и алгоритмов.

azTotMD
04.03.2026 14:10for (const v of vs) { point(screen(project(translate_z(rotate_xz(v, angle), dz)))) }тут бы хорошо из rotate вынести за скобку вычисление синуса и косинуса. И вообще, как у этой штуки с производительностью? Поди с WebGL как-нибудь будет более производительно?

Tiriet
04.03.2026 14:10Пять вызовов функций на одну точку. Да никак у неё с производительностью, даже если бы это было написано на чистом С. Чтобы была производительность в своём коде нужно Аммерала понять и матрицы 4*4 умножать на ГПУ. Но тогда получится самопальный ОпенГЛ, однако. И то- без текстур и отсечения невидимых треугольников.

kekusprod
04.03.2026 14:10Я думаю в контексте данной темы вопрос производительности стоит далеко не на первом месте. В качестве обучающего материала примитивными функциями пошагово показать что это и как оно работает - очень даже достойная работа. За это я Мистера Зозина и уважаю.

da-nie
04.03.2026 14:10Да вы шутите! Это же страшный поимитив., описанный много раз в книжках по компьютерной графике. Причем, тут только проекцию одну показали. А там этих проекций много. Как и методов настройки матриц проецирования по всяким плоскостям и углам зрения. Неужели мы дошли до ситуации, когда то, что всем интересующимся давно было известно стало откровением спустя 20-30 лет?

haqreu
04.03.2026 14:10С чего-то надо начинать, и как материал для интересующихся, например, школьников - очень даже. А если тема зайдёт, то потом и книги подтянутся.

da-nie
04.03.2026 14:10Так я ведь не о том, что с чего-то надо начинать. Всё с чего-то начинается. Речь про то, что этот материал восхваляют, словно это откровение. Странно. В 90-е даже в ZX-Spectrum "Динамическая графика" это было, и школьники не воспринимали такое откровением. Им были привычны формулы. А это просто очередной алгоритм и формулы и всё. А здесь прямо какое-то восхищение. Вот этого я не понимаю.

haqreu
04.03.2026 14:10Угу, и видео, а не текст. Но цена входа в этого пингвина на жаваскрипте сильно ниже (и в этом педагогический вклад), нежели в книгу по программированию на ZX спектрум. В итоге пингвина может нарисовать большее количество людей, что неплохо.

da-nie
04.03.2026 14:10Да я бы не сказал бы, что раньше было что-то сложнее... В целом там просто давалась математика. Иногда - программа на бейсике для примера.

haqreu
04.03.2026 14:10Уже иметь спектрум дома - это было сложнее :)
Помню я, как работает видеопамять в спектруме. Не говорите, что это было просто.

da-nie
04.03.2026 14:10Так на бейсике это не нужно. plot и draw всё сделают сами. Кстати, покупал в 90-е тоже книгу про 3D на QBasic для IBM. Жаль, отдал. Так и не вернули.

kekusprod
04.03.2026 14:10Я не просто так выделил слова "примитивные" и "пошагово". Никто и не говорил о том, что автор изобрел что-то новое. Примитивные функции на то они и примитивные - 100 раз описанные в учебниках и во многом составляют фундамент. Материал восхваляют (если рассматриваем мой случай, то я и автор оригинального ролика) не потому, что это откровение, а потому что подано и разжевано всё прекрасно, по существу и без духоты, сопровождая малейшее изменение функции наглядной демонстрацией что конкретно поменялось. Плюс, что не мало важно, порог усвоения подобного формата информации достаточно низкий. Посмотрят - понравится - там и до книг дело дойдет. Для нас с вами, возможно, это и не является чем-то новым, но если смотреть с точки зрения новичка, который хочет разобраться в теме - это идеальный вариант. Не у всех в мозгу с рождения знания о том как это работает.

da-nie
04.03.2026 14:10а потому что подано и разжевано всё прекрасно, по существу и без духоты,
Вот этот комментарий как раз и показывает, что вы увидели-таки откровение. Видите ли, примитив подать не по существу и, как вы выразились, с "духотой" это надо очень сильно постараться. Нет, чуть сложнее можно сделать. Но именно, что чуть. А фраза "Не у всех в мозгу с рождения знания о том как это работает. " заставляет подозревать, что восхищающиеся это чуть сложнее не могут воспринимать. Оттого и восхищение, что наконец-то они поняли. До этого ну никак не получалось понять простейшую вещь. А тут ещё проще кто-то сумел написать. А это означает, что планка упала, потому что в 90-е школьники вполне себе воспринимали обычные формулы и даже матричные преобразования.

kekusprod
04.03.2026 14:10Вы в правильном направлении думаете, но выводы делаете ложные. Да, сейчас не 90-е, запросы у молодого поколения совершенно другие, как и подход к усвоению информации. Но то что формат этой информации изменился, еще не значит, что люди перестали воспринимать обычные формулы и матричные преобразования.
Вся разница исключительно в подаче, а не в сложности/простоте материала. В современных реалиях, когда информация доступна в любом виде, люди будут выбирать более наглядный и демонстрационный вариант (простой в освоении), недели сухой текстовый, и это вы никаким аргументом не перебьете, уж извините. Мне ваша риторика напоминает "а вот раньше никаких калькуляторов не было, все в уме считали.."
А то что я лично этим материалом восхищаюсь, не говорит о том, что для меня эта информация какая-то новая. Я сам все это давно прошел в университете. Мне нравится автор, его подача и рассматриваемые темы. Поэтому и высказался в пользу оригинального автора.

da-nie
04.03.2026 14:10Более наглядный способ означает меньшие усилия. Тот самый "царский путь". То есть, способность приложить усилие для понимания даже примитива теперь не в чести. А это неминуемо скажется на общих способностях обучаться. Зачем такому человеку браться за то, что сложнее, если он привык к максимально простому уровню?
Да, чтобы считать на калькуляторе нужно уметь считать в уме. Иначе будет как в рассказе Азимова, где делали обратный процесс- выясняли, как считает калькулятор, чтобы не зависеть от него. Так вот, умеющий прилагать усилие поймет и простейшую подачу материала, но не наоборот.

azTotMD
04.03.2026 14:10Да, какой вопрос, такой и ответ, извините. Полный вопрос должен был звучать так:
Допустим мы не делаем такой глупости, как сто раз вычисляем синус одного и того же угла. Допустим, нам достаточно только одной проекции, нет нужды вращать камеру. Наше решение обязательно браузерное, так что никаких Си. Пускай у нас нет поверхностей, только проволочные каркасы. Пускай нам нужно отрисовать порядка тысячи линий. Имеет ли смысл подобный движок или всё равно WebGL будет эффективнее?

Tiriet
04.03.2026 14:10Личный опыт это не опыт, но все же по личному опыту: такой движок позволит не тянуть за собой кучу зависимостей и сделать очень небольшое _все_ по своему вкусу, а потом все равно придется переделать на нормальную либу, потому что или 1000 превратится в 100к, или текстур захочется, или камеру вертеть надо будет или еще чего и нужно будет быстрое и полноценное отсечение невидимых поверхностей и раскраска граней.
belch84
Вот проволочная модель Шаттла. Не пользуюсь ничем, кроме собственной программы для просмотра проволочных моделей (но сам файл с описанием Шаттла взял из набора файлов древней программы 3DViewer для MS DOS)
Шаттл