Pull to refresh

Comments 63

Ура! Я правда не поспеваю за вами, но частота выхода статей стимулирует не бросать начатое ))
А можете пояснить суть этого хака:
P.x = j; P.y = t0.y+i; // a hack to fill holes (due to int cast precision problems),
? О каких дырках речь, и почему в прошлой части подобный хак был не нужен?
В прошлых версиях он присутствовал, вот код из конечного кода второй главы: image.set(j, t0.y+i, color); // attention, due to int casts t0.y+i != A.y
А дырки… Сейчас попробую картинку сделать.
Вот что мы получаем, если закомментировать вот эту строчку с комментарием про хак:


Проблема в том, что при вычислении координат текущего пикселя я не округляю плавающую точку до целого, я кастую в целое.
int(.999) = 0
вот в этой строке кода происходит ужас:
Vec3i P = A + (B-A)*phi;

Смешно в том, что иксовую коорднату (j) и игрековую координату я точно знаю (t0.y+i). А реально я точку P вычисляю для z (в последующих статьях ещё для текстурных координат, нормальных векторов и проч), где мне такая точность ни к чему. То есть, я поправляю x и y до правильных, а z остаётся как есть.
Понял Вас, спасибо большое. Насколько понимаю, в реальном коде лучше не разбрасываться ресурсами, а произвести напрямую операции над координатой Z
Разумеется. Просто я экономлю строки кода, и оставшуюся информацию нагружу внутрь P. Она впоследствии будет не Vec3, а структурой с кучей полей. При этом я изменю только заголовок функции растеризатора, всё остальное останется нетронутым.
Я переписал код, теперь там есть аккуратное округление. Должно стать читаемее.
Простите, знаю c++ только на уровне «могу читать код и примерно понимать работу операторов» — а где добавилось округление в коде?
в файле geometry.h я добавил конструктор, который из Vec3f строит Vec3i с округлением:
template <> template <> vec<3,int>  ::vec(const vec<3,float> &v) : x(int(v.x+.5f)),y(int(v.y+.5f)),z(int(v.z+.5f)) {}

Если мы имеем число 0.9 в переменной типа float:
float a = .9;

То простой каст в целое число просто отбросит дробную часть, int (a) равен нулю. int(a+0.5) округлит.
Понял. Тоже подумал про манипуляции внутри векторов, однако предполагал, что есть просто стандартные функции округления чисел, которые лучше использовать вместо кастований.
Вы будете смеяться, но в c98 функции округления нет, появилась в c11.
А можно еще следующий вопрос: я, видимо, упустил, или не понимаю, но хуже не знать, чем спросить:
1. Непонятно, почему два массива текстурных координат, vt и f? И как понять в случае f, текстурная координата какого треугольника имеется ввиду? Возможно, я просто неверно понимаю формат этого файла
2. И второй момент, что есть интерполяция координаты? Как-то не увидел этот момент в статьях
v и vt задают вершины в геометрическом и в текстурном пространствах, соответственно.
f задаёт связь между этими вершинами, в строчках f хранятся индексы из массивов вершин v и vt.

Я не очень понял второй вопрос, можете пояснить?
В f связи — это через слеш индекс в v, индекс в vt, и индекс в vn?

По второму вопросу: и до домашки есть следующая фраза: «Стоп, мы только что интерполировали z-координату». И вот мне непонятно, где мы ее интерполировали? Гугль выдает немало ссылок, но не знаю, что именно почитать, чтоб понять этот термин в отношении координаты или Вашего рендерера.
Про строчки с f совершенно верно.

Про z-координату надо смотреть на вот эту строчку:
            Vec3i P = Vec3f(A) + Vec3f(B-A)*phi;


Реально меня интересует z-координата точки P, т.к. x и y мы уже знаем (j, t0.y+i). Поэтому можно было бы написать просто что-то типа

            int z = A.z + (B.z-A.z)*phi;


Это и есть интерполяция координаты z для треугольника t0,t1,t2:
мы посчитали z-координату точки A и точки B, найдя соответствующие alpha и beta (это линейная интерполяция).
Затем мы посчитали z для текущего пикселя, сделав линейную интерполяцию между A.z и B.z.

В сумме получилось, что z каждого пикселя считается билинейной интерполяцей координат t0.z, t1.z, t2.z.

Абсолютно точно так же можно поступить с uv координатами.
То есть phi у Вас — это просто множитель в формуле линейной интерполяции image (тут немного иначе записано, фактически множитель (x-x0)/(x1-x0)), соответственно мы при расчете не думаем в рамках третьего измерения, а просто поступаем с z, как поступили бы, скажем, с х-координатой, верно я понял смысл?
Ох, я не безнадежен :) Спасибо большое.
UFO just landed and posted this here
Захотелось немного и своими экспериментами поделиться. Пару лет назад писал рендерер на JS, просто чтобы удостовериться в соем понимании сути происходящего:

