Comments 58
Вот именно такая графика была в Героях Меча и Магии III
За исключением того, что там в игре рендеринг был не трёхмерный :)
И вообще там был православный пиксель арт)
Как-то так 

Откуда скрин?..
Спрашиваю, потому что не в курсе, как вообще получить такое состояние экрана в оригинальной игре. Это отдельный режим, или какая-то модификация, или отдельная игра на тех же ресурсах?
Это абсолютно точно не оригинальная игра, и не hota. Больше всего тут даже не вид смущает, а количество стеков, их не более 7 должно быть. Могу предположить что это неофициальное дополнение WOG с очень кастомными настройками.
Но тут же 2+2 армии на одном экране. Что за модификация?
Я подозреваю, что человек просто нашел скрин в гугле и сам не знает. Если сделать поиск по картинке, то можно узнать, что это "HeroesLand, или Герои меча и магии 3 онлайн"
С интересом читаю ваши статьи. Особенно про трассировку лучей.
Спасибо Вам за Вашу работу. Жду следующих Ваших публикаций.
Спасибо Вам за Вашу работу. Жду следующих Ваших публикаций.
Спасибо!
Вот бы такое про «звуковую оптику». Посмотрел на всякий случай, да зачитался:)
Жаль у вас про звук только совсем немного было про робота.
Вот бы такое про «звуковую оптику». Посмотрел на всякий случай, да зачитался:)
Жаль у вас про звук только совсем немного было про робота.
Толи я плохо ищу, толи в русскоязычном сегменте не хватает подобных материалов.
Проще изучить английский, чем обойтись материалами доступными на русском.
И это наверное не плохо — если все будут пользоваться одним языком, информация для всех будет доступна максимально целостной. Хотя это, конечно, не значит, что не нужно производить контент на иных языках в принципе.
И это наверное не плохо — если все будут пользоваться одним языком, информация для всех будет доступна максимально целостной. Хотя это, конечно, не значит, что не нужно производить контент на иных языках в принципе.
Проблема скорее в удобстве понимания, так как при написании материалов на русском будут пытаться адаптировать под русскоговорящего человека, потому что не всегда ему понятно то, то написал американец. Сам иногда сталкиваюсь с таким, вроде и понимаешь о чём автор, но всё же не до конца, а если бы было такое же или пусть даже немного хуже, но на русском, то, с большой вероятностью, автор русской версии объяснил бы понятнее.
Похожий пример на шейдерах.
Удобный способ создания палитр (для GPU)
www.iquilezles.org/www/articles/palettes/palettes.htm
Оттуда же про raymarching
www.iquilezles.org/www/material/nvscene2008/rwwtt.pdf
www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
www.iquilezles.org/www/articles/palettes/palettes.htm
Оттуда же про raymarching
www.iquilezles.org/www/material/nvscene2008/rwwtt.pdf
www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
Даже не знаю что и добавить)) Учитывая что я «долбанный» верстальщик, для меня это нечто грандиозное!)) Всегда мечтал работать с графикой.
Не бывает долбаных верстальщиков. Бывают люди, которым интересно ковырять что-то помимо их основной работы/учёбы, и люди, которым неинтересно.
Я не коим образом не хотел обделить верстальщиков, но всё же по факту вёрстка сайтов дело куда более проще чем графика, 3d или разработка тех же игр.
А свободное время можно уделять на другие занятия отличные от коддинга — спорт, творчество и тд.
А свободное время можно уделять на другие занятия отличные от коддинга — спорт, творчество и тд.
Оно все кажется сложным, если наблюдать, а не щупать.
Ну начнём с того, что на этом сайте вряд ли много людей, которые рисуют или бегают, и при этом совсем не занимаются техническими хобби. А если продолжать, то технические хобби повышают уровень подготовки, что напрямую сказывается на карьерном росте.
Вот меня интересует вопрос, а можно ли материал одной текстурой задавать? Создать, допустим, 2-3 сотни различных материалов и каждому свой цвет определить. На каждый цвет/материал свои шейдеры, ну или местами один и тот же с разными переменными. А то сейчас по 6 текстур на объект в играх делают, никакой памяти не хватит.
Так а ведь шесть текстур на объект — это и есть материал, заданный в текстуре (ну, если не считать карт нормалей всяких). Мы же задаём материал вообще каждой точке нашего объекта. Тут у меня капелька пота, здесь сажей испачкано, и т.п.
1) Я сильно сомневаюсь, что будет какой-то выигрыш в создании отдельной библиотеки материалов, которая покрывает весь игровой мир.
2) Ну аж как художники взвоют (и совершенно справедливо)! Сделать нормальный интерфейс работы с этой билиотекой просто нереально.
1) Я сильно сомневаюсь, что будет какой-то выигрыш в создании отдельной библиотеки материалов, которая покрывает весь игровой мир.
2) Ну аж как художники взвоют (и совершенно справедливо)! Сделать нормальный интерфейс работы с этой билиотекой просто нереально.
отдельной библиотеки материалов, которая покрывает весь игровой мир
Есть и такой подход ru.wikipedia.org/wiki/Мегатекстура
Если правильно понял про «цвет=материал», то сейчас в deferred подходах и так используется условно говоря «цветовое» кодирование материалов в буфере кадра для последующей обработки. Только в конце концов все равно же понядобятся эти «шесть текстур», ведь PBR шейдинг должен откуда-то брать данные о фактуре, цвете поверхности, физических свойствах. Да и более простые модели освещения все одно захотят несколько текстур.
Совсем уж альтернатива — это только процедурная генерация текстур.
Совсем уж альтернатива — это только процедурная генерация текстур.
Где то я видел похожую статью, но в упрощенном виде. Попробовал — посмотрел на взыв в 16-17 FPS и забыл, только академический интерес.
«Графоний», конечно, у Вас всегда отменный. Настоящая магия программирования. Всё никак не найду в себе сил допройти (на практике) хоть одну из статей.
Но вот в этой генерации взрыва с точки зрения реализма смущает формирование по периферии клубов дыма оторванных от источника. Нечто подобное, конечно, можно наблюдать при мощных взрывах, например на испытаниях ядерных бомб — облака конденсируются в области перед ударной волной, но там совсем иной масштаб и механизм. Есть ли методы исправления этого недостатка? Может быть, достаточно использовать растущую сферу заполненную трёхмерным шумом Перлина?
Но вот в этой генерации взрыва с точки зрения реализма смущает формирование по периферии клубов дыма оторванных от источника. Нечто подобное, конечно, можно наблюдать при мощных взрывах, например на испытаниях ядерных бомб — облака конденсируются в области перед ударной волной, но там совсем иной масштаб и механизм. Есть ли методы исправления этого недостатка? Может быть, достаточно использовать растущую сферу заполненную трёхмерным шумом Перлина?
Да, это проблема, и её нетрудно решить, сгенерировав нужный шум. Я решать не стал, так как обычно взрывы быстрые (в отличие от моей анимации), и этих артефактов просто не будет видно.
Но взрывы приятно скриншотить… А то раз быстрые, то можно и двумерными анимациями обойтись тогда.
А вообще мне кажется должна быть какая-то техника для моделирования физически корректных взрывов, подобно тому как ray tracing неплохо моделирует распространение света до учёта квантовых и гравитационных эффектов (хотя наверняка есть и такие модификации).
А вообще мне кажется должна быть какая-то техника для моделирования физически корректных взрывов, подобно тому как ray tracing неплохо моделирует распространение света до учёта квантовых и гравитационных эффектов (хотя наверняка есть и такие модификации).
Вообще статья отличная, но в некоторых местах (особенно в либе) качество кода оставляет желать лучшего, что к сожалению затрудняет понимание
Порт на c#: tinykaboom in c#. Считает немного долговато (порядка 30 сек на моей машине).
Параметр времени в анимации — это радиус увеличивающейся ограничивающей сферы?

