Векторы, скалярное и векторное произведение

Векторы описывают позиции, направления и нормали, а два их произведения — скалярное и векторное — лежат в основе освещения.

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

Зачем это знать

Освещение, отражения, определение видимости граней — всё считается через эти две операции. Скалярное произведение измеряет «насколько направления согласованы» (под каким углом падает свет на поверхность). Векторное даёт нормаль к плоскости и определяет ориентацию треугольника.

Нормализация вектора

Направление часто нужно как единичный вектор (длина 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) — порядок задаёт сторону нормали.
Проверьте себя
1. Чему равно скалярное произведение двух единичных перпендикулярных векторов?
A1
B0
C-1
D90
2. Что даёт векторное произведение двух векторов?
AИх сумму
BВектор, перпендикулярный обоим (например, нормаль)
CКосинус угла
DДлину вектора
3. Как считается базовое диффузное освещение Фонга?
Adot(N, L) без ограничений
Bmax(dot(N, L), 0.0), где N — нормаль, L — на свет
Ccross(N, L)
Dдлина N