Рейтрейсер на JavaScript

    TitleImage

    Знаете ли вы что такое рейтрейсер? Это программа которая рисует трёхмерную сцену на экране так, как её бы увидели вы. Конечно, не совсем так, но некоторые рейтрейсеры умеют рисовать очень правдоподобные картинки, например как в "Аватаре".

    Идея рейтрейсера очень простая и в этой статье я раcскажу как устроен этот алгоритм и даже напишу его на JavaScript. Картинки и пример прилагаются.



    Как рисуют трёхмерные сцены?



    Сегодня, насколько мне известно, есть два метода проектирования трёхмерных сцен на плоский экран. Первый метод основан на матричных преобразованиях. Его идея также простая, он работает быстро, но то что он рисует не похоже на фотографию и годится лишь для игр. Второй метод это рейтрейсинг. Он просто устроен, он легко позволяет изобразить тени, отражения, рефракцию и другие световые эффекты, но он работает очень медленно и потому для игр не годится. Кроме того, алгоритм рейтрейсинга легко распараллеливается: сколько есть процессоров, ровно во столько раз и будет ускорение.

    Идея алгоритма



    Представьте, что монитор за которым вы сидите это окно, а за окном какая то сцена. Цвет каждого пикселя на мониторе это цвет луча который выходит из глаза, проходит через этот пиксель и сталкивается со сценой. Чтобы узнать цвет каждого пикселя, нужно через каждый пиксель запустить луч и узнать где этот луч сталкивается со сценой. Отсюда название алгоритма: ray-tracing — трассировка лучей.

    Получается, что достаточно написать функцию которая по координатам луча — двум точкам в пространстве — вычисляет цвет поверхности куда этот луч попадает. Какие ситуации надо рассмотреть? Их по крайней мере три:

    1. Обычная поверхность. Когда луч сталкивается с такой, то можно сказать, что цвет луча это цвет этой поверхности. Это самый простой случай.
    2. Отражение. Луч может попасть в зеркало и отразиться под тем же углом. Чтобы обработать такую ситуацию, надо уметь отражать луч.
    3. Преломление. Луч может перейти через гранизу двух сред, например из воздуха в воду. При переходе из одной среды в другую, луч преломится. Это явление называют рефракцией.


    Каждая из этих ситуаций легко обрабатывается, поэтому написать рейстрейсер не сложно.

    Сцена



    На сцене есть два вида объектов: предметы которые нужно нарисовать на экране и источники света. Для простоты будут только шары, кубы (параллелепипеды) и точечные источники света которые равномерно светят во всех направлениях вокруг себя. Любой предмет должен уметь три вещи или, говоря другими словами, иметь три метода:

    1. norm(p) находит нормаль к поверхности предмета в точке p. Нормаль направлена наружу и имеет длину 1.
    2. color(p) говорит какой цвет на поверхности предмета в точке p.
    3. trace(ray) идёт вдоль луча ray и останавливается там где луч пересекает поверхность предмета. Этот метод возвращает координаты пересечения и расстояние от начала луча до точки пересечения.


    Вот так выглядят эти методы у сферы:

    sphere.norm = function(at)
    {	
    	return vec.mul(1 / this.r, vec.sub(at, this.q))
    }
    
    sphere.trace = function(ray)
    {
    	var a = ray.from	
    	var aq = vec.sub(a, this.q)
    	var ba = ray.dir		
    	var aqba = vec.dot(aq, ba)	
    	
    	if (aqba > 0) return
    	
    	var aq2 = vec.dot(aq, aq)
    	var qd = aq2 - this.r * this.r
    	var D = aqba * aqba - qd
    	
    	if (D < 0) return
    		
    	var t = qd > 0 ? -aqba - Math.sqrt(D) : -aqba + Math.sqrt(D)	
    	
    	var sqrdist = t * t
    	var at = vec.add(a, vec.mul(t, ba))
    	
    	return {at:at, sqrdist:sqrdist}
    }
    
    sphere.color = function(p)
    {
    	return [1, 0, 0] // red color
    }
    


    Смысл отдельных обозначений, вроде this.q, сейчас не важен: вы легко можете написать свою функцию sphere.trace. Существенно только, что написать эти три метода довольно просто. Аналогично описывается куб.

    Рейтрейсер



    Теперь перейдём к коду рейтрейсера. У него есть несколько основных функций:

    • trace(ray) идёт вдоль луча ray и останавливается там где луч пересекает какой нибудь предмет. Другими словами, эта функция находит ближайшее пересечение луча с предметом. trace возвращает координаты пересечения и расстояние до него, а также ссылку на предмет с кем пересеклись. Я написал эу функцию так:

      rt.trace = function(ray)
      {
      	var p	
      
      	for (var i in rt.objects)
      	{		
      		var obj = rt.objects[i]
      		var ep = obj.trace(ray)
      		
      		if (ep && (!p || ep.sqrdist < p.sqrdist))
      		{
      			p = ep
      			p.owner = obj
      		}
      	}	
      	
      	return p
      }
      

    • inshadow(p, lightpos) проверяет, находится ли точка p в тени от источника света в точке lightpos. Другими словами, эта функция проверяет светит ли lightpos на p. Вот её код:

      rt.inshadow = function(p, lightpos)
      {
      	var q = rt.trace(rt.ray(lightpos, p))
      	return !q || vec.sqrdist(q.at, p) > math.eps
      }
      


      На первом шаге функция выпускает луч из lightpos в точку p и смотрит где этот луч пересекает предметы. На втором шаге функция проверяет, совпадает ли точка пересечения с точкой p. Если не совпадает, значит луч света не добрался до p.
    • color(ray) выпускает луч ray и смотрит где он столкнётся с предметами. В точке столкновения узнаёт цвет поверхности и возвращает его. Вот её код:

      rt.color = function( r )
      {
      	var hit = rt.trace( r )	
      	
      	if (!hit) 
      		return rt.bgcolor
      	
      	hit.norm = hit.owner.norm(hit.at)
      		
      	var surfcol = rt.diffuse(r, hit) || [0, 0, 0]
      	var reflcol = rt.reflection(r, hit) || [0, 0, 0]
      	var refrcol = rt.refraction(r, hit) || [0, 0, 0]
      	
      	var m = hit.owner.mat // material
      	
      	return vec.sum
      	(
      		vec.mul(m.reflection, reflcol),
      		vec.mul(m.transparency, refrcol),
      		vec.mul(m.surface, surfcol)
      	)	
      }
      


      Сначала функция находит точку столкновения луча с ближайшим предметом и вычисляет нормаль к поверхности в этой точке (если такой точки не нашлось, то луч прошёл мимо всех предметов и можно вернуть цвет фона, скажем чёрный). В точке столкновения цвет суммируется из трёх частей:

      1. diffuse — цвет самой поверхности с учётом углов под которыми эту точку освещают источники света и угла под которым луч r упал на неё.
      2. reflection — цвет отражённого луча.
      3. refraction — цвет преломлённого луча.


      Эти три части суммируются с весовыми коэффициентами: цвет поверхности surfcol имеет вес m.surface, цвет отражённого луча reflcol — m.reflection, цвет преломлённого луча — m.transparency. Сумма весовых коэффициентов равна 1. Например, если прозрачность m.transparency = 0 то нет смысла считать преломление.


    Осталось рассмотреть способы вычисления цвета в точке. Есть разные подходы к реализации функций diffuse, reflection и refraction. Я рассмотрю несколько из них.

    Модель Ламберта



    Это модель вычисления цвета поверхности в зависимости от того как на неё светит источник цвета. Согласно этой модели, освещённость точки равна произведению силы источника света и косинуса угла под которым он светит на точку. Напишем функцию diffuse применяющую модель Ламберта:

    rt.diffuse = function(r, hit)
    {	
    	var obj = hit.owner	
    	var m = obj.mat
    	var sumlight = 0	
    		
    	for (var j in rt.lights)
    	{
    		var light = rt.lights[j]
    		
    		if (rt.inshadow(hit.at, light.at))
    			continue
    						
    		var dir = vec.norm(vec.sub(hit.at, light.at))				
    		var cos = -vec.dot(dir, hit.norm)
    		sumlight += light.power * cos
    	}	
    	
    	return vec.mul(sumlight, obj.color)
    }
    


    Функция перебирает все источники света и проверяет, не находится ли точка hit в тени. Если она на освещённом участке, то вычисляется вектор dir — направление от источника света light к точке hit. Затем функция находит косинус угла между нормалью hit.norm к поверхности в точке hit и направлением dir. Этот косинус равен скалярному произведению dir•hit.norm. Наконец, функция находит освещённость по Ламберту: light.power•cos.

    Вот что получается если применить только эту модель освещения:

    LambertModelExample

    Модель Фонга



    Модель Фонга (Phong) как и модель Ламберта описывает освещение точки. В отличие от модели Ламберта, эта модель учитывает под каким углом мы смотрим на поверхность. Освещённость по Фонгу вычисляется так:

    1. Проводим луч от источника света до рассматриваемой точки на поверхности и отражаем этот луч от поверхности.
    2. Находим косинус угла между отражённым лучом и направлением по которому мы смотрим на поверхность.
    3. Возводим этот косинус в некоторую степень и умножаем полученное число на силу источника света.


    Согласно этой модели, видимая освещённость точки на поверхности будет максимальной если мы в этой поверхности видим отражение источника света, т.е. он отражается прямо в глаза. Соответсвующий код diffuse:

    rt.diffuse = function(r, hit)
    {	
    	var obj = hit.owner	
    	var m = obj.mat
    	var sumlight = 0	
    		
    	for (var j in rt.lights)
    	{
    		var light = rt.lights[j]
    		
    		if (rt.inshadow(hit.at, light.at))
    			continue
    						
    		var dir = vec.norm(vec.sub(hit.at, light.at))
    		var lr = vec.reflect(dir, hit.norm)
    		var vcos = -vec.dot(lr, r.dir)
    		
    		if (vcos > 0)
    		{			
    			var phong = Math.pow(vcos, m.phongpower)
    			sumlight += light.power * phong
    		}			
    	}	
    	
    	return vec.mul(sumlight, obj.color)
    }
    


    Вот так это выглядит:

    PhongModelExample

    Видно, что одного Фонга мало для хорошего освещения, но если взять освещение по Фонгу с одним весовым коэффициентом и добавить к нему освещение по Ламберту с другим весовым коэффициентом, то получится вот такая картинка:

    LambertPlusPhong

    Код соответствующей функции diffuse я не привожу: он представляет из себя комбинацию предыдущих двух diffuse и его можно найти в файле rt.js в примере.

    Отражение



    Для вычисления цвета отражённого луча нужно этот луч отразить от поверхности используя вектор нормали и запустить уже написанную функцию rt.color для отражённого луча. Здесь есть всего одна тонкость: поверхность отражает не всю энергию луча, а только некоторый процент, поэтому добавим к лучу помимо координат начала и направления ещё и энергию. Этот параметр будет говорить, актуально ли ещё вычислять цвет луча, потому что если энергия маленькая, то цвет луча, каким бы он ни был, внесёт малый вклад в суммарный цвет получаемый в rt.color.

    rt.reflection = function(r, hit)
    {	
    	var refl = hit.owner.mat.reflection
    
    	if (refl * r.power < math.eps)
    		return
    			
    	var q = {}
    			
    	q.dir = vec.reflect(r.dir, hit.norm)		
    	q.from = hit.at
    	q.power = refl * r.power	
    					
    	return rt.color(q)
    }
    
    vec.reflect = function(a, n)
    {
    	var an = vec.dot(a, n)	
    	return vec.add(a, vec.mul(-2 * an, n))
    }
    


    Теперь у каждого предмета должен быть коэффициент отражения — он показывает какая доля энергии луча отражается от поверхности. После написания этой функции получается такая картинка:

    Reflection

    Преломление



    Когда луч света переходит из одной среды в другую, он преломляется. Об этом можно почитать в википедии. Реализация преломления почти такая же как и отражения:

    rt.refraction = function(r, hit)
    {
    	var m = hit.owner.mat
    	var t = m.transparency
    	
    	if (t * r.power < math.eps)
    		return
    	
    	var dir = vec.refract(r.dir, hit.norm, m.refrcoeff)
    	
    	if (!dir)
    		return
    		
    	var q = {}
    
    	q.dir = dir
    	q.from = hit.at
    	q.power = t * r.power	
    		
    	return rt.color(q)
    }
    
    vec.refract = function(v, n, q)
    {
    	var nv = vec.dot(n, v)
    	
    	if (nv > 0)	
    		return vec.refract(v, vec.mul(-1, n), 1/q)
    	
    	var a = 1 / q
    	var D = 1 - a * a * (1 - nv * nv)	
    	var b = nv * a + Math.sqrt(D)	
    	
    	return D < 0 ? undefined : vec.sub(vec.mul(a, v), vec.mul(b, n))
    }
    


    Теперь у каждого предмета есть коэффициент прозрачности — доля света которую он пропускает через поверхность, и коэффициент преломления — число участвующее в вычислении направления преломлённого луча.

    Refraction

    Коэффициент Френеля



    Количество отражённого света зависит от угла под которым луч падает на поверхность и коэффициента преломления. Формулу можно посмотреть в википедии. Я не стал учитывать этот эффект в рейтрейсере, потому что он вносил незаметные изменения.

    Сглаживание



    Если через каждый пиксель запускать один луч, то линии гладкие в трёхмерном пространстве окажутся ступенчатыми на экране после проектирования. Чтобы этого избежать, можно через каждый пиксель запускать несколько лучей, считать для каждого цвет и находить среднее между ними.

    Пример



    Здесь картинка 1000×1000 (RPS означает Rays Per Second — число лучей которые успевает просчитать браузер за одну секунду), а здесь другая картинка 800×800. Пример можно скачать по этой ссылке. Я сравнил скорость рендеринга в разных браузерах. Получилось следующее:

    Opera 33,000 RPS
    Chrome 38,000 RPS
    Firefox 16,000 RPS
    Explorer 20,000 RPS
    Safari 13,000 RPS


    Я использовал самые последние версии браузеров на 5-ое февраля 2011 года.

    Чего нет в этом рейтрейсере?



    Я рассмотрел базовые возможности рейстрейсера. Что если предмет стоит перед зеркалом и вы светите в зеркало? Задняя сторона предмета будет освещена отражённым светом. Что если посветить на стеклянный шар? Он соберёт лучи света как линза и на подставке под ним будет светлая точка. Что если в комнате есть только маленькое окошко через которое попадает свет? Вся комната будет слабо, но освещена. Ничего из этого рассмотренный рейтрейсер не умеет, однако это несложно дописать, потому что основная идея рейтрейсера позволяет это сделать.

    Можно заметить, что для вычисления всех функций — освещение по Ламберту, по Фонгу, отражение и преломление — требуют лишь умения складывать векторы, умножать их на число и находить скалярное произведение. Эти операции над векторами не зависят от размерности пространства и значит можно написать рейтрейсер четырёхмерного пространства, внеся некоторые изменения код.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 102

      +1
      Мозг вскипел
      • UFO just landed and posted this here
          +28
          Да вы чо, это в любом институте на компьютерной графике проходят. Автор крут тем, что сделал трассировку на JavaScript!
          • UFO just landed and posted this here
              +18
              А также НЕ на JavaSсript это не каждый сделает)
              0
              Я уже однажды писал в комментариях к одному посту о том, что наш преподаватель по компьютерной графике в Одесском национальном политехническом университете считает Paint векторным редактором… грустно :(
                +2
                язык дело вторичное — просто сейчас пришло время, когда браузеры начали поддерживать такие вещи
                +2
                Это ж самый простой и понятный, без всяких хитростей (по реализации) метод рендеринга, другое дело, что ресурсоемкий настолько, что в геймдеве в чистом виде не используется, сильно накладно по ресурсам.
                0
                В Safari не работает(
                  0
                  работает (Mac Safari 5)
                    0
                    Айпад не потягал :(
                  +23
                  Следующий по смыслу пост будет «рейтрейстинг на брейнфаке» :)
                    +2
                    Строго говоря, это всё же, наверное, ray casting?
                    • UFO just landed and posted this here
                        +3
                        Ray Casting — это другое. Задача ray casting — найти только первое пересечение луча с объектом. На нем постороены движки DooM и Wolf3D.
                        ru.wikipedia.org/wiki/Ray_casting
                        • UFO just landed and posted this here
                        +2
                        Исправьте, пожалуйста, синтаксические ошибки: ключевое слово new нельзя использовать в качестве имен функций.
                          0
                          Почему нельзя? Chrome ведь выполняет код. Я исправил, ради любопытства и запустил в Firefox. Результат тот же: браузер ест много памяти, грузит процессор и ничего не рисует (если очень подождать то нарисует один квадратик). Или что вы хотели добиться заменой new?
                            +2
                            Есть еще браузеры, Сафари и Опера. Ваш К.О.
                              +2
                              Вы определённо правы ) Исправил .new на .create
                                +4
                                В сафари получилось что-то совсем страшное, что-то не так с геометрией. А в Опере почти все хорошо, но вылез один глюк — не глюк, особенность — не особенность.
                                Когда вы помещаете imgdata на канву с помошью putImageData (это функция renderarea), у вас в imgdata должны быть значения от 0 до 255. Из-за погрешностей алгоритма у вас там иногда оказываются значения -1 и 256. Опера для этих значений делает «переполнение», Хром и Фаерфокс их обрезают. Решение простое — нужно обрезать значения вручную:

                                imgdata[base + 0] = color[0] < 0 ? 0 : (color[0] > 255 ? 255 : color[0])
                                imgdata[base + 1] = color[1] < 0 ? 0 : (color[1] > 255 ? 255 : color[1])
                                imgdata[base + 2] = color[2] < 0 ? 0 : (color[2] > 255 ? 255 : color[2])
                                
                                  +1
                                  Можно картинку из Сафари? Может быть я догадаюсь где ошибка по ней.

                                  Я думал, что image.data округляет значения по определению, а оказывается это фича Хрома. Надо мне документацию почитать по HTML5 )

                                  А вообще замечание правильное: отрицательное значение цвета говорит об ошибке в вычислении коэффициента Ламберта (его нужно как коэффициента Фонга урезать снизу нулём). Имхо, значения цвета нужно не только урезать сверху до 255, но и в конце балансировать яркость: делить оригинальные цвета на максимум, чтобы не было засвеченных областей. Это также спасёт и от чрезмерно тёмных картинок: они станут ярче.

                                  Я исправил код. Можно заново скачать пример.
                                    +1
                                    Рендер в сафари.

                                    А в Опере все поломалось. Проблема в файле sphere.js, в первых строчках, где var sp. У вас нет желания почитать про объектную модель js, про прототипы, чтобы привести код в соответствие с идеологией языка?
                                      0
                                      Не догадаюсь :( Что то тут совсем плохо. Хотя говорят, что в Mac Safari 5 работает.

                                      Я тогда оставлю __proto__, он хотя бы работает. Почитаю как нибудь про объектную модель. Вы случайно имеете ввиду не такой код?

                                      function sphere(...)
                                      {
                                        this.radius = ...
                                        this.color = ...
                                      }
                                      
                                      var s = new sphere(...)
                                      
                                        0
                                        Такой
                                          +3
                                          function Sphere(...) {
                                              this.radius = ...
                                              this.color = ...
                                              // Конструктор
                                          }
                                          Sphere.prototype = {
                                              method1: function () {
                                                  // тело первого метода
                                              },
                                              method2: function () {
                                                  // тело второго метода
                                              },
                                          };
                                          
                                          var s = new Sphere(...)
                                          
                                            0
                                            Поправил. Можно скачать обновление :)
                                            0
                                            Хотя говорят, что в Mac Safari 5 работает.
                                            У меня на Маке и на Виндовс одинаковый результат.
                              +2
                              Это просто великолепно! Нет слов.
                                +2
                                Осталось реализовать radiosity. На самом деле я думал, что это чудовищно сложный алгоритм, особенно отражения/преломления. Но судя по исходному коду… Сплошная математика.
                                  +1
                                  Сделать radiosity совсем несложно: примерно 5-10 строчек кода. Нужно из каждой точки выпустить N (большое число) лучей во все стороны и найти среднее. Но представьте сколько это будет работать на JS… Если бы код был хотя бы на Lua, я бы попробовал, а на JS не рискну )
                                  +1
                                  Прекрасно работает в firefox 4b10, только результат немного отличается от примера.
                                    +3
                                    Потому что одного луча на пиксель — недостаточно =(
                                      –1
                                      Точно )
                                      0
                                      Поставьте побольше Rays per pixel для сглаживания. А вообще у автора что-то с округлением, слишком уж рваные края получаются…
                                        0
                                        Chromium 8.0, результат в точности такой же, только RPS за 11 тысяч.
                                          +1
                                          IE9 beta1 16тыс. RPS (P9500 2,5GHz)
                                            0
                                            Хохо.
                                            Mobile Intel® Core™ i3-330M (3M Cache, 2.13 GHz)
                                              +1
                                              Упс сорри. Думал там прямой линк.
                                            0
                                            Так RPS (RPP?) разное. Чем больше, тем более гладкими будут границы.
                                            +2
                                            курсач по машграфу в Бауманке? :) если да, то Курову привет.
                                            а вообще Вы молодец, что на JavaScript решились. неординарный выбор, зато кроссплатформенный
                                            кстати, не пробовали в C++ переводить? насколько возрастет производительность?
                                            • UFO just landed and posted this here
                                                0
                                                ну то ВМК ж
                                                0
                                                «не пробовали в C++ переводить? насколько возрастет производительность?» — а вы как думаете?
                                                  +1
                                                  Раз 50 я думаю.
                                                  +1
                                                  Не, это я для интереса написал ) Сам учусь на мехмате, но там такую курсовую никогда не зададут.
                                                  0
                                                  Работает в safari4, если исправить obj.new = function на var obj = function
                                                    0
                                                    Есть еще такая давнишняя реализация: jupiter909.com/mark/jsrt.html
                                                    Дкмаю, тоже будет интересно посмотреть.
                                                      0
                                                      Да, работает быстрее.
                                                      +5
                                                      В комментарии врывается рейтрейсер реального времени на WebGL на том же javascript.
                                                      • UFO just landed and posted this here
                                                          0
                                                          Вероятно, это должно помочь: www.opennet.ru/opennews/art.shtml?num=29301

                                                          На крайний случай графика будет переложена на Mesa, но это всё равно быстрее.
                                                          +2
                                                          Запустилось на Firefox 4 Beta. Супер ) Там наверно оптимизации на ассемблере не ниже SSE4.2 )
                                                            0
                                                            скорее, там использование GPU и шейдеров )
                                                          0
                                                          а в FF4b10 работает примерно в 3 раза медленнее.
                                                          i5-650 (3.2GHz) 4Gb RAM, Win7-64
                                                          Chrome 8.0.552.237 — 18067 RPS
                                                          Firefox 4.0b10 — 6248 RPS
                                                            0
                                                            мда, пришел домой, запустил на домашнем ноуте (C2Duo T6600, 2.2GHz) — 8422 RPS
                                                            не зря мне давно хотелось ноут поменять
                                                              0
                                                              У меня вообще морально устаревший Dual-Core 2.0 GHz выдаёт 16,000 RPS. Тут наверно дело в браузере.
                                                            +2
                                                            Отличный топик.
                                                            С большим удовольствием слежу за Вашими статьями.

                                                              +5
                                                              А теперь делаем это в виде модуля для системы распределенных вычислений, миллион пользователей запускают у себя в браузере страничку, считают, отсылают посчитанное на сервер и на сервере создается 3D video.

                                                              Можно его же им и тут же показывать.
                                                                +2
                                                                Подобрать бы челюсть и вытереть слюнки с клавиатуры после такого топика!
                                                                  +1
                                                                  Свойство __proto__ не стандартное. Для совместимости с ECMAScript, лушче использовать Object.create, примерно так:

                                                                  return Object.create(cube, {a:{value:a}, b:{value:b}, color:{value:color}, mat:{value:material}});
                                                                    +1
                                                                    Ну, на самом деле, лучше было бы перейти на нормальное создание объектов.
                                                                      0
                                                                      И это тоже :) Пришлось поломать голову в понимании, как связаны объекты и почему не работает в рамках ES5
                                                                    +4
                                                                    Посмотреть не качая: ridersx.ru/bench/
                                                                      0
                                                                      В Опере работает довольно резво, вот только с цветами что-то странное:

                                                                      Как будто произошло переполнение цветовых разрядов.
                                                                      0
                                                                      Извините, не туда ответил
                                                                      +8
                                                                      РЕЙТРЕЙСЕР! НА JAVASCRIPT!!!

                                                                      Извините за капслок, но, блин, я помню еще то время когда рейтрейсеры писались на спектруме, работали в ЧБ и ждать приходилось даже оп часу и больше.

                                                                      У меня ломка детских стереотипов и вообще когнитивный диссонанс =)
                                                                      • UFO just landed and posted this here
                                                                          +1
                                                                          к сожалению из моих 4х ядер используется только одно :( А так прикольно. мы в институте делали движок на raytrace ускоряемый с помощью cuda :)
                                                                            0
                                                                            Как круто-то.
                                                                            Даешь в следующей версии фотонные карты и каустику!
                                                                              +2
                                                                              Вы готовы ждать 10 дней пока JS завершит рассчитывать фотонную карту? :) Вот здесь несколько картинок полученных на POV Ray — это оптимизированный рейтрейсер в виде нормальной программы (exe файл на Windows). Так вот, он строит одну такую картинку 8-10 часов.

                                                                              Мне кажется нет смысла «прокачивать» этот рейтрейсер. Цель статьи ведь была доказать, что рейтрейсер это просто и не важно на каком языке он написан. Следующую статью по рейтрейсингу я посвящу многомерным пространствам — интересно ведь на тень от тессеракта поглядеть :)
                                                                                0
                                                                                Можно попробовать Node.js :)
                                                                                  0
                                                                                  Предлагаете облачные вычисления всем Хабром? :)
                                                                                    0
                                                                                    Не, без фанатизма :)
                                                                                  0
                                                                                  Проекция многомерного пространства на плоскость… Да, посмотреть будет очень интересно. :)
                                                                                    0
                                                                                    Ждём!

                                                                                    А кроме проекции многомерного на плоскость будет не менее интересно посмотреть проекцию на (3d) пространство.
                                                                                    Но такое без WebGL уже никак.
                                                                                      0
                                                                                      У меня есть одна мысль как проектировать 4d на 2d (подход который выбран в википедии для рендеринга тессеракта мне не нравится), но этот метод требует отрейтрейсить хотя бы 0.3 триллиона лучей — JS такое не потянет. Я подумал, что возможно LuaJIT имеет большую производительность, но как показало сравнение (можете посмотреть в маленькой статье которую я только что написал) LuaJIT оказался в 4 раза медленнее Chrome (JavaScript V8). Так что остаётся один вариант: C+Assembler, но запустить такой пример из браузера уже не получится.

                                                                                      Откуда такое огромное количество лучей? Человек с плоским зрением не может увидеть весь тессеракт, в лучшем случае его маленький кусочек. Чтобы увидеть 4d нужно трёхмерное зрение: сначала 4d проецируется на 3d изображение, а затем это 3d можно спроецировать на 2d экран. В википедии сделали нечто подобное: они спроецировали каркас тессеракта на 3d (тривиальная операция которая требует несколько матричных умножений), затем этот каркас обтянули цилиндрами и уже его отрейтрейсили. Фактически получился рейтрейсинг 3d сцены — никакой четырёхмерности там нет.
                                                                                        0
                                                                                        astr73.narod.ru/pics/4D/4d_sph_001.jpg

                                                                                        Вот первая попытка 4D рейтрейсинга. Пока на сцене только сферы (три маленьких лежат на большой). Вычисляется цвет точек объекта, а при визуализации показывается ближайшая точка из тех, которые могли бы попасть в данный пиксель. В результате, например, зеленая сфера видна целиком (она ближе, чем белая сфера, проектирующаяся в то же место), но ее отражение в белой сфере видно лишь частично — только те его точки, которые оказались ближе других точек белой сферы. Аналогичные эффекты с тенями.
                                                                                  0
                                                                                  "… цвет луча который выходит из глаза..." конечно, феерично, но топик классный. Автору респект и плюсик в карму.

                                                                                  Отдельная просьба — когда-то в детстве баловался в 3dMax, еще в первых его версиях. при создании нового материала никогда не понимал, что значат все эти названия, даже не помню сейчас. Но вот Phong там по-моему точно было. Можете рассказать побольше про эти все параметры, которые я рандомно двигал пока не получал искомое?
                                                                                    +2
                                                                                    Спасибо :)

                                                                                    Я немного рассказал про Ламберта и Фонга. Вообщем, идея всех этих фонгов такая. Луч попал в предмет и надо узнать какого цвета та точка куда мы попали. Что есть в нашем распоряжении? Вот что мы имеем:

                                                                                    • вектор ray луча
                                                                                    • вектор dir направленный от точки к источнику света
                                                                                    • вектор view направленный от глаза к точке
                                                                                    • вектор norm нормали в данной точке


                                                                                    Комбинируя хитрым образом эти параметры мы получаем цвет точки. Это не более чем фокусы которые к физике имеют далёкое отношение, просто некоторые формулы точно подогнаны к реальности.

                                                                                    Вот например Ламберт. Цвет Ламберта просто равен cos(dir, norm) * k где k это некоторая константа, скажем сила источника света умноженная на цвет поверхности. Модель Ламберта описывает диффузное отражение: луч света падает на шероховатую поверхность и равномерно рассеивается во все стороны, поэтому в формуле Ламберта не важно чему равен view — свет всё равно рассеивается во все стороны равномерно.

                                                                                    Фонг, Блинн, Френель просто предлагают другие формулы.

                                                                                    Чтобы картинка получалась реалистичной, надо соблюдать требования к параметрам этих фонгов. Требования происходят от закона сохранения энергии. Например если луч падает на стекло, то сумма энергий отражённого луча и преломлённого равна энергии упавшего луча минус энергия поглощённая стеклом.
                                                                                      0
                                                                                      Очень интересно, а где про это можно поподробнее почитать? Я вот хочу удариться в программирование шейдеров (освещения, отражения, преломления и прочего), но копипастить формулы вообще никак не хочется, гораздо интереснее ведь изучить материал.
                                                                                        0
                                                                                        Это основы векторной алгебры: почитайте про векторное умножение, скалярное умножение и т.д.
                                                                                    0
                                                                                    Нереально круто! :D
                                                                                      –1
                                                                                      классно! можно прикрутить к серверному JS и потом запустить на компах с GPU
                                                                                      • UFO just landed and posted this here
                                                                                        +2
                                                                                        Мощная статья, но все-таки теплое с мягким не путайте) Матричные преобразования используются для афинных преобразований 3д-вершин — повороты объектов, их переносы, отсечение и все такое. В играх используется текстурирование и освещение — как грубая модель заранее расчитанного рейтрейсинга. Зато очень быстрое.
                                                                                          +1
                                                                                          Сегодня, насколько мне известно, есть два метода проектирования трёхмерных сцен на плоский экран. Первый метод основан на матричных преобразованиях. Его идея также простая, он работает быстро, но то что он рисует не похоже на фотографию и годится лишь для игр. Второй метод это рейтрейсинг.


                                                                                          Вы путаете вместе методы проецирования, растеризации и освещения.

                                                                                          К слову ray tracing далеко не единственный метод получения реалистичного освещения, смотрите en.wikipedia.org/wiki/Global_illumination

                                                                                          Кстати например в рендерере который использует Пиксар рейтрейсинг используется далеко не в первую очередь – en.wikipedia.org/wiki/PhotoRealistic_RenderMan
                                                                                            0
                                                                                            Я добавлю даже больше – рейтрейсинг чуть менее чем полностью не годится для расчета рассеяного освещения.

                                                                                            Там нужны другие алгоритмы global illumination, например en.wikipedia.org/wiki/Radiosity_(3D_computer_graphics)

                                                                                              0
                                                                                              Не совсем верно. Наиболее точный global illumination получается как раз рейтрейсингом (если точнее, bidirectional path tracing, использующий Metropolis-Hastings algorithm) — maxwell render, fryrender, kerkythea и т.д.
                                                                                              Следом идут интерполирующие алгоритмы, например, в vray — light cache + irradiance mapping. Они просчитывают только часть точек сцены, и пытаются расчитать освещение между ними.
                                                                                              Радиосити, как и photon mapping можно считать морально устаревшими на данный момент…
                                                                                            0
                                                                                            Прикрутить Worker, на вебките есть. Можно ускорить рендер на порядок
                                                                                              0
                                                                                              Я видел только то, что Worker-ы позволяют запускать минимум один js файл. Мне же нужно запустить много раз одну функцию с разными параметрами:

                                                                                              for (var y = 0; y < height; y += 100)
                                                                                                new thread(renderarea(0, y, width, 100))
                                                                                              


                                                                                              Я не знаю как тут применить Worker.
                                                                                                +1
                                                                                                nerget.com/rayjs-mt/rayjs.html
                                                                                                Специально нашел вам пример ретрейсинга с воркерами
                                                                                                  0
                                                                                                  Спасибо, буду изучать.
                                                                                              0
                                                                                              Чёрт возьми! Это лучшая статья про реализацию алгоритма рейтрейсинга!
                                                                                              Ну и конечно огромный респект, что реализовано на JS.
                                                                                                0
                                                                                                Вообще, прозрачная сфера должна переворачивать изображение. См., например, ray-tracing.ru/articles202.html
                                                                                                  0
                                                                                                  Если я возьму стеклянный шарик и посмотрю через него на букву W на бумаге, то увижу M?
                                                                                                    0
                                                                                                    У меня стеклянного шарика нет, поэтому рассуждаю теоретически. Вообще, результат зависит от того, с какого расстояния смотреть. Это как с обычной увеличивающей линзой. На одном расстоянии формируется изборажение прямое, на другом — перевёрнутое.
                                                                                                  0
                                                                                                  Одно слово, на JS круто, помню писал курсач по этому.
                                                                                                  • UFO just landed and posted this here
                                                                                                    • UFO just landed and posted this here

                                                                                                      Only users with full accounts can post comments. Log in, please.