Документация на растеризатора
Алгоритми и математически формули
Сцената се рисува изцяло със собствен код: линейна алгебра, орбитална камера, перспективна проекция, растеризация на триъгълници, Z-буфер, текстуриране и селекция на върхове. Няма Three.js, WebGL или други графични библиотеки.
1. Топология на мрежата
Мрежата е правоъгълна решетка с R реда клетки и C колони клетки. Броят на върховете е (R + 1) × (C + 1). Всеки връх Vi,j се индексира с ред i ∈ [0, R] и колона j ∈ [0, C].
Начална позиция в световни координати
Vi,j = ( (j − C / 2) · w , (i − R / 2) · h , 0 )
w е ширината на клетка, h е височината на клетка. Равнината на мрежата е XY, а Z е нормалата.
Всяка клетка е четириъгълник и се разбива на два триъгълника, за да може повърхността да се растеризира:
Два триъгълника за клетка (i, j)
T1 = (Vi,j, Vi,j+1, Vi+1,j)
T2 = (Vi,j+1, Vi+1,j+1, Vi+1,j)
Ръбовете са отсечки между съседни върхове по хоризонтала, вертикала и по диагонала на клетката. Текстурните координати (u, v) се разпределят равномерно върху цялата мрежа, така че зареденото изображение да покрива повърхността:
(u, v)i,j = ( j / C , 1 − i / R )
2. Координатни пространства
Всяка точка минава през няколко пространства, преди да стане пиксел:
- World (световно) — координатите на върховете на мрежата.
- Camera / view — светът, видян от камерата. Камерата гледа по −Z.
- Clip — след перспективната матрица; хомогенна точка (x, y, z, w).
- NDC — нормализирани координати след деление на w.
- Screen — пиксели върху платното.
3. Орбитална камера
Камерата обикаля около точката на интерес (центъра на мрежата) по сфера. Позицията се задава със сферични координати: ъгъл на завъртане около вертикалата yaw (ψ), ъгъл на издигане pitch (θ) и разстояние radius (ρ).
Позиция на камерата
Ex = Tx + ρ · cos(θ) · sin(ψ)
Ey = Ty + ρ · sin(θ)
Ez = Tz + ρ · cos(θ) · cos(ψ)
T е целевата точка. Pitch се ограничава, за да не се преобърне камерата при θ = ±90°.
Влаченето с левия бутон променя ъглите пропорционално на преместването на мишката:
ψ ← ψ + Δx · s
θ ← clamp(θ + Δy · s, −1.4, 1.4)
s е чувствителност в радиани на пиксел. Колелото на мишката променя ρ.
4. Матрица на изгледа (look-at)
Матрицата на изгледа преобразува световни координати в координати на камерата. Базисът на камерата се строи от позицията на окото E, целта T и световния вектор нагоре U = (0, 1, 0).
Ортонормиран базис
f̂ = normalize(T − E) (напред)
ŝ = normalize(f̂ × U) (надясно)
û = ŝ × f̂ (нагоре в камерата)
В камерно пространство камерата стои в началото и гледа по −Z, затова третият ред на матрицата е −f̂. Транслацията премахва позицията на окото:
View матрица 4×4
[ ŝx ŝy ŝz −ŝ·E ]
[ ûx ûy ûz −û·E ]
[ −f̂x −f̂y −f̂z f̂·E ]
[ 0 0 0 1 ]
5. Перспективна проекция
Перспективата прави далечните обекти по-малки. Ъгълът на зрителното поле е fov, отношението на страните е aspect = ширина / височина, а ближната и далечната равнина са n и f.
Фокусен мащаб
F = 1 / tan(fov / 2)
Перспективна матрица
[ F / aspect 0 0 0 ]
[ 0 F 0 0 ]
[ 0 0 (f + n) / (n − f) (2 f n) / (n − f) ]
[ 0 0 −1 0 ]
След умножението се получава хомогенна точка C = (xc, yc, zc, wc). Делението на w възстановява перспективното свиване:
Нормализирани координати (NDC)
xndc = xc / wc
yndc = yc / wc
zndc = zc / wc
Преход към екранни пиксели
xs = (xndc · 0.5 + 0.5) · W
ys = (1 − (yndc · 0.5 + 0.5)) · H
Y се обръща, защото в NDC нагоре е +Y, а върху платното надолу е +Y.
6. Пълен графичен конвейер
За всеки връх V се изпълнява:
C = P · View · (Vx, Vy, Vz, 1)
ако wc ≤ 0, точката е зад камерата и се отхвърля
Триъгълник, чиито три върха са зад ближната равнина, не се рисува. Обхождането на върховете (winding) определя предната страна. Ако ориентираната площ в екранни координати е отрицателна, триъгълникът е задна стена и се пропуска (back-face culling):
Двойна ориентирана площ
2S = (Bx − Ax)(Cy − Ay) − (Cx − Ax)(By − Ay)
При 2S ≤ 0 триъгълникът е обърнат с гръб към камерата.
7. Растеризация и барицентрични координати
Триъгълник със екранни върхове A, B, C покрива пикселите в ограничителния правоъгълник. За пиксел P се пресмятат барицентричните координати (α, β, γ):
Барицентрични тегла
α = площ(PBC) / площ(ABC)
β = площ(PCA) / площ(ABC)
γ = площ(PAB) / площ(ABC)
α + β + γ = 1
P е вътре в триъгълника точно когато α ≥ 0, β ≥ 0 и γ ≥ 0. Всяка интерполирана величина φ (цвят, дълбочина, текстура) е:
φ(P) = α · φA + β · φB + γ · φC
Това е същият принцип като линейна интерполация, но в двумерен симплекс. Площите се пресмятат чрез кръстосано произведение в 2D, което е евтино и стабилно.
8. Z-буфер (алгоритъм на дълбочината)
За всеки пиксел се пази най-малката видяна дълбочина. Нов фрагмент се записва само ако е по-близо до камерата. Така по-близките триъгълници закриват далечните без да е нужно сортиране (за разлика от алгоритъма на художника).
Тест за дълбочина
z(P) = α zA + β zB + γ zC
ако z(P) < zBuffer[x, y] → запиши цвета и обнови буфера
Буферът се инициализира с +∞ в началото на всеки кадър. Използва се
Float32Array с размер W × H. Цветовете се пишат директно в
ImageData чрез Uint32Array.
9. Текстуриране с перспективна корекция
Линейната интерполация на (u, v) в екранното пространство е грешна при перспектива — текстурата „пълзи“. Правилната интерполация е по 1/w, защото перспективното деление е нелинейно.
Перспективно-коректни UV
invW(P) = α / wA + β / wB + γ / wC
u(P) = (α · uA / wA + β · uB / wB + γ · uC / wC) / invW(P)
v(P) = (α · vA / wA + β · vB / wB + γ · vC / wC) / invW(P)
След това се чете тексел от изображението с най-близък съсед:
xtex = floor(u · (ширина − 1))
ytex = floor(v · (височина − 1))
Ако няма заредено изображение, фрагментът се оцветява с цвета на клетката, модулиран от осветяването.
10. Осветяване по Ламберт
За всеки триъгълник се пресмята нормала в световни координати. Светлината е насочена (без позиция), което е достатъчно, за да се чете релефът на мрежата.
Нормала на лице
N = normalize( (B − A) × (C − A) )
Интензитет
I = ambient + diffuse · max(0, N · L̂)
ambient ≈ 0.28, diffuse ≈ 0.72. I се умножава по RGB на цвета или на тексела.
Векторното произведение дава нормала, перпендикулярна на равнината на триъгълника. Скаларното произведение с посоката към светлината е косинусът на ъгъла между тях — законът на Ламберт.
11. Селекция на върхове
Върховете се рисуват като малки квадратчета в екранното пространство (бийлборди — винаги обърнати към камерата). Селекцията не използва лъч в 3D, а проверка в 2D след проекцията, което е точно за този интерфейс.
Попадане в квадратче
|xmouse − xs| ≤ half и |ymouse − ys| ≤ half
Ако няколко върха се припокриват, се избира този с най-малка камерна дълбочина (най-близкият). При задържане на курсора квадратчето се осветява; при клик върхът става активен и полетата X, Y, Z се пълнят с неговите световни координати.
Ако курсорът не е върху връх, влаченето с левия бутон върти камерата. Ако е върху връх, влаченето го плъзга в равнина, успоредна на екрана.
Лъч от камерата през пиксел
xndc = 2 · xs / W − 1
yndc = 1 − 2 · ys / H
посока в view: normalize( (xndc · aspect / F, yndc / F, −1) )
Равнината на плъзгане минава през върха и има нормала, равна на посоката на камерата. Сечението лъч–равнина дава новата позиция:
Сечение лъч–равнина
лъч: X(t) = E + t · D
равнина: N · (X − V) = 0
t = N · (V − E) / (N · D)
X = E + t · D при t > 0
12. Преместване на връх и посока на ръб
Ръбът е вектор между два върха. Ако се премести Vi,j, всеки свързан ръб променя посоката и дължината си.
Посока на ръб
Dhorizontal = Vi,j+1 − Vi,j
Dvertical = Vi+1,j − Vi,j
единична посока: D̂ = D / ‖D‖
Полетата X, Y, Z записват директно световните координати на избрания връх. Промяната на Z повдига или снижава върха спрямо равнината на мрежата и така „огъва“ повърхността. Нормалите на съседните триъгълници се преизчисляват автоматично при следващия кадър, затова осветяването следва релефа.
При промяна на броя редове и колони мрежата се създава наново. При промяна само на ширината или височината на клетка X и Y се пренареждат по формулата от раздел 1, а изместването по Z се запазва.
Реализация в кода
js/math3d.js— вектори, матрици, проекция, лъчи и сечения.js/camera.js— орбитална камера и матрици View / Projection.js/mesh.js— върхове, триъгълници, ръбове и UV координати.js/renderer.js— софтуерен растеризатор, Z-буфер, текстура, квадратчета.js/app.js— връзка с интерфейса, селекция и цикъл на рисуване.