Векторы, скалярное и векторное произведение
Векторы описывают позиции, направления и нормали, а два их произведения — скалярное и векторное — лежат в основе освещения.
Скалярное произведение двух единичных векторов равно косинусу угла между ними; векторное произведение даёт вектор, перпендикулярный обоим.
Зачем это знать
Освещение, отражения, определение видимости граней — всё считается через эти две операции. Скалярное произведение измеряет «насколько направления согласованы» (под каким углом падает свет на поверхность). Векторное даёт нормаль к плоскости и определяет ориентацию треугольника.
Нормализация вектора
Направление часто нужно как единичный вектор (длина 1) — тогда скалярное произведение сразу даёт косинус угла. Нормализация делит вектор на его длину.
import math
def normalize(v):
length = math.sqrt(sum(c*c for c in v))
return tuple(round(c / length, 3) for c in v)
print("Длина (3,4,0):", math.sqrt(3*3 + 4*4 + 0))
print("Нормализованный:", normalize((3, 4, 0)))
Вывод:
Длина (3,4,0): 5.0 Нормализованный: (0.6, 0.8, 0.0)
Скалярное произведение и угол
Скалярное произведение — сумма произведений соответствующих компонент. Для единичных векторов оно равно косинусу угла: 1 — направления совпадают, 0 — перпендикулярны, -1 — противоположны. В освещении именно эта величина определяет яркость поверхности.
import math
def dot(a, b):
return sum(x*y for x, y in zip(a, b))
def angle_deg(a, b):
la = math.sqrt(dot(a, a)); lb = math.sqrt(dot(b, b))
cos = dot(a, b) / (la * lb)
return round(math.degrees(math.acos(cos)), 1)
print("dot (1,0,0)·(1,0,0):", dot((1,0,0), (1,0,0)), "угол", angle_deg((1,0,0),(1,0,0)))
print("dot (1,0,0)·(0,1,0):", dot((1,0,0), (0,1,0)), "угол", angle_deg((1,0,0),(0,1,0)))
print("dot (1,0,0)·(-1,0,0):", dot((1,0,0), (-1,0,0)), "угол", angle_deg((1,0,0),(-1,0,0)))
Вывод:
dot (1,0,0)·(1,0,0): 1 угол 0.0 dot (1,0,0)·(0,1,0): 0 угол 90.0 dot (1,0,0)·(-1,0,0): -1 угол 180.0
Свет, падающий перпендикулярно поверхности (dot=1), освещает её максимально; вскользь (dot→0) — почти не освещает; сзади (dot<0) — не освещает вовсе.
Векторное произведение и нормаль
Векторное произведение двух векторов даёт третий, перпендикулярный обоим. Так из двух рёбер треугольника получают его нормаль — направление «наружу» от грани, без которого невозможно посчитать освещение.
def cross(a, b):
return (a[1]*b[2] - a[2]*b[1],
a[2]*b[0] - a[0]*b[2],
a[0]*b[1] - a[1]*b[0])
edge1 = (1, 0, 0) # ребро вдоль X
edge2 = (0, 1, 0) # ребро вдоль Y
print("Нормаль к плоскости XY:", cross(edge1, edge2))
Вывод:
Нормаль к плоскости XY: (0, 0, 1)
Два ребра, лежащие в плоскости XY, дали нормаль (0,0,1) — точно вверх по оси Z, перпендикулярно грани.
Как работает под капотом
В GLSL это встроенные функции dot(), cross(), normalize(), и GPU считает их аппаратно за такты. Диффузное освещение Фонга — это буквально max(dot(N, L), 0.0), где N — нормаль, L — направление на свет. Векторное произведение нормали к грани также определяет, «лицом» или «спиной» треугольник повёрнут к камере (backface culling).
Частые ошибки
- Забыть нормализовать векторы перед скалярным произведением — тогда оно не будет косинусом угла.
- Перепутать порядок в векторном произведении: cross(a,b) = -cross(b,a), нормаль смотрит в другую сторону.
- Не обрезать отрицательное диффузное освещение через max(...,0) — получите «отрицательный свет».
Итоги
- Нормализация даёт единичный вектор длины 1.
- Скалярное произведение единичных векторов = косинус угла; основа диффузного освещения.
- Векторное произведение даёт перпендикуляр — нормаль к грани.
- cross(a,b) = -cross(b,a) — порядок задаёт сторону нормали.