Картинки
image
image


А статьи отличные. Надеюсь, шестью частями все не ограничится, и после них последуют какие-нибудь плюшки про более частные вещи: основные принципы АА или BVH, к примеру.
Прикольно. А как это сделано? Рейтрейсер рендерер элементарно рисует отражения/тени. Я не знаю как такое делать шейдерами.
Так рейтрейсингом и сделано, собственно.
Рейтрейсеры вообще лего писать ведь. У них проблема спроизводительностью, что довольно плохо, если скажем, игры делаем.
Напишете код, который влезет на визитку? :)
А вообще зависит от применения рейтрейсера. Может быть вполне эффективно. Я буду рассказывать о примитивном рейтрейсере (в фрагмент шейдере) в статье 8.
Экспериментирую с z-буфером. Вот что будет если P.z = A.z;
Картинка
image
У вас точно такая же ошибка, как и тут. Только у вас забыта z-координата при сортировке, а не цвет.
Хотя стоп, нет, я неправ. Всё у вас хорошо, нужно только продолжать интерполировать внутри треугольника.
В общем, вот такая штука у меня получилась:
Картинка
image

Не пойму, то ли с UV-координатами у меня что-то не то, то ли дело просто в том, что я не разбирался еще с резкими переходами и поэтому так кажется.
Слепок кода вот тут: github.com/FunkyCat/3dHabraLessons/tree/546c0ab3eda58e746de15fd1fe737648a9b63af0 (осторожно, VS 2013 :) )
Мм, трудно сказать, можете дать скриншот без текстуры? Я поищу компилятор, способный сжевать ваш проект, но пока под руками c11 у меня нет :(
За c11 извиняюсь — на работе так устаю от 03 плюсов, что в своих проектах отвожу душу :)
Без текстуры
image
Так, з-буфер отличный, а вот с текстурой есть пара треугольников, которые меня смущают. Ну что, сейчас сам сяду домашку делать :)
Не сочтите за придирку, но почему вы не использовали автоцикл по коллекции ('ranged for')?
main.cpp, строка 133:
for (auto iter = fx.begin(); iter != fx.end(); iter++) {
Просто потому, что пока очень мало работал с новыми стандартами, и забыл про эту возможность :) Спасибо за замечание, поправил.
На С++11 (благодаря спискам инициализации и шаблонам с переменным количеством аргументов), этот код (после полного перетряхивания) становится очень симпатичным.

Ах да, на самом распоследнем GCC (4.9) работает regex. Можно существенно сократить парсер *.obj и добавить ему читаемости.
Да, точно у вас проблемы с UV, вот я обвёл треугольники, которые бросаются в глаза. Сейчас попробую ваш код посмотреть
Да, я уже понял в чем проблема по вашему домашнему задания — я забыл свапнуть uvA и uvB, когда A.x > B.x :) Спасибо!
Сейчас вот так:
Картинка
image

Хехе, получил сообщение о входящем комментарии ровно когда писал ответ об этой строчке кода :)
Теперь хорошая текстура
Теперь попробуйте использовать сначала нормальные вектора к вершинам, а не к треугольникам. Они сохранены в .obj файле в строчках vn x y z и
f x/x/IVN1 x/x/IVN2 x/x/IVN3

Передаёте ровно как текстурные координаты внутрь растеризатора и интерполируете.
Интенсивность считаете внутри растеризатора в зависимости от интерполированной нормали.
Получите тонировку Фонга.
Да, именно этим и займусь, если решу окончательно не готовиться к завтрашнему экзамену :) Хотя звучит довольно просто.
Я попробовал и получил.

image

Правда, не сразу. Когда мы делали плоское освещение, наш источник света имел координаты (0, 0, -1); Т.е. координата Z возрастала в направлении «от нас». Нормали модели (внутри *.obj) наоборот сохранены в пространстве с направлением Z «к нам». Тогда источник света должен иметь координаты (0, 0, 1), что соответствует системе координат openGL, но не соответствует нашим изначально выбранным.