Параметр времени в анимации — это радиус увеличивающейся ограничивающей сферы?

Отлично! А теперь надо добавить фоновую (сферическую) картинку и сделать центр взрыва прозрачным.
Вы про какой параметр времени, я не очень понял?
Вы про какой параметр времени, я не очень понял?
Не совсем ясно со сферической картинкой. Это картинка размером 2 π × π. Я должен «вырезать» из неё сегмент, который пробегает луч, формируя изображение?
Да, у нас есть картинка размером 2 π × π. Если у вас луч промахнулся мимо сцены, то сейчас вы просто рисуете пиксель постоянного цвета. А ведь ничто не мешает продолжить луч и пересечь его со сферой, например, радиуса 1000, которая описывает всю сцену. Получить точку (x,y,z) пересечения, превратить её в долготу и широту (φ, θ), которые сами живут в [0, 2π] ×[0, π], и вместо постоянного взять цвет из картинки.
Сразу скажу про маленькую тонкость, я регулярно забываю, что у нас верх — это игрек, а не зед. Надо не ошибиться в формуле сферических координат :)
Сразу скажу про маленькую тонкость, я регулярно забываю, что у нас верх — это игрек, а не зед. Надо не ошибиться в формуле сферических координат :)
Я добавил фон при помощи магических констант, взяв исходные известные углы для «окна» и наложив их на сферическую картинку. Я думал, что не важно какой имеет радиус внешняя сфера, т.к. с его изменением область проекции нашего изображения будет постоянной относительно всей площади сферы. Так?
А прозрачность пока не знаю как делать.

