Комментарии 63
P.x = j; P.y = t0.y+i; // a hack to fill holes (due to int cast precision problems),
? О каких дырках речь, и почему в прошлой части подобный хак был не нужен?А дырки… Сейчас попробую картинку сделать.
Проблема в том, что при вычислении координат текущего пикселя я не округляю плавающую точку до целого, я кастую в целое.
int(.999) = 0
вот в этой строке кода происходит ужас:
Vec3i P = A + (B-A)*phi;
Смешно в том, что иксовую коорднату (j) и игрековую координату я точно знаю (t0.y+i). А реально я точку P вычисляю для z (в последующих статьях ещё для текстурных координат, нормальных векторов и проч), где мне такая точность ни к чему. То есть, я поправляю x и y до правильных, а z остаётся как есть.
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) округлит.
1. Непонятно, почему два массива текстурных координат, vt и f? И как понять в случае f, текстурная координата какого треугольника имеется ввиду? Возможно, я просто неверно понимаю формат этого файла
2. И второй момент, что есть интерполяция координаты? Как-то не увидел этот момент в статьях
f задаёт связь между этими вершинами, в строчках f хранятся индексы из массивов вершин v и vt.
Я не очень понял второй вопрос, можете пояснить?
По второму вопросу: и до домашки есть следующая фраза: «Стоп, мы только что интерполировали z-координату». И вот мне непонятно, где мы ее интерполировали? Гугль выдает немало ссылок, но не знаю, что именно почитать, чтоб понять этот термин в отношении координаты или Вашего рендерера.
Про 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 координатами.
А статьи отличные. Надеюсь, шестью частями все не ограничится, и после них последуют какие-нибудь плюшки про более частные вещи: основные принципы АА или BVH, к примеру.
Не пойму, то ли с UV-координатами у меня что-то не то, то ли дело просто в том, что я не разбирался еще с резкими переходами и поэтому так кажется.
Слепок кода вот тут: github.com/FunkyCat/3dHabraLessons/tree/546c0ab3eda58e746de15fd1fe737648a9b63af0 (осторожно, VS 2013 :) )
main.cpp, строка 133:
for (auto iter = fx.begin(); iter != fx.end(); iter++) {
Сейчас вот так:
Теперь хорошая текстура
f x/x/IVN1 x/x/IVN2 x/x/IVN3
Передаёте ровно как текстурные координаты внутрь растеризатора и интерполируете.
Интенсивность считаете внутри растеризатора в зависимости от интерполированной нормали.
Получите тонировку Фонга.
Правда, не сразу. Когда мы делали плоское освещение, наш источник света имел координаты (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;
Да и таким методом вы даже не сможете тонирование потом сделать, поэтому лучше сразу делать точечную отрисовку.
Достаете back buffer (getImageData()) из canvas, полноразмерный, пишите в него поточечно, затем копируете в canvas с помощью putImageData().
Могу кинуть свой вариант такого движка на js.
Демка с z-буфером: jsfiddle.net/2wvyga24/24/
godlin.ucoz.ru/3D/index1.html
Если водить мышкой — меняется освещение.
Такое рисование явно годится лишь для своих экспериментов и получения опыта, а если нужно что-то реальное, лучше взять WebGL.
Репозиторий: github.com/coremission/elRenderer
У меня есть проблема с текстурированием, в одном месте на макушке странный артефакт и глаза пустые, не могу понять где именно ошибка, просмотрел комментарии во всех статьях цикла и подобной не встретил:
(чуть повернутая картинка для лучшей демонстрации «артефакта», без перспективных искажений)
Посмотрите мои статьи в этом курсе, с описанием того, как это здорово делается на CPP и перенесите эти идеи на ваш тотемный язык.
А про отсечения с прозрачностью и полупрозрачностью статья будет?
Пример
Пишу на JS. Затемнение на основе нормалей полигонов и текстурирование по отдельности выглядят нормально, но при затемнении поверх текстуры появляется сетчатая "штриховка".
Подозреваю что причина в каких-то округлениях, но где именно?
Ой какой интересный артефакт! А можно картинки затенения и текстурирования отдельно?
Я пару часов над этим багом сидел и когда уже сдался и пошёл играть в Starcraft, прямо во время загрузки игры, понял в чём ошибка!
Меня подвела преждевременная оптимизация - я использовал метод subarray, получая значения пикселя текстуры. Этот метод возвращает объект, данные которого хранятся в том же буфере, что и вся текстура. И затемнение я делал меняя значения этого объекта, а затем уже копировал его в буфер кадра. А так как точки текстуры в следствие интерполяции с последующим округлением соотносятся с точками экрана не как 1 к 1, то есть одна и та же точка текстуры соответствует нескольким пикселям экрана, то одни и те же точки в буфере подвергались затемнению несколько раз. Заменил subarray, на slice, который возвращает новый массив, и артефакт исчез.
Краткий курс компьютерной графики: пишем упрощённый OpenGL своими руками, статья 3 из 6