Чтобы работали оба случая, по-быстрому можно поменять знак нормали для каждой грани (для случая плоского освещения):
Vec3f n = (world_coords[1] - world_coords[0]) ^ (world_coords[2] - world_coords[0]); // поменять местами множители
n.normalize(); 
float intensity = n * light_dir;
Господа, а есть у кого идеи, как реализовать z-буфер на JS+Canvas, если пользоваться для отрисовки канвасовскими «путями», а не рисовать самому поточечно?
Не думаю, что это возможно. Вы хотите сделать z-буфер не для точек, а для целых треугольников? Нормальной отрисовки так не добиться.
Да и таким методом вы даже не сможете тонирование потом сделать, поэтому лучше сразу делать точечную отрисовку.
Достаете back buffer (getImageData()) из canvas, полноразмерный, пишите в него поточечно, затем копируете в canvas с помощью putImageData().
Могу кинуть свой вариант такого движка на js.
Рисование ненативных треугольников, хоть и медленнее, но всё же вполне приемлемо.
Демка с z-буфером: jsfiddle.net/2wvyga24/24/
Ну, так-то оно работает, конечно, но для плавной анимации мощностей не хватает. Вот, сами гляньте:
godlin.ucoz.ru/3D/index1.html

Если водить мышкой — меняется освещение.
В статье представлен относительно простой, но не очень производительный растеризатор. Если вам действительно принципиально написать софт движок так, чтобы на js в реал тайме был высокий фпс, то попробуйте другие алгоритмы.
Ну так-то да.
Такое рисование явно годится лишь для своих экспериментов и получения опыта, а если нужно что-то реальное, лучше взять WebGL.
Ещё можно воспользоваться алгоритмом художника, отсортировав полигоны по z-координате.
Я знаю, что вы компилируете на Linux, я к сожалению не могу скомпилировать код в Visual studio. Ругается на Vec3 (to few arguments) в файле geometry.cpp. Можно скомпилировать другим компилятором (g++, clang) но хотелось бы просто поправить код.
У меня такое впечатление, что это баг вижл студии, причём конкретной версии. Я прекрасно компилировал под виндой с разными версиями, но некоторые люди жаловались, что у них не компилируется. Лично я баг воспроизвести не могу, к сожалению. Простой способ поправить — убрать кастомные конструкторы, которые обеспечивают приведение типов (в geometry.cpp)
Всем привет!, haqreu, большое спасибо за этот классный курс статей. Я пытаюсь сделать рендерер на C#, был бы признателен за любую критику/ревью кода.

Репозиторий: github.com/coremission/elRenderer

У меня есть проблема с текстурированием, в одном месте на макушке странный артефакт и глаза пустые, не могу понять где именно ошибка, просмотрел комментарии во всех статьях цикла и подобной не встретил:

Артефакт при текстурировании
image
(чуть повернутая картинка для лучшей демонстрации «артефакта», без перспективных искажений)
Критика кода простая — начиная с полного отсутствия комментариев в тексте (совершенно демотивирует искать, где же у вас там притаился растеризатор и чтение текстуры) и заканчивая фатальной неисправностью вашего компилятора, из которого монтировкой выломали циклы — разгребать кирпич кода умножения матриц на предмет, а не попутал ли автор индекс или знак — удовольствие ниже среднего.

Посмотрите мои статьи в этом курсе, с описанием того, как это здорово делается на CPP и перенесите эти идеи на ваш тотемный язык.
UFO just landed and posted this here
UFO just landed and posted this here
Вот он чоткий пацанчик флешового гетто image

А про отсечения с прозрачностью и полупрозрачностью статья будет?

Вряд ли. Я и так уже нашпиговал рендерер всякими ambient occlusion и tangent space normal mapping (к слову, они в оглавлении не показаны, надо ковыряться в моих статьях, либо идти в вики на гитхабе). Добавлять ещё фарша — усложнять программу, а она как раз задумана быть крайне короткой.
Пример

Пишу на JS. Затемнение на основе нормалей полигонов и текстурирование по отдельности выглядят нормально, но при затемнении поверх текстуры появляется сетчатая "штриховка".
Подозреваю что причина в каких-то округлениях, но где именно?

Ой какой интересный артефакт! А можно картинки затенения и текстурирования отдельно?

Я пару часов над этим багом сидел и когда уже сдался и пошёл играть в Starcraft, прямо во время загрузки игры, понял в чём ошибка!
Меня подвела преждевременная оптимизация - я использовал метод subarray, получая значения пикселя текстуры. Этот метод возвращает объект, данные которого хранятся в том же буфере, что и вся текстура. И затемнение я делал меняя значения этого объекта, а затем уже копировал его в буфер кадра. А так как точки текстуры в следствие интерполяции с последующим округлением соотносятся с точками экрана не как 1 к 1, то есть одна и та же точка текстуры соответствует нескольким пикселям экрана, то одни и те же точки в буфере подвергались затемнению несколько раз. Заменил subarray, на slice, который возвращает новый массив, и артефакт исчез.

Sign up to leave a comment.

Articles