А прозрачность пока не знаю как делать.

Да, радиус абсолютно неважен, главное, чтобы он был больше сцены. А прозрачность очень просто: на данный момент вы считаете фоновый пиксель тогда, когда не пересеклись со взрывом, так? А теперь и тогда, когда пересеклись со взрывом, тоже считайте цвет фонового пикселя. И складывайте их цвета: для начала просто к цвету взрыва добавляйте цвет фона, помноженный на коэффициент прозрачности, который просто равен уровню шума в данном пикселе. Так вы получите серые части взрыва непрозрачными, и красно-жёлтые прозрачными.
Что-то вроде этого получается:

framebuffer[ i + j * width ] = ( framebuffer[ i + j * width ] * noise + palette_fire( noise ) ) * lightIntensity;

Ну вообще красота! Теперь добавить ещё объектов (для начала просто пару взрывов), потом просто шахматную доску снизу, и чтобы не-взрывы освещались бы в зависмости от расстояния до центра взрыва. Потом можно будет добавить тени от непрозрачных частей. В общем, простор для фантазии широчайший, и всё стоит десяток строк кода…
Кстати, а что изменилось, что фон стал другим по сравнению с предыдущей картинкой?
Кстати, а что изменилось, что фон стал другим по сравнению с предыдущей картинкой?
Это центр сферической картинки, в отличие от предыдущего варианта. У меня в коде есть коэффициенты масштабирования (магические константы). Я их слегка меняю, чтобы по вертикали и горизонтали масштаб совпадал более менее.
Ну, вообще, отличается. Я не использую значения высоты и ширины сферической картинки в расчётах, т.к. считаю в радианах. Ширина — 2 π, а — высота — π. Далее нужно посчитать углы для нашего окна и соотнести их с размерами в радианах, так получим коэффициенты масштабирования по направлениям. Заполняя буфер фоном мы берём цвета из сферического изображения с учётом этих коэффициентов. Я всё это делал интуитивно, поэтому получилось с точностью до каких-то множителей (нужно дополнительно посидеть, поразбираться). Начало отсчёта взял от центра сферической картинки.
На последней картинке итак видно, что она не совсем соответствует проекции, которая должна быть, но мне вид с Солнцем понравился. А должен быть угол π / 3 по высоте (fov).
На последней картинке итак видно, что она не совсем соответствует проекции, которая должна быть, но мне вид с Солнцем понравился. А должен быть угол π / 3 по высоте (fov).
Красота какая! Спасибо!
Методом гугления и издевательств над кодом реймаршинга в Lua, получил вот такую картинку:
Теперь хотелось бы допилить этот код до рендеринга сцены в равноугольную панораму. Осталось только код для камеры нагуглить.

Теперь хотелось бы допилить этот код до рендеринга сцены в равноугольную панораму. Осталось только код для камеры нагуглить.
Картинка отличная, выкладывайте код!
И давайте ссылку на определение нужной вам проекции.
И давайте ссылку на определение нужной вам проекции.
Код сцены
Для запуска кода используется движок Instead.
Код необходимо сохранить в файл с именем main3.lua
instead-hub.github.io
--$Name:Рейтрейсер$
--$Info:порт рейтрейсера с pico-8$
--$Author:Kerbal$
--$Version:0.1$
require "sprite"
require "timer"
require "click"
local sqrt = math.sqrt
local flr = math.floor
local min = math.min
local max = math.max
local rnd = math.random
local abs = math.abs
local cam_shift_x = 0.1
local cam_shift_y = 0.1
local r_g_b = {
[0] = {0,0,0},
{29, 43, 83},
{126, 37, 83},
{0, 135, 81},
{171, 82, 54},
{95, 87, 79},
{194, 195, 199},
{255, 241, 232},
{255, 0, 77},
{255, 163, 0},
{255, 236, 39},
{0, 228, 54},
{41, 173, 255},
{131, 118, 156},
{255, 119, 168},
{255, 204, 170},
{199, 240, 216}, --NOKIA SCREEN
{67, 82, 61}, --NOKIA INK
{0,0,0},
{0,0,0},
{0,0,0},
}
--[[
local colorGradients = {
sky = {16, 16, 16, 16},
green = {17, 16, 16, 17},
red = {17, 16, 17},
}
]]
local colorGradients = {
red = {1, 2, 8, 9, 10, 7},
green = {0, 1, 5, 3, 11},
sky = {15, 12, 12, 1},
nokia1 = {16, 16, 16, 16},
nokia2 = {17, 16, 16, 17},
nokia3 = {17, 16, 17},
}
--math
--local function length(x, y, z) return sqrt(x*x + y*y + z*z) end
local function length(x, y, z)
local y = y or 0
local z = z or 0
return (x*x + y*y + z*z)^0.5
end
local function norm(x, y, z)
local y = y or 0
local z = z or 0
local l = length(x,y,z)
return x/l, y/l, z/l
end
local function dot(xa, ya, za, xb, yb, zb)
local za = za or 0
local zb = zb or 0
return xa*xb + ya*yb + za*zb
end
local function dot2(xa, ya, xb, yb)
return xa*xb + ya*yb
end
--globals
local ex, ey, ez = 0, 1, -1.5 --camera position
local fov = 45 --camera FOV
local tmin, tmax = .1, 200 --minimum and maximum distance from camera
local maxSteps = 100 --maximum number of steps to take
local lx, ly, lz = norm(.2, .5, -.6) --light direction
--distance field functions
----------Shapes
local function sdCircle(x, y, r)
return length(x, y) - r
end
local function sdBox(x, y, w, h)
local n, m = abs(x)-w, abs(y)-h
return length(max(max(n,0),max(m,0))) + min(max(n,m),0.0);
end
----------Shapes
----------Primitives
local function plane(x, y, z) return y end
local function cube(x, y, z, side)
return max(abs(x), abs(y), abs(z)) - side / 2
end
local function box(x, y, z, a, b, c)
local l, n, k = abs(x) - a, abs(y) - b, abs(z) - c
return length(max(l, n, k, 0.0)) + min(max(l,max(n, k)),0.0)
end
--[[
local function rbox(x, y, z, a, b, c, r)
local l, n, k = abs(x) - a - r, abs(y) - b - r, abs(z) - c - r
return length(max(l, n, k, 0.0)) + min(max(l,max(n, k)),0.0)
end
]]
local function sphere(x, y, z, radius)
local manhattanDistance = x + y + z
--this if is to avoid errors due to pico's limited number range
if manhattanDistance > radius * 2 then return manhattanDistance end
return length(x, y, z) - radius
end
local function cone(x, y, z, h, r)
local h, r = norm(h, r)
local q = length(x, z)
return dot2(h, r, q, y)
end
local function torus(x, y, z, rring, r)
return (((x^2 + y^2)^0.5 - rring)^2 + z^2)^0.5 - r
end
local function torus2(x, y, z, rring, r)
return (((y^2 + z^2)^0.5 - rring)^2 + x^2)^0.5 - r
end
local function capped_cylinder(x,y,z, h, r)
local a, b = abs(length(y,z)) - h, abs(x) - r
return min(max(a, b), 0.0) + length(max(a, b, 0.0))
end
local function sdTriPrism(x, y, z, w, h)
local a,b,c = abs(x), abs(y), abs(z)
return max(c - h, max(a * 0.866025 + y * 0.5, -y) - w * 0.5)
end
----------Union, Subtraction, Intersection
local function op_union (a, b)
return min(a, b)
end
local function op_substraction (a, b)
return max(-a, b)
end
local function op_intersection (a, b)
return max(a, b)
end
local function cutbox(x, y, z, h)
return max(-sphere(x, y, z, h*.7), box(x, y, z, h))
end
local function cutsphere(x, y, z, h)
return max(sphere(x, y, z, h*.75), box(x, y, z, h))
end
--union combines two distance functions
local function union(a, am, b, bm) if a < b then return a, am else return b, bm end end
--scene defines the total distance function
local function scene(x, y, z)
local d, m = tmax, 0 --max distance is skyk
d, m = union(d, m, plane(x, y, z), "green")
-- d, m = union(d, m, cube(x-2, y-2, z-1, 1), "green")
d, m = union(d, m, box(x-2, y-2, z-1, .1, .5, .5), "green")
d, m = union(d, m, sdBox(x, z - 3, .2, .2), "green")
d, m = union(d, m, sdBox(x, z, .2, .2), "green")
d, m = union(d, m, sdBox(x - 3, z - 3, .2, .2), "green")
d, m = union(d, m, sdBox(x - 3, z, .2, .2), "green")
d, m = union(d, m, sdCircle(x - 3, y, .1), "red")
d, m = union(d, m, sdCircle(y, z, .1), "red")
-- d, m = union(d, m, cone(x-4, y-2, z, 0.01, 0.01), "green")
d, m = union(d, m, torus( z-1, x-2, y-1, 1.5, .1), "green")
-- d, m = union(d, m, torus2(x-3, y-1, z-2, 2, .1), "green")
d, m = union(d, m, sphere(x, y-1, z, .5), "red")
d, m = union(d, m, sdTriPrism(y-2, x-2, z, .3, .2), "red")
d, m = union(d, m, sphere(x, y-.5, z, .3), "red")
d, m = union(d, m, capped_cylinder(x-2, y-1, z, .2, .2), "red" )
-- d, m = union(d, m, cutbox(x-2, y-1, z-1, 1), "red" )
d, m = union(d, m, op_substraction(sphere(x-2, y-1, z-1, 0.7), box(x-2, y-1, z-1, .5, .5, .5)), "red" )
-- d, m = union(d, m, cutsphere(x-2, y-1, z-.75, 1), "cnob" )
d, m = union(d, m, sphere(x, y-1.5, z, .3), "red")
d, m = union(d, m, sphere(x-1, y-1, z-1, .3), "green")
return d,m
end
--calculates the normal at xyz
local function sceneNormal(x, y, z)
local eps = 0.1
local xa, xb = scene(x+eps, y, z), scene(x-eps ,y ,z)
local ya, yb = scene(x, y+eps, z), scene(x, y-eps ,z)
local za ,zb = scene(x, y, z+eps), scene(x, y, z-eps)
return norm(xa-xb, ya-yb, za-zb)
end
--rendering
--returns smooth color based on position and gradient
local function dither(x, y, gradient, value)
local whole = flr(value * #gradient)
local fraction = value * #gradient - whole
local low = gradient[min(whole + 1, #gradient)]
local high = gradient[min(min(whole + 1, #gradient) + 1, #gradient)]
if fraction < 1/7 then return low end
if fraction < 2/7 then if (x+1)%3==0 and y%3==0 then return high else return low end end
if fraction < 3/7 then if x%2==0 and y%2==0 then return high else return low end end
if fraction < 4/7 then if (x%2==0 and y%2==0) or (x%2~=0 and y%2~=0) then return high else return low end end
if fraction < 5/7 then if x%2==0 and (y+1)%2==0 then return low else return high end end
if fraction < 6/7 then if (x+1)%3==0 and y%3==0 then return low else return high end end
return high
end
local function getShadowPoint(t, x, y, z) return x + lx * t, y + ly * t, z + lz * t end
--computes the shoft shadow value
local function shadow(x,y,z)
--t starts at 0.2 so the shadow ray doesn't intersect
--the object it's trying to shadow
local res, t, distance, sx, sy, sz = 1, 0.2, 0, 0, 0, 0
for i = 1, 6 do
sx, sy, sz = getShadowPoint(t, x, y, z)
distance, _ = scene(sx, sy, sz) --we don't care about the color
res = min(res, 2 * distance / t) --increase 2 to get sharper shadows
t = t + min(max(distance, .02), .2)
if distance < .05 or t > 10.0 then break end
end
return min(max(res, 0), 1)
end
--calculates the final lighting and color
local function render(x, y, t, tx, ty, tz, rx, ry, rz, color)
local nx, ny, nz = sceneNormal(tx, ty, tz)
local light = 0
light = light + min(max(dot(nx, ny, nz, lx, ly, lz), 0), 1) --sun light
light = light * shadow(tx,ty,tz) --shadow color
light = min(max(light, 0), 1) --clamp final light value
return dither(x, y, colorGradients[color], light)
end
--calculates the sky color
local function sky(x, y, rx, ry, rz)
local altitude = (min(max(ry, 0), 1) ^ 1.5)
return dither(x, y, colorGradients.sky, altitude)
end
local function nokia1(x, y, rx, ry, rz)
local altitude = (min(max(ry, 0), 1) ^ 1.5)
return dither(x, y, colorGradients.nokia1, altitude)
end
--tracing
--this is the heart of a ray tracer
--the for loop pushes the test point forward until
--it finds a surface that is close enough to render
--the forward direction is based off the xy of the screen and fov
local function getRayDirection(x, y) return norm(x / 64 - 1, (128 - y) / 64 - 1, 90 / fov) end
local function getTestPoint(t, rx, ry, rz) return ex + rx * t, ey + ry * t, ez + rz * t end
function trace(x,y)
local rx, ry, rz = getRayDirection(x, y)
local tx, ty, tz = 0, 0, 0
local t, distance, color = 0, 0, 0
for i = 1, maxSteps do
tx, ty, tz = getTestPoint(t, rx, ry, rz)
distance, color = scene(tx, ty, tz)
--the test point is close enough, render
if distance < .05 then return render(x, y, t, tx, ty, tz, rx, ry, rz, color) end
--the test point is too far, give up, draw the sky
if distance >= tmax then break end
--move forward by some fraction
t = t + distance * .7
end
return sky(x, y, rx, ry, rz)
-- return nokia1(x, y, rx, ry, rz)
end
--just here to get pico to work the way it should be defaut tbh
--function _init() cls() end
--function _update() end
--pick random points to trace, but only if they
--havent been traced before
--cache the expensive trace function, and set pixel
--local traced = {}
function _draw()
for y = 1, 512 do
for x = 1, 512 do
-- local x, y = flr(rnd(128)), flr(rnd(128))
local i = x + y * 255
-- if not traced[i] then
-- pxl:val(x, y, i, j, t)
-- print ("x ".."y "..trace(x,y))
local k = trace(x,y)
--[[ print (r_g_b[k])
print (trace(x,y))
print (r_g_b[k][2])
print (r_g_b[k][3] or "nope")
]]
pxl:val(x,y, r_g_b[k][1], r_g_b[k][2], r_g_b[k][3])
-- pset(x, y, trace(x,y))
--traced[i] = true
-- end
end
end
end
sprite.direct(true)
declare 'pxl' (false)
function game:timer()
--[[
local x, y, i, j
t = t + f
v = v + r
if t < 1 then f = 1 end
if t > 254 then f = -1 end
if v < 1 then r = 1 end
if v > 254 then r = -1 end
-- i = (x*y^0.5+t)%512
for x = 0, 255 do
for y = 0, 255 do
i = (x^2-y^2+t^1.3)%255
j = (x^2-y^2+v^1.3)%255
-- pxl:val(x, y, i, j, t)
pxl:val(x, y, j, t, v)
end
end
]]
-- print (colorGradients["red"][3])
-- table.insert(colorGradients["red"],colorGradients["red"][1])
-- table.remove(colorGradients["red"],1)
_draw()
pxl:draw_spr(sprite.scr())
if ex > 1 or ex < -1 then cam_shift_x = cam_shift_x * -1 end
if ey > 2 or ey < 0.3 then cam_shift_y = cam_shift_y * -1 end
ex = ex + cam_shift_x
ey = ey + cam_shift_y
-- fps = (1000/(instead.ticks() - old_ticks))
-- print (fps)
-- old_ticks = instead.ticks()
end
function start(load)
pxl = pixels.new(512, 512)
timer:set(10)
end
Для запуска кода используется движок Instead.
Код необходимо сохранить в файл с именем main3.lua
instead-hub.github.io
wiki.panotools.org/Equirectangular_Projection
en.wikipedia.org/wiki/Equirectangular_projection
Sign up to leave a comment.
Рисуем мультяшный взрыв за 180 строчек голого C++