Week 1 · Lecture 1.1
Introduction & Image Formation
Что такое курс, как формируется изображение (свет, цвет, восприятие), synthetic camera model, почему не ray tracing, и архитектура графической системы: pipeline.
Курс
Top-down подход, shader-based OpenGL, совместимый с OpenGL 3.1+, OpenGL ES 2.0 и WebGL. Нужны программирование, структуры данных, геометрия и linear algebra (векторы и матрицы). Каждую неделю programming assignments (written reports), в конце final project в формате research paper. Ресурсы: learnopengl.com, OpenGL Programmer's Guide 9th ed. (OpenGL 4.5), OpenGL Shading Language 3rd ed., GLUT/freeglut и GLEW.
Image formation
В компьютерной графике мы формируем (обычно 2D) изображения процессом, похожим на физические системы: cameras, microscopes, telescopes, human visual system.
Elements of image formationObjects · Viewer · Light source(s) · Attributes (как свет взаимодействует с материалом). Важна независимость объектов, наблюдателя и источников света.
Свет и цвет
Light = часть электромагнитного спектра, на которую реагирует зрение: примерно 350–750 nm. Длинные волны выглядят красными, короткие синими (violet 388–440, blue 440–490, green 490–565, yellow 565–590, orange 590–630, red 630–780 nm). Спектр описывается spectral energy distribution: белый (все волны), серый (меньше энергии), лазер (одна волна).
Luminance image
Монохромное: значения = уровни серого, как ч/б плёнка или ТВ.
Color image
Перцептивные атрибуты: hue, saturation, lightness. Совпадать по каждой частоте спектра не нужно.
Three-color theory
В глазу два типа сенсоров: rods (монохромные, ночное зрение) и cones (цветовые, три типа). В мозг идут только три числа, tristimulus values, поэтому достаточно совпасть по трём значениям, то есть хватает трёх primary colors.
Цветовые модели
| Модель | Как получается цвет | Где |
| Additive (RGB) | Складываем свет трёх primaries R, G, B. Пространство = RGB color cube | CRT, мониторы, проекторы, positive film. ТВ использует похожую кодировку YIQ |
| Subtractive (CMY) | Фильтруем белый свет фильтрами Cyan, Magenta, Yellow. Cyan поглощает red и отражает green+blue; blue = cyan (убирает red) + magenta (убирает green) | Печать (hardcopy), negative film, взаимодействие свет–материал |
| HSB / HSV | Hue в градусах по конусу (red 0/360, yellow 60, green 120); Saturation 0–100% от центра к поверхности (при 0% hue не имеет смысла); Brightness 0 (чёрный) – 100 (белый) | Интуитивный выбор цвета |
Ray tracing и synthetic camera
Ray tracing: прослеживаем лучи от источника и смотрим, какие попадают в линзу. Но каждый луч может много раз взаимодействовать с объектами. Global vs local lighting: цвет объекта нельзя посчитать независимо, потому что объекты загораживают свет, отражают его друг на друга и бывают полупрозрачными.
Synthetic camera model: от точки p идёт projector к center of projection; пересечение с image plane = проекция p. Геометрия проецируется на плоскость, точка закрашивается по локальной информации (local lighting).
Pinhole cameraТочка (x, y, z), плёнка на расстоянии d: xp = −x/(z/d), yp = −y/(z/d), zp = d. Это уравнения simple perspective (подобные треугольники).
Почему local lighting удобно
- объекты, наблюдатель и свет разделены;
- 2D-графика — частный случай 3D;
- простой API: задаём objects, lights, camera, attributes, а изображение строит реализация;
- быстрая аппаратная реализация, pipelined и parallel вычисления.
Why not ray tracing?
Для простых объектов (полигоны, quadrics) и точечных источников он несложен и даёт глобальные эффекты (тени, многократные отражения), но медленный и плохо подходит для интерактивных приложений. Железо для ray tracing (Pixel Machines) не выжило на рынке; на современных GPU ray tracing близок к real time.
Models and Architectures
API должен задать только objects, materials, viewer, lights. Как его реализовать?
Physical approaches
Ray tracing: следим за лучами от COP, пока они не поглотятся или не уйдут в бесконечность. Глобальные эффекты есть, но медленно и нужна вся база объектов сразу. Radiosity: подход через перенос энергии, очень медленный.
Practical approach
Обрабатываем объекты по одному в порядке генерации приложением, только local lighting. Pipeline architecture: все шаги в железе на видеокарте.
Graphics pipeline
- Vertex processingobject → camera → screen coords; каждая смена координат = матрица; цвета вершин
- Clipping & primitive assemblyвершины собираются в отрезки, полигоны; всё вне view volume отсекается
- Rasterizationдля каждого объекта набор fragments; атрибуты вершин интерполируются
- Fragment processingцвет пикселя (texture или интерполяция), hidden-surface removal
- Projection
- Объединяет 3D-вид с 3D-объектами в 2D-изображение. Perspective: все projectors сходятся в center of projection (как фото). Parallel: projectors параллельны, вместо COP — direction of projection; хорошо передаёт форму и размеры.
- Fragment
- «Potential pixel»: позиция в frame buffer + color и depth. Может быть закрыт более близким фрагментом.
- Clipping
- Виртуальная камера, как настоящая, видит только часть мира; объекты вне объёма clipped out.
API
- Objects: ограниченный набор primitives — points (0D), line segments (1D), polygons (2D), некоторые curves и surfaces (quadrics, parametric polynomials). Всё задаётся через vertices.
- Camera: 6 degrees of freedom (позиция центра линзы + ориентация), lens, film size, ориентация film plane.
- Lights: point vs distributed, spot lights, near/far, цвет. Materials: absorption (цвет), scattering (diffuse, specular).
- Также ввод с устройств (мышь, клавиатура) и возможности системы.
Old style
glBegin(GL_POLYGON);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(0.0, 1.0, 0.0);
glVertex3f(0.0, 0.0, 1.0);
glEnd();
GPU based
vec3 points[3];
points[0] = vec3(0.0, 0.0, 0.0);
points[1] = vec3(0.0, 1.0, 0.0);
points[2] = vec3(0.0, 0.0, 1.0);
// send array to GPU,
// render as triangle
Week 1 · Lecture 1.2
Programming with OpenGL: Background & Complete Programs
Immediate vs retained mode, история API до OpenGL, версии OpenGL, библиотеки (GLUT, GLEW), OpenGL как state machine, формат функций, структура программы, VAO и buffer objects, системы координат.
Immediate vs Retained mode
Immediate
- геометрия рисуется, когда CPU отправляет её на GPU
- всё надо слать заново, даже если ничего не изменилось
- после отрисовки на GPU удаляется
- большой bandwidth CPU↔GPU, мало памяти GPU
Retained
- геометрия отправляется на GPU и хранится
- показывается по команде CPU
- CPU может слать только трансформации
- мало передач, но GPU нужно много памяти
История API
- IFIPS 1973два комитета: GKS (2D, ISO/ANSI в 80-х) и Core (2D+3D)
- PHIGSиз CAD, database model, retained graphics (structures)
- X WindowDEC/MIT, client–server; PEX = PHIGS+X, неудобный
- SGI GL 1982pipeline в железе, 3D-интерактив стал простым
- OpenGL 1992platform-independent, близко к железу, только rendering
OpenGL не включает windowing и input, чтобы не зависеть от оконной системы. Изначально immediate mode, позже добавил retained. Управлялся ARB (SGI, Microsoft, Nvidia, HP, 3DLabs, IBM…), теперь Khronos Group. До версии 2.5 стабилен и backward compatible; новые возможности железа (3D textures, vertex и fragment programs) приходили через extensions.
Modern OpenGL
Производительность за счёт GPU, которым управляют программы-shaders. Работа приложения — отправить данные на GPU; рендерит GPU.
OpenGL 3.1 (2009)Полностью shader-based: нет default shaders, каждое приложение даёт vertex и fragment shader. Нет immediate mode, мало state variables, большинство функций 2.5 deprecated (помечены устаревшими, чтобы постепенно убрать). Backward compatibility не обязательна.
- OpenGL ES
- Для embedded systems. 1.0 ≈ упрощённый 2.1; 2.0 ≈ упрощённый 3.1 (shader based); 3.0 ≈ упрощённый 4.3.
- WebGL
- JavaScript-реализация ES 2.0 в браузерах.
- OpenGL 4.1–4.5
- Добавили geometry и compute shaders и tessellator.
- DirectX
- Только Windows. Плюсы: лучше контроль ресурсов, высокоуровневые функции. Минусы: новые версии не backward compatible, только Windows. Шейдеры сближают его с OpenGL.
Библиотеки
- GL (core)OpenGL32 / libGL
- Основная библиотека.
- GLU
- Utility library поверх core, чтобы не переписывать код; работает только с legacy-кодом.
- GLX / WGL / AGL
- Связь с оконной системой: X Window / Windows / Macintosh.
- GLUT
- Общая для всех окон функциональность: открыть окно, ввод с мыши и клавиатуры, меню, event-driven. Переносим, но нет slide bars и других виджетов.
- freeglut
- Обновлённый GLUT: новые возможности, context checking (часть старого GLUT требует deprecated-функций).
- GLEW
- Extension Wrangler: простой доступ к extensions системы;
#include <GL/glew.h> и glewInit().
OpenGL как state machine
Группы функций: primitives (points, line segments, triangles), attributes, data transfer & control (color, transformation, lighting), control и input (GLUT), query. Два типа функций:
Primitive generating
Могут дать вывод, если примитив виден. Как обрабатываются вершины и как выглядит примитив, определяет state.
State changing
Transformation и attribute функции. В 3.1 большинство state variables задаёт приложение и шлёт в шейдеры.
OpenGL не объектно-ориентированный, поэтому у одной логической функции много версий: glUniform3f, glUniform2i, glUniform3dv.
Формат имениglUniform3fv(p): gl — библиотека GL, Uniform — имя, 3 — размерность, f — float, v — p указатель на массив. Константы в gl.h/glu.h/glut.h: GL_DEPTH_TEST, GL_COLOR_BUFFER_BIT; типы GLfloat, GLdouble.
Shader-based OpenGL — скорее data flow, чем state machine: действие происходит в шейдерах. GLSL — C-подобный язык с vector/matrix типами, перегрузкой операторов, C++-конструкторами (как Cg и HLSL); шейдеры передаются как исходный код и компилируются.
Структура программы
- main() — задаёт callbacks, открывает окна, входит в event loop (последний оператор).
- init() — state variables (viewing, attributes), VAO, buffers, clear color.
- initShader() — read, compile, link shaders.
- callbacks — display, input, window functions.
int main(int argc, char** argv){
glutInit(&argc, argv); // init + command line
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); // window properties (ORed)
glutInitWindowSize(500, 500); // pixels
glutInitWindowPosition(0, 0); // from top-left of display
glutCreateWindow("simple");
glutDisplayFunc(mydisplay); // display callback
glewInit();
init(); // state + shaders
glutMainLoop(); // infinite event loop
}
void mydisplay(){
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
glFlush();
}
Старый «простой» код с glBegin(GL_QUAD)…glEnd() больше не работает: функции deprecated и он опирался на default-значения состояния (viewing, colors, window), которых больше нет. Но processing loop тот же. Display callback обязателен в каждой GLUT-программе и вызывается, когда окно надо обновить (например, при открытии).
VAO и Buffer Objects
У вершины много атрибутов: position, color, texture coordinates, application data.
- VAO
- Vertex Array Object объединяет все данные вершин.
glGenVertexArrays(1,&a); glBindVertexArray(a); Bind позволяет переключаться между массивами.
- Buffer object
- Переносит много данных на GPU. Create → bind → data.
GLuint buffer;
glGenBuffers(1, &buffer); // create ids
glBindBuffer(GL_ARRAY_BUFFER, buffer); // make current
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
glBindBuffer(target, id): GL_ARRAY_BUFFER — атрибуты вершин; GL_ELEMENT_ARRAY_BUFFER — массив индексов для glDrawElements. Первый вызов создаёт буфер нулевого размера.
glBufferData(target, size, data, usage): size в байтах; usage = подсказка производительности.
glBufferSubData(target, offset, size, data) — заменяет часть данных; общий размер должен быть задан glBufferData заранее.
glDeleteBuffers(n, ids) — удалить, содержимое теряется.
| Usage | Значение |
| STATIC | данные не меняются |
| DYNAMIC | меняются часто |
| STREAM | меняются каждый кадр |
| DRAW / READ / COPY | для рисования на GPU / читает приложение / и то и другое |
Зачем BO: memory manager кладёт данные в лучшую память (system, GPU, video) по подсказкам; буфер на стороне сервера, его могут разделять несколько клиентов.
Координаты, камера, viewport
- Object (world, model, problem) coordinates — единицы задаёт приложение → camera (eye) coordinates → window coordinates (пиксели).
- Камера OpenGL в начале координат смотрит в −z. Default view volume — куб со стороной 2 с центром в начале: (−1,−1,−1) → (1,1,1).
- Default orthographic проекция: точки проецируются вдоль z на плоскость z = 0.
glViewport(x, y, w, h) — часть окна под изображение, в пикселях.
- Проекция делается projection matrix. Старые функции трансформаций deprecated; варианты: свой код, GLSL-функции или
vec.h/mat.h.
Week 2 · Lecture 2.1
Math Preliminaries, Shaders, Color & Attributes
Векторы, dot и cross product, матрицы; vertex и fragment shaders, GLSL (типы, qualifiers, функции, swizzling); примитивы OpenGL, triangulation, атрибуты и цвет.
Геометрия: что нужно знать
- Affine geometry: scalars, points, vectors и операции над ними. Нет углов и расстояний. Affine combination α₁v₁ + … + αₙvₙ при Σα = 1.
- Euclidean geometry добавляет inner (dot) product: длина, расстояние, нормализация, угол, ортогональность, ортогональная проекция.
- Projective geometry — для перспективы.
Vector = n-ка чисел; сложение (коммутативно, ассоциативно, есть 0) и умножение на скаляр. Vectors ≠ points: точки складывать нельзя, но можно найти вектор между двумя точками.
Dot productu·v = u₁v₁ + … + uₙvₙ = |u||v| cos θдлина |v| = √(v·v), нормализация v/|v|, ортогональность u·v = 0, проекция
Cross productA×B = (aybz − azby, azbx − axbz, axby − aybx)вектор, ортогональный A и B (не параллельным) → нормаль
Matricesсложение, identity (1 на диагонали), умножение — не коммутативно; индекс с 0 или 1 — как договоришься
Determinant, transpose, inversedet [a b; c d] = ad − bc (рекурсивно для больших); transpose меняет строки и столбцы; AB = BA = I → B = A⁻¹ (только квадратные)
Shaders
Vertex shader
Раз на вершину. Moving vertices: transformations (modeling, projection), morphing, wave motion, fractals, particle systems. Lighting: более реалистичные модели, cartoon shaders.
Fragment shader
Раз на фрагмент. Per-fragment lighting (Phong shading, а per-vertex — Gouraud), texture mapping, procedural textures, environment mapping, bump mapping.
Эволюция: сначала шейдеры на ассемблере → extensions OpenGL → Cg (C for graphics; OpenGL и DirectX) → GLSL, часть OpenGL 2.0+. С 3.1 приложение обязано дать шейдеры.
- ApplicationglDrawArrays, vertex data
- Vertex shadergl_Position
- Primitive assembly+ clipping
- Rasterizerfragments, интерполяция
- Fragment shaderfragment color
- Frame buffer
Simple vertex shader
in vec4 vPosition;
void main(void){
gl_Position = vPosition;
}
// GLSL 1.4: attribute vec4 vPosition;
Simple fragment shader
out vec4 fragcolor;
void main(void){
fragcolor = vec4(1.0, 0.0, 0.0, 1.0);
} // every fragment red
// old: gl_FragColor = ...
GLSL
- Типы: int, float, bool, uint, double;
vec2/3/4 (ivec, bvec, uvec, dvec); mat2/3/4 — хранятся по столбцам, обращение m[row][column]; samplers. Конструкторы: vec3 a = vec3(1.0, 2.0, 3.0); vec2 b = vec2(a);
- Нет указателей; structs можно возвращать; матрицы и векторы — базовые типы:
mat3 func(mat3 a).
- Функции как в C, но нет рекурсии; передача call by value-return:
in, out, inout.
- Встроенные: тригонометрия, арифметика,
normalize, reflect, length; перегрузка для векторов и матриц.
- Swizzling: компоненты по x,y,z,w / r,g,b,a / s,t,p,q;
a[2], a.b, a.z, a.p — одно и то же; a.yz = vec2(1.0, 2.0); a.xw = b.yy;
Qualifiers
| Qualifier | Как часто меняется | Смысл |
attribute → in | не чаще раза на вершину | данные вершины из приложения: in vec3 velocity; Встроенные (gl_Position) почти все deprecated |
uniform | постоянна для всего primitive | задаёт приложение, в шейдере нельзя менять; например bounding box, угол, матрица |
varying → out/in | раз на фрагмент | из vertex в fragment shader, интерполируется rasterizer'ом |
const | никогда | как в C |
// vertex shader // fragment shader
const vec4 red = vec4(1.0, 0.0, 0.0, 1.0); in vec4 color_out;
in vec4 vPosition; out vec4 fragcolor;
out vec4 color_out; void main(void){
void main(void){ fragcolor = color_out;
gl_Position = vPosition; }
color_out = vPosition.x * red;
}
Примитивы и полигоны
GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN.
OpenGL рисует только треугольникиПолигон корректно рисуется, если он simple (рёбра не пересекаются), convex (отрезок между любыми двумя точками внутри) и flat (все вершины в одной плоскости). Проверка дорогая, поэтому приложение делает tessellation / triangulation (в OpenGL 4.1 есть tessellator).
- Длинные тонкие треугольники рисуются плохо, равносторонние хорошо → maximize minimum angle; для неструктурированных точек — Delaunay triangulation.
- Convex polygon: abc, убрать b, потом acd… Concave: рекурсивное деление.
Attributes и цвет
- Attributes: color, size и width (points, lines), stipple pattern, polygon mode (filled, edges, vertices). Функцией OpenGL осталось мало (
glPointSize).
- RGB: каждая компонента отдельно в frame buffer, обычно 8 бит; 0.0–1.0 (float) или 0–255 (unsigned byte).
- Smooth shading (default) — интерполяция цветов вершин; flat — цвет первой вершины, делается в шейдере.
- Цвет окончательно задаётся в fragment shader, но может прийти из приложения (uniform или vertex attribute), из vertex shader (varying) или вычисляться в fragment shader.
Week 2 · Lecture 2.2
More GLSL & Three Dimensions
Как связать шейдеры с приложением, vertex attributes и uniforms, double buffering и idle callback, цвет для каждой вершины, примеры vertex shaders, Sierpinski gasket и z-buffer.
Linking shaders with application
- Readфайл → null-terminated string
- Create shaderglCreateShader, glShaderSource
- CompileglCompileShader
- Program objectglCreateProgram, glAttachShader
- LinkglLinkProgram, glUseProgram
- Link variablesattributes и uniforms
Program object — контейнер для шейдеров (может содержать несколько). Шейдер-ридер: fopen → fseek в конец → ftell (размер) → fread в буфер size+1 → buf[size] = '\0'.
GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vShader, 1, &vSource, NULL);
glCompileShader(vShader);
glAttachShader(myProgObj, vShader);
Linker строит таблицу имён переменных шейдеров; приложение берёт индекс и связывает со своей переменной.
Vertex attribute
GLuint loc = glGetAttribLocation(program, "vPosition");
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, 2, GL_FLOAT,
GL_FALSE, 0, BUFFER_OFFSET(0));
Uniform
GLint angleParam =
glGetUniformLocation(myProgObj, "angle");
GLfloat my_angle = 5.0;
glUniform1f(angleParam, my_angle);
Анимация: double buffering и idle
Меняем uniform в display callback и форсируем перерисовку glutPostRedisplay(). Но нельзя показывать частично перерисованный frame buffer.
Double bufferingРисуем в back buffer, показываем front buffer, после отрисовки swap. glutInitDisplayMode(GLUT_DOUBLE), в конце display — glutSwapBuffers().
glutIdleFunc(myIdle); // when no other actions pending
void myIdle(){ /* change something */ glutPostRedisplay(); }
С GLSL 1.5 attribute/varying заменены на in/out, в приложении ничего менять не надо.
Цвет для каждой вершины
Цвет из приложения отправляем как vertex attribute или uniform — по тому, как часто он меняется. Для цвета на вершину: массив цветов того же размера, что позиции, в один buffer object:
glBufferData(GL_ARRAY_BUFFER, sizeof(points) + sizeof(colors), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(points), points);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(points), sizeof(colors), colors);
// vPosition: offset 0; vColor: offset sizeof(points)
loc2 = glGetAttribLocation(program, "vColor");
glEnableVertexAttribArray(loc2);
glVertexAttribPointer(loc2, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(sizeof(points)));
// vertex shader
in vec3 vPosition, vColor;
out vec3 color;
void main(){
gl_Position = vec4(vPosition, 1);
color = vColor; }
// fragment shader
in vec3 color;
out vec4 fragcolor;
void main(){
fragcolor = vec4(color, 1); }
Примеры vertex shaders
Wave motion
uniform float xs, zs, h, time;
vec4 t = vPosition;
t.y = vPosition.y
+ h*sin(time + xs*vPosition.x)
+ h*sin(time + zs*vPosition.z);
gl_Position = t;
Particle system
uniform vec3 vel; uniform float g, m, t;
pos.x = vPosition.x + vel.x*t;
pos.y = vPosition.y + vel.y*t
+ g/(2.0*m)*t*t;
pos.z = vPosition.z + vel.z*t;
gl_Position = MVP * vec4(pos, 1);
Геометрия отправляется один раз, каждый кадр меняется только uniform time.
Three dimensions: Sierpinski gasket
В OpenGL 2D — частный случай 3D. Переход к 3D: vec3, glUniform3f и забота о порядке отрисовки или hidden-surface removal.
- Startс треугольника
- Connectсередины сторон
- Removeцентральный треугольник
- Repeatдля трёх оставшихся
void divide_triangle(point2 a, point2 b, point2 c, int m){
point2 ab, ac, bc;
if(m > 0){
ab = (a + b)/2; ac = (a + c)/2; bc = (b + c)/2;
divide_triangle(a, ab, ac, m-1);
divide_triangle(c, ac, bc, m-1);
divide_triangle(b, bc, ac, m-1);
}
else triangle(a, b, c); // store points[i++] = a, b, c
}
void display(){ glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, NumVertices); glFlush(); }
FractalПри продолжении деления площадь → 0, а периметр → ∞. Объект ни 2D, ни 1D: fractional dimension.
3D gasket: point3, начинаем с tetrahedron, делим каждую из 4 граней (как будто из центра вынули тетраэдр, осталось 4 меньших). Можно делить и volume: середины рёбер задают 4 тетраэдра у вершин. Проблема: треугольники рисуются в порядке задания, и задние иногда оказываются поверх передних.
Hidden-surface removal: z-buffer
OpenGL использует z-buffer (depth buffer) algorithm: при рендеринге сохраняется глубина, и в изображении остаются только передние объекты.
- Visible-surface detection, реализуется в железе и софте.
- Нужна доп. структура — z-buffer того же размера, что frame buffer; хранит глубину [0..ZMAX] каждой точки.
- Если новая точка ближе сохранённой, она заменяет значение в z-buffer и цвет в frame buffer.
for each pixel: F[x][y] = BACKGROUND; Z[x][y] = -1; // back value in NPC
for each polygon
for each pixel in polygon's projection {
pz = polygon's z at (x,y);
if (pz < FRONT && pz > Z[x][y]) { // in front of back plane, closer
Z[x][y] = pz; F[x][y] = polygon's color at (x,y);
} }
| Шаг | Код |
| Request (main) | glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH) |
| Enable (init) | glEnable(GL_DEPTH_TEST) |
| Clear (display) | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) |
Week 3 · Lecture 3.1
Input and Interaction
Physical и logical devices, measure и trigger, request и event mode, GLUT callbacks и event loop, double buffering для анимации, мышь, клавиатура, reshape, timers, menus, несколько окон.
Project Sketchpad
Ivan Sutherland (MIT, 1963) задал парадигму интерактивной графики: пользователь видит объект → указывает на него (pick) устройством (light pen, mouse, trackball) → объект меняется (moves, rotates, morphs) → повтор.
Устройства
Physical
Mouse, keyboard, trackball, light pen, data tablet, joystick, space ball.
Logical
Что возвращается программе через API: position, object identifier, scalar value. scanf("%d",&x) — по коду не понять, клавиатура это, файл или другая программа: программа получает int.
Absolute vs incremental: data tablet возвращает позицию сразу. Mouse, trackball, joystick возвращают приращения или скорости, их надо интегрировать: абсолютную позицию получить трудно, зато можно менять чувствительность.
Шесть логических устройств (GKS, PHIGS)
| Device | Возвращает |
| Locator | position |
| Pick | ID объекта |
| Keyboard | string of characters |
| Stroke | array of positions |
| Valuator | floating point number |
| Choice | one of n items |
X Window ввёл client–server модель: client = OpenGL-программа, graphics server = bitmap display + pointing device + keyboard.
Input modes
- Trigger
- Сигнал ОС: кнопка мыши, нажатие или отпускание клавиши.
- Measure
- Что устройство возвращает при срабатывании: мышь — позицию, клавиатура — ASCII.
- Request mode
- Ввод отдаётся программе только при срабатывании триггера. Как клавиатура: редактируешь (backspace), пока не нажмёшь Enter.
- Event mode
- Устройств много, каждое срабатывает когда угодно. Каждый триггер создаёт event, его measure кладётся в event queue, которую проверяет программа.
Event types: window (resize, expose, iconify), mouse (click), motion, keyboard (press/release), idle (non-event: что делать, если очередь пуста).
Callbacks и event loop
Для каждого типа события задаём callback; он выполняется, когда событие происходит: glutMouseFunc(mymouse). GLUT распознаёт подмножество событий Windows, X, Mac: glutDisplayFunc, glutMouseFunc, glutReshapeFunc, glutKeyboardFunc, glutIdleFunc, glutMotionFunc, glutPassiveMotionFunc.
glutMainLoop()Последняя строка main, бесконечный цикл. На каждом проходе GLUT смотрит события в очереди и вызывает подходящий callback; если callback не задан, событие игнорируется.
- Display callback вызывается: при первом открытии окна, reshape, expose, когда программа хочет изменить картинку. Обязателен.
- glutPostRedisplay() ставит флаг; в конце цикла GLUT проверяет флаг и вызывает display один раз. Иначе много событий → много лишних перерисовок за один проход. Вызывать в конце всех callbacks, которые меняют картинку.
- Animating: рисование в frame buffer отделено от его показа (dual ported memory), поэтому виден недорисованный кадр → double buffering: front (показывается, не пишется), back (пишется, не показывается),
GLUT_DOUBLE + glutSwapBuffers().
- Idle callback выполняется, когда очередь пуста:
t += dt; glutPostRedisplay();
- Сигнатуры callbacks фиксированы (
void mymouse(GLint button, GLint state, GLint x, GLint y)), поэтому данные передаются через globals.
Mouse
mymouse(button, state, x, y): button = GLUT_LEFT/MIDDLE/RIGHT_BUTTON, state = GLUT_UP/GLUT_DOWN, позиция в окне.
Инверсия yПозиция в окне меряется в пикселях от верхнего левого угла (экран обновляется сверху вниз), а мировая система OpenGL — от нижнего левого. Поэтому y = h − y; Высота h меняется → хранить в global (из reshape) или узнать через glGetIntegerv / glGetFloatv.
void mymouse(int btn, int state, int x, int y){
if(btn==GLUT_RIGHT_BUTTON && state==GLUT_DOWN) exit(0); // terminate
if(btn==GLUT_LEFT_BUTTON && state==GLUT_DOWN) drawSquare(x, y);
}
void drawSquare(int x, int y){
y = h - y; // invert y
points[i] = point2(x+size, y+size); points[i+1] = point2(x-size, y+size);
points[i+2] = point2(x-size, y-size); points[i+3] = point2(x+size, y-size);
i += 4; glutPostRedisplay();
}
glutMotionFunc(drawSquare) — мышь движется с нажатой кнопкой; glutPassiveMotionFunc — без нажатия (порог движения зависит от системы).
glutEntryFunc(f) — курсор входит/выходит из окна: GLUT_ENTERED / GLUT_LEFT.
Keyboard
glutKeyboardFunc(mykey), void mykey(unsigned char key, int x, int y) — ASCII + позиция мыши. if(key=='Q' || key=='q') exit(0);
glutSpecialFunc(myspecial) — function и arrow keys: GLUT_KEY_F1, GLUT_KEY_UP.
glutGetModifiers() — GLUT_ACTIVE_SHIFT / CTRL / ALT; можно эмулировать трёхкнопочную мышь.
Reshape
Пользователь тянет угол окна → приложение должно перерисовать. Два варианта: показать часть мира или весь мир, подогнав под окно (может исказиться aspect ratio).
glutReshapeFunc(myreshape);
void myReshape(int w, int h){ // new size in pixels
glViewport(0, 0, w, h); // viewport = window
// viewing (projection) goes here too
} // redisplay is posted automatically
Хорошее место для viewing-функций: reshape вызывается и при первом открытии окна. У GLUT есть default reshape, но обычно пишут свой.
Timers, callbacks, menus, windows
glutTimerFunc(1000, timerColor, 0) — вызов через N мс, один раз; чтобы повторять, внутри снова вызвать glutTimerFunc.
- Callbacks можно переопределять во время работы и отключать:
glutReshapeFunc(NULL).
- Widgets (menus, slidebars, dials, input boxes) дают toolkits, но они платформозависимы; в GLUT есть только меню и немного другого.
Menus (pop-up, с submenus) — три шага
- определить пункты меню;
- определить действие для каждого пункта;
- привязать меню к кнопке мыши.
menu_id = glutCreateMenu(mymenu);
glutAddMenuEntry("clear Screen", 1);
glutAddMenuEntry("exit", 2);
glutAttachMenu(GLUT_RIGHT_BUTTON);
void mymenu(int id){ if(id == 1) glClear(...); if(id == 2) exit(0); }
// submenu: glutAddSubMenu("RGB Menu", submenu_id);
Multiple windows
glutCreateWindow, glutDestroyWindow, glutGetWindow, glutSetWindow, glutCreateSubWindow(parent, x, y, w, h), glutPostWindowRedisplay(id). OpenGL рисует в текущее окно. У каждого окна свой graphics context: свои VAO, буферы, шейдеры и display callback; меню привязаны к конкретному окну.
Week 3 · Lecture 3.2
Geometry & Representation
Scalars, vectors, points и coordinate-free геометрия; affine space, прямые, convexity, плоскости, нормали, barycentric coordinates; базис, frames, homogeneous coordinates и смена системы координат.
Coordinate-free geometry
В школе геометрия декартова: точка p = (x, y, z), результаты через алгебру координат. Но это нефизично: точки существуют независимо от произвольной системы координат, и большинство результатов от неё не зависит (два треугольника равны, если равны две стороны и угол между ними). Нужен минимальный набор элементов: scalars, vectors, points.
- Scalars
- Элементы множества с двумя операциями (сложение, умножение) и аксиомами (ассоциативность, коммутативность, обратные). Real, complex numbers. Геометрических свойств нет.
- Vectors
- Величина с direction и magnitude: сила, скорость, directed line segment. Есть обратный (−v), умножение на скаляр, zero vector (нулевая длина, направление не определено), сумма head-to-tail. Vectors lack position: одинаковые длина и направление = один и тот же вектор.
- Points
- Положение в пространстве.
v = P − Q (точка − точка = вектор), P = v + Q (точка + вектор = точка). Точки складывать нельзя.
- Linear vector space
- Scalar–vector multiplication и vector–vector addition:
v = u + 2w − 3r. Для геометрии мало — нет точек.
- Affine space
- Точки + векторное пространство: vector+vector, scalar·vector, point+vector, scalar ops. Определяем
1·P = P, 0·P = 0 (zero vector).
Lines, rays, segments
Parametric formP(α) = P₀ + α d — все точки через P₀ в направлении d. α ≥ 0 — ray. Через две точки: P(α) = Q + α(R − Q) = αR + (1 − α)Q, при 0 ≤ α ≤ 1 — отрезок QR.
Формы прямой в 2D: explicit y = mx + h, implicit ax + by + c = 0, parametric x(α) = αx₀ + (1−α)x₁. Параметрическая форма устойчивее и общее, обобщается на curves (P(α), нелинейная функция) и surfaces (P(α, β)); линейные функции дают плоскости и полигоны.
Convexity, affine sums, convex hull
- Convex: для любых двух точек объекта весь отрезок между ними в объекте.
- Affine sum:
P = α₁P₁ + … + αₙPₙ имеет смысл при α₁ + … + αₙ = 1.
- Если ещё все αᵢ ≥ 0 — convex hull: наименьший выпуклый объект, содержащий точки («shrink wrapping»).
Planes, triangles, normals
Плоскость задаётся точкой и двумя векторами или тремя точками: P(α, β) = R + α(Q − R) + β(P − R) = R + αu + βv.
Triangle: convex sum точек → при 0 ≤ α, β ≤ 1 получаем все точки треугольника.
Barycentric coordinatesТреугольник выпуклый, поэтому любая точка внутри — affine sum: P(α₁, α₂, α₃) = α₁P + α₂Q + α₃R, α₁ + α₂ + α₃ = 1, αᵢ ≥ 0.
Normal: у каждой плоскости есть вектор n, перпендикулярный ей. Из формы R + αu + βv: n = u × v, эквивалентная форма плоскости (P(α, β) − P₀) · n = 0.
Linear independence, dimension, basis
- v₁…vₙ linearly independent, если α₁v₁ + … + αₙvₙ = 0 только при всех α = 0: ни один нельзя выразить через другие.
- Dimension = максимальное число линейно независимых векторов.
- В n-мерном пространстве любые n независимых векторов — basis; любой v = α₁v₁ + … + αₙvₙ, коэффициенты единственны.
- Список {α₁…αₙ} = representation вектора в этом базисе:
v = 2v₁ + 3v₂ − 4v₃ → a = [2, 3, −4]ᵀ. В OpenGL сначала object basis, потом нужна запись в camera (eye) basis.
Чтобы связать объекты с миром, нужна frame of reference: «где точка?» без неё не ответить (world coordinates, camera coordinates).
Frames
Coordinate system (только базис) недостаточна для точек: векторы не имеют положения, так что два одинаковых вектора в разных местах — «оба правильные». В affine space добавляем к базису одну точку, origin P₀, и получаем frame (P₀, v₁, v₂, v₃):
- вектор:
v = α₁v₁ + α₂v₂ + α₃v₃; - точка:
P = P₀ + β₁v₁ + β₂v₂ + β₃v₃.
Проблема: записи p = [β₁ β₂ β₃] и v = [α₁ α₂ α₃] выглядят одинаково — точку легко спутать с вектором.
Homogeneous coordinates
Single representationОпределив 0·P = 0 и 1·P = P: v = [α₁ α₂ α₃ 0][v₁ v₂ v₃ P₀]ᵀ, P = [β₁ β₂ β₃ 1][v₁ v₂ v₃ P₀]ᵀ. Значит вектор = [α₁ α₂ α₃ 0]ᵀ, точка = [β₁ β₂ β₃ 1]ᵀ.
- Общая форма точки [x y z]:
p = [wx wy wz w]ᵀ; обратно (w ≠ 0): x ← x′/w, y ← y′/w, z ← z′/w.
- w = 0 → вектор; для точки обычно w = 1:
[x y z 1].
- Точка 3D заменяется прямой через начало координат в 4D.
Почему это ключ ко всей графике
- все стандартные трансформации (rotation, translation, scaling) — умножение на матрицы 4×4;
- hardware pipeline работает с 4-мерными представлениями;
- для orthographic viewing держим w = 0 для векторов и w = 1 для точек;
- для перспективы нужна perspective division.
Change of coordinate systems
Один вектор в двух базисах: v = α₁v₁ + α₂v₂ + α₃v₃ = β₁u₁ + β₂u₂ + β₃u₃. Каждый uᵢ выражается через {v}: uᵢ = γᵢ₁v₁ + γᵢ₂v₂ + γᵢ₃v₃. Коэффициенты дают матрицу M 3×3, и a = Mᵀb.
Change of frames (P₀, v₁, v₂, v₃) → (Q₀, u₁, u₂, u₃): дополнительно Q₀ = γ₄₁v₁ + γ₄₂v₂ + γ₄₃v₃ + P₀ → матрица 4×4. Для точек и векторов в обоих frames a = Mᵀb (4-я компонента 1 у точек, 0 у векторов). M задаёт affine transformation в homogeneous coordinates.
M =
World и camera frames
Работаем с n-ками чисел; смена frame — матрица 4×4. В OpenGL стартуем в world frame, затем переводим всё в camera frame через model-view matrix; изначально frames совпадают (M = I). Если объекты по обе стороны z = 0, надо сдвинуть camera frame:
M =
Пример цепочки frames: M₁₂ = T(4, 2), M₂₃ = T(2, 3)·S(0.5, 0.5), M₃₄ = T(6.7, 1.8)·R(45°) — одна точка P имеет разные координаты в четырёх системах.
Week 4 · Lecture 4.1
Transformations
Affine transformations, translation, rotation, scaling, reflection, shear в homogeneous coordinates; inverses, concatenation и порядок; rotation about a fixed point, instancing; трансформации в OpenGL (CTM, mat.h); smooth rotation, Euler angles и quaternions.
General и affine transformations
Трансформация переводит точки в точки и/или векторы в векторы: Q = T(P), v = T(u).
Affine transformationLine preserving. Сюда входят rigid body (rotation, translation), scaling, shear. В графике важно, что достаточно преобразовать концы отрезков (вершины), а линию между ними нарисует реализация.
Pipeline: vertices → transformation T (из приложения) → rasterizer → pixels в frame buffer.
Notation: P, Q, R — точки; u, v, w — векторы; α, β, γ — скаляры; p, q, r и u, v, w жирным — их представления (массивы из 4 чисел в homogeneous coordinates).
Translation
Сдвиг точки на вектор d (3 степени свободы): P′ = P + d. Для многих точек обычно единственный способ — все сдвигаются на один и тот же вектор. В координатах: p = [x y z 1]ᵀ, d = [dx dy dz 0]ᵀ, p′ = p + d (точка = вектор + точка).
p′ = Tp, T(dx, dy, dz) =
Матричная форма лучше: все affine transformations выражаются так же, и их можно перемножать.
Rotation
2D, вокруг начала координат на θ: радиус тот же, угол растёт на θ. Из x = r cos φ, y = r sin φ: x′ = r cos(φ + θ) = x cos θ − y sin θ, y′ = x sin θ + y cos θ.
Вокруг оси z в 3D z не меняется (2D-поворот в плоскостях z = const); вокруг x не меняется x, вокруг y не меняется y.
Rz(θ)
Rx(θ)
Ry(θ)
Arbitrary axis nc = cos θ, s = sin θ, t = 1 − c (Graphics Gems)
Rodrigues formula: P_rot = P cos θ + (n × P) sin θ + n(n · P)(1 − cos θ).
Scaling, reflection, shear
Scaling S(sx, sy, sz)растяжение/сжатие по осям, fixed point = origin: x′ = sₓx …
Shear H(θ) вдоль x«тянем грани в разные стороны»: x′ = x + y cot θ, y′ = y, z′ = z
Reflection = отрицательные коэффициенты масштаба: sₓ = −1, sy = 1; sₓ = −1, sy = −1; sₓ = 1, sy = −1.
Inverses
Можно не считать общей формулой, а использовать геометрию:
T⁻¹(dx, dy, dz) = T(−dx, −dy, −dz)
R⁻¹(θ) = R(−θ); так как cos(−θ) = cos θ, sin(−θ) = −sin θ, то R⁻¹(θ) = Rᵀ(θ) (верно для любой rotation matrix)
S⁻¹(sx, sy, sz) = S(1/sx, 1/sy, 1/sz)
Concatenation и порядок
Любую affine transformation строим перемножением R, T, S. Одна матрица применяется к множеству вершин, поэтому стоимость построения M = ABCD мала по сравнению с вычислением Mp для всех p. Сложно — понять, какую трансформацию собрать из описания задачи.
Порядокp′ = ABCp = A(B(Cp)) — правая матрица применяется первой (pre-multiply). Для строк: p′ᵀ = pᵀCᵀBᵀAᵀ (post-multiply). Умножение матриц не коммутативно: M₁M₂ ≠ M₂M₁ (например, отражение относительно y и поворот).
- Euler angles: поворот вокруг произвольной оси раскладывается:
R(θ) = Rz(θz) Ry(θy) Rx(θx). Повороты не коммутируют; в другом порядке нужны другие углы.
- Rotation about a fixed point pf: перенести pf в начало → повернуть → вернуть:
M = T(p_f) R(θ) T(−p_f).
- Instancing: объект в начале координат, по осям, стандартного размера; instance transformation: Scale → Orient → Locate, т.е.
M = T R S.
Трансформации в OpenGL
До 3.1 матрицы были частью состояния: Model-View (GL_MODELVIEW), Projection, Texture, Color; выбор через glMatrixMode. Концептуально была CTM (current transformation matrix) 4×4, применяемая ко всем вершинам: p′ = Cp; она = model-view × projection.
Операции делали post-multiplication: C ← I, C ← CT, C ← CR, C ← CT⁻¹ даёт C = TRT⁻¹ — «задом наперёд». Чтобы получить C = T⁻¹RT, операции задают в обратном порядке. Последняя заданная операция выполняется первой.
vec.h и mat.h
Типы vec2/3/4, mat2/3/4, перегруженные операторы (+, −, *, /, +=, [] …), генераторы RotateX/Y/Z, Translate, Scale, Ortho, Ortho2D, Frustum, Perspective, LookAt.
// rotation about z by 30° with fixed point (1, 2, 3)
mat4 m = identity();
m = Translate(1.0, 2.0, 3.0) * Rotate(30.0, 0.0, 0.0, 1.0) * Translate(-1.0, -2.0, -3.0);
// last matrix specified in the program is the first applied
- Матрицы хранятся как массив 16 элементов по столбцам; функции позволяют слать матрицу или её transpose.
- Matrix stacks (сохранить матрицу на потом: иерархии, display lists) — раньше были в OpenGL, теперь простой свой stack class.
- Можно слать в шейдер готовую матрицу или угол и ось, а матрицу строит шейдер. Это эффективнее, чем трансформировать данные в приложении и пересылать.
Важно в кодеGLSL-матрицы column major, поэтому транспонируем при отправке: glUniformMatrix4fv(matrix_loc, 1, GL_TRUE, model_view); В шейдере: gl_Position = xform * vPosition;
Model-view matrix: позиционирует камеру (rotations + translations или LookAt) и строит модели. Projection matrix: view volume и линза. Они больше не часть состояния OpenGL, но создавать их в приложении — хорошая стратегия.
Пример: вращающийся куб — idle увеличивает theta[axis] += 2.0 (по модулю 360) и вызывает glutPostRedisplay; mouse выбирает ось (left/middle/right → x/y/z); display: clear color+depth, glUniform, glDrawArrays, glutSwapBuffers.
Smooth rotation и quaternions
Задача: найти последовательность model-view матриц M₀…Mₙ для плавного перехода. Любой поворот — часть большого круга на сфере: найти ось и угол (virtual trackball).
- Для каждой Rᵢ находить Euler angles,
Rᵢ = Rᵢz Rᵢy Rᵢx — неэффективно.
- По конечным положениям найти ось и угол, увеличивать только угол.
- Quaternions эффективнее и стабильнее.
Проблемы Euler angles
- как интерполировать keyframes?
- углы не независимы
- интерполяция даёт gimbal lock — потерю степени свободы, когда оси совпадают
Quaternions
q = q₀ + q₁i + q₂j + q₃k — 1 real + 3 imaginary- поворот на θ вокруг единичной n:
r = (cos θ/2, sin θ/2 · n), точка p = (0, x), p′ = r p r⁻¹ - model-view → quaternion → операции → model-view
slerp (spherical linear interpolation) — равные шаги по сфере между quaternions A и B. Для нескольких keyframes Shoemake (SIGGRAPH '85) предложил Bézier curves на сфере: вариант De Casteljau со slerp.
Week 4 · Lecture 4.2
Building Models & Classical Viewing
Right/left-handed системы, представление mesh (vertex lists, edge lists), outward-facing polygons, программа вращающегося color cube; классические проекции: taxonomy, orthographic, axonometric, oblique, perspective и vanishing points.
Системы координат
Right-handed
Глядя «вниз» на начало координат, положительный поворот — против часовой (CCW).
Left-handed
Положительный поворот — по часовой (CW). Естественнее для дисплеев: большой z = «далеко» (в экран). Z = глубина.
Representing a mesh
Пример mesh: 8 nodes и 12 edges, 5 interior polygons, 6 interior (shared) edges; у каждой вершины положение vᵢ = (xᵢ, yᵢ, zᵢ).
- Simple representation
- Каждый полигон хранит координаты вершин:
vertex[i] = vec3(x1,y1,z1); vertex[i+1] = vec3(x6,y6,z6); … Неэффективно и неструктурированно: чтобы сдвинуть вершину, нужно найти все её копии.
- Geometry vs topology
- Geometry — положения вершин. Topology — как связаны вершины и рёбра (polygon = упорядоченный список вершин, рёбра между соседними и от последней к первой). Topology сохраняется, даже если geometry меняется. Хорошие структуры их разделяют.
- Vertex list
- Geometry в одном массиве вершин; polygon list хранит указатели (индексы) в него. Filled polygons рисуются правильно, но если рисовать рёбрами, общие рёбра рисуются дважды.
- Edge list
- Каждое ребро — пара индексов вершин; общие рёбра хранятся один раз. Полигоны при этом не представлены.
Inward / outward facing polygons
Порядки {v₁, v₆, v₇} и {v₆, v₇, v₁} эквивалентны (тот же полигон), а {v₁, v₇, v₆} — другой. Первые два — outward facing.
Right-hand ruleОбход вершин counter-clockwise (если смотреть снаружи) охватывает outward-pointing normal. OpenGL может по-разному обрабатывать front и back faces.
Rotating color cube
- 8 вершин единичного куба с центром в начале и сторонами по осям (
point4(±0.5, ±0.5, ±0.5, 1.0)) и 8 цветов RGBA: black, red, yellow, green, blue, magenta, white, cyan.
quad(a, b, c, d) делает 2 треугольника (a,b,c и a,c,d) и копирует позиции и цвета; colorcube(): quad(1,0,3,2), quad(2,3,7,6), quad(3,0,4,7), quad(6,5,1,2), quad(4,5,6,7), quad(5,4,0,1) → 12 треугольников, 36 вершин и 36 цветов. Порядок вершин даёт outward normals.
- init: colorcube(); VAO; один buffer размера sizeof(points)+sizeof(colors) с NULL + два glBufferSubData;
InitShader("vshdrcube.glsl", "fshdrcube.glsl"); vPosition (offset 0) и vColor (offset sizeof(points)), 4 float; thetaLoc = glGetUniformLocation(program, "theta").
- display: clear color+depth,
glUniform3fv(thetaLoc, 1, theta), glDrawArrays(GL_TRIANGLES, 0, NumVertices), glutSwapBuffers().
- mouse: GLUT_DOWN + left/middle/right → axis = X/Y/Z. idle:
theta[axis] += 0.01, если > 360 то −360, glutPostRedisplay.
- vertex shader:
gl_Position = rot * vPosition; color = vColor;
Default view volume: куб со сторонами 2, (−1,−1,−1) → (1,1,1); вся геометрия внутри проецируется параллельно на z = 0 и рендерится.
Classical viewing
Три элемента: objects, viewer с projection surface, projectors от объекта к поверхности. Классические виды основаны на их взаимном расположении; наблюдатель сам поворачивает объект, как хочет его видеть. Объекты состоят из плоских principal faces (здания, многогранники, изделия).
Planar geometric projections: проекция на плоскость; projectors либо сходятся в center of projection, либо параллельны. Сохраняют прямые, но не обязательно углы. Для карт нужны nonplanar projections.
Компьютерная графика реализует все проекции одним pipeline; классическое черчение имело отдельную технику для каждой. Главное разделение — parallel vs perspective, хотя математически parallel = предел perspective.
Taxonomy
| Ветка | Тип | Подтипы |
| Parallel | Orthographic: multiview | front, top, side |
| Orthographic: axonometric | isometric, dimetric, trimetric |
| Oblique | plan oblique, elevation oblique |
| Perspective | по числу vanishing points | 1-point, 2-point, 3-point |
Parallel projections
Orthographic: projectors ортогональны projection surface.
| Проекция | Плюсы | Минусы |
Multiview orthographic плоскость параллельна principal face | Сохраняет расстояния и углы, форму; можно мерить: building plans, manuals | Не видно, как объект выглядит на самом деле, много поверхностей скрыто → в CAD добавляют isometric |
Axonometric плоскость повернута относительно объекта | Видно три грани коробки; линии укорачиваются (foreshortened), но коэффициенты можно найти; CAD | Углы не сохраняются (круг → эллипс); оптические иллюзии (параллельные линии будто расходятся); нереалистично: дальние объекты того же масштаба, что ближние |
Oblique произвольный угол projectors к плоскости | Можно выбрать углы, чтобы выделить грань; углы в гранях, параллельных плоскости, сохраняются, и при этом видно «вокруг» сбоку | Простой камерой не получить, нужна bellows camera или специальный объектив (architectural) |
Axonometric классифицируют по тому, сколько углов у угла спроецированного куба равны: три → isometric, два → dimetric, ни одного → trimetric.
Perspective projection
Projectors сходятся в center of projection. Vanishing point: параллельные линии объекта, не параллельные projection plane, сходятся в одну точку проекции; ручные рисунки перспективы строятся по этим точкам.
| Тип | Условие | VP для куба |
| Three-point | ни одна principal face не параллельна плоскости | 3 |
| Two-point | одно principal direction параллельно плоскости | 2 |
| One-point | одна principal face параллельна плоскости | 1 |
Плюсы
Дальние объекты меньше таких же ближних (diminution) — выглядит реалистично.
Минусы
Равные отрезки на прямой проецируются неравными (nonuniform foreshortening); углы сохраняются только в плоскостях, параллельных projection plane; сложнее строить вручную (но не компьютером).
Week 5 · Lecture 5.1
Computer Viewing & Projection Matrices
Три части viewing, камера OpenGL по умолчанию, перемещение камеры и LookAt, математика перспективы и perspective division, Ortho / Frustum / Perspective, projection normalization, матрицы ортографической, косой и перспективной проекций.
Три аспекта viewing
- Positioning the cameramodel-view matrix
- Selecting a lensprojection matrix
- Clippingview volume
OpenGL camera по умолчанию: object и camera frames совпадают (model-view = identity); камера в начале координат смотрит в −z; default view volume — куб со стороной 2 с центром в начале; default projection matrix = identity, проекция orthographic, всё вне куба clipped out.
Moving the camera
Чтобы видеть объекты с положительными и отрицательными z, можно сдвинуть камеру в +z (translate camera frame) или объекты в −z (translate world frame). Это эквивалентно и задаётся model-view: Translate(0.0, 0.0, −d), d > 0. На самом деле двигается мир относительно камеры.
Любое положение — серия rotations и translations. Side view: повернуть сцену, отодвинуть от начала: C = TR.
mat4 t = Translate(0.0, 0.0, -d);
mat4 ry = RotateY(-90.0);
mat4 m = t * ry; // moves world frame relative to camera:
// the inverse of the desired camera movement
LookAt(eye, at, up)eye — где камера, at — куда смотрит, up — вектор «вверх», не параллелен направлению взгляда. Был gluLookAt в GLU, теперь mat4 mv = LookAt(vec4 eye, vec4 at, vec4 up); в mat.h; можно умножать на modeling transformations (пример: isometric view куба).
Другие viewing APIs: view reference point / view plane normal / view up (PHIGS, GKS-3D); yaw, pitch, roll; elevation, azimuth, twist; direction angles.
Projections
Default в eye frame — orthographic: xp = x, yp = y, zp = 0 (в homogeneous: M = diag(1, 1, 0, 1); на практике M = I, а z обнуляют позже). Большинство систем используют view normalization: все виды переводятся в default view трансформациями, задающими projection matrix → один pipeline для всех видов.
Simple perspective
COP в начале координат, projection plane z = d (d < 0). По видам сверху и сбоку: xp = x/(z/d), yp = y/(z/d), zp = d.
M =p = [x y z 1]ᵀ → q = Mp = [x y z z/d]ᵀ
Perspective divisionw ≠ 1, поэтому делим на w, чтобы вернуться из homogeneous coordinates: получаем ровно xp = x/(z/d), yp = y/(z/d), zp = d. Матрица делает линейную часть, деление — нелинейную.
OpenGL projection functions
- Ortho(l, r, b, t, near, far)
- Orthogonal view, параллелепипед в camera coordinates; near и far отсчитываются от камеры.
- Frustum(l, r, b, t, near, far)
- Perspective; усечённая пирамида в camera coordinates; может быть несимметричной. l < r, b < t, near < far, near и far положительны. Часто трудно получить нужный вид.
- Perspective(fovy, aspect, near, far)
- Менее гибко (симметрично), но интуитивнее: field of view в градусах, aspect = w/h, near < far > 0.
Normalization
Вместо отдельной матрицы для каждой проекции все проекции превращаем в ортогональные с default view volume. Можно использовать стандартные трансформации в pipeline и эффективно клиппировать.
- Model-view4D, nonsingular
- Projection4D, nonsingular; = vertex shader output
- Perspective division4D → 3D
- Clippingпротив default cube
- Projection3D → 2D
- Через model-view и projection остаёмся в 4D homogeneous coordinates; обе матрицы невырожденные, по умолчанию identity (orthogonal view).
- Normalization позволяет клиппировать против простого куба при любой проекции.
- Финальную проекцию откладываем до конца: важно для hidden-surface removal сохранять глубину как можно дольше.
Orthographic normalization
Найти трансформацию, которая переводит заданный clipping volume Ortho(left, right, bottom, top, near, far) в default. Два шага:
- перенести центр в начало:
T(−(left+right)/2, −(bottom+top)/2, (near+far)/2);
- масштабировать до сторон 2:
S(2/(right−left), 2/(top−bottom), 2/(near−far)).
P = ST =
Final projection: z = 0, Morth = diag(1, 1, 0, 1); общая ортографическая проекция в 4D: P = M_orth S T.
Oblique projections
Функции OpenGL не дают общих параллельных проекций. Но косой вид куба похож на скошенный (sheared) куб: Oblique = Shear + Orthographic.
H(θ, φ) =xy shear, z не меняется
Projection matrix P = M_orth H(θ, φ), в общем случае P = M_orth S T H(θ, φ). Матрица STH переводит исходный clipping volume в default; искажённый объект проецируется правильно.
Perspective normalization
Простая перспектива: COP в начале, near plane z = −1, угол обзора 90° (плоскости x = ±z, y = ±z). Матрица с последней строкой [0 0 −1 0]: x′ = x, y′ = y, z′ = z, w′ = −z → x″ = −x/z, y″ = −y/z. Матрица не зависит от far plane.
N =после деления: x″ = −x/z, y″ = −y/z, z″ = −(α + β/z)
Точка проецируется ортогонально в нужное место при любых α и β. Выбираем
α = −(far + near)/(far − near), β = −2·near·far/(far − near)
Тогда near plane → z = −1, far plane → z = 1, стороны → x = ±1, y = ±1: новый clipping volume = default.
Normalization и hidden-surface removalФорма матрицы выбрана так, что если z₁ > z₂ в исходном объёме, то z₁′ > z₂′ после трансформации: порядок по глубине сохраняется, поэтому HSR работает после нормализации. Но z″ = −(α + β/z) искажает расстояния → численные проблемы (depth precision), особенно при маленьком near.
Frustum требует сначала shear (чтобы пирамида стала прямой), затем scale к нормализованному объёму, затем perspective matrix: P = N S H, после чего нужна только ортографическая проекция.
Почему так
- один pipeline для perspective и orthogonal;
- 4D homogeneous coordinates как можно дольше, чтобы сохранить 3D-информацию для hidden-surface removal и shading;
- проще clipping.
Код
// application
model_view = LookAt(eye, at, up);
projection = Ortho(left, right, bottom, top, near, far);
// or projection = Perspective(fov, aspect, near, far);
glUniformMatrix4fv(matrix_loc, 1, GL_TRUE, model_view); // transpose: GLSL is column major
// vertex shader
gl_Position = projection * model_view * vPosition;
Simple Mesh Format (SMF) (Michael Garland): список 3D-вершин и список граней-треугольников со ссылками на вершины; индексы с 1. Нормаль треугольника: n = (p₂ − p₀) × (p₁ − p₀), нормализовать; plane: n·(p − p₀) = 0; right-hand rule задаёт внешнюю сторону.
Week 5 · Lecture 5.2
Shading
Зачем shading, взаимодействие света и материала, rendering equation, local vs global, источники света, Phong и Blinn–Phong model, нормали, shading в OpenGL: flat, Gouraud и Phong, шейдеры освещения.
Lessons learned from A2
idle() нужен, только если что-то анимируется; когда анимация остановлена, idle = NULL.
- Если keyboard/mouse/menu меняет что-то на экране, в конце callback —
glutPostRedisplay().
- Геометрию (вершины, цвета) слать, только когда она меняется; если не меняется — один раз.
- Матрицы трансформаций применять в правильном порядке.
Зачем shading
Сфера из многих полигонов одного цвета выглядит плоской. Настоящая сфера выглядит объёмной, потому что из-за взаимодействия света и материала у каждой точки свой оттенок. Нужно учитывать: light sources, material properties, location of viewer, surface orientation.
Scattering и rendering equation
Свет попадает на A: часть рассеивается, часть поглощается; рассеянный попадает на B, снова частично рассеивается, часть возвращается на A, и так далее. Бесконечное рассеяние описывает rendering equation: в общем виде не решается; ray tracing — частный случай для идеально отражающих поверхностей. Она глобальна: тени, многократное рассеяние между объектами, полупрозрачность.
Global
Правильное shading требует расчёта по всем объектам и источникам сразу. Несовместимо с pipeline.
Local
Pipeline закрашивает каждый polygon независимо. В real-time достаточно, чтобы «look right»; глобальные эффекты аппроксимируют отдельными техниками.
Light–material interaction
Свет частично поглощается, частично рассеивается (отражается). Отражённое количество задаёт цвет и яркость: поверхность красная в белом свете, потому что red отражается, остальное поглощается. Характер рассеяния зависит от гладкости и ориентации: гладкая поверхность концентрирует отражённый свет около зеркального направления, шероховатая рассеивает во все стороны.
Источники света
Общие (протяжённые) источники сложны: надо интегрировать свет со всех точек источника. Простые:
- Point source
- Позиция + цвет. Distant source — бесконечно далеко, лучи параллельны.
- Spotlight
- Ограниченный точечный источник: direction, cutoff, ослабление ∝ coseφ.
- Ambient light
- Одинаковое количество света везде; моделирует вклад многих источников и отражающих поверхностей.
Phong model
Простая модель, быстро считается. Три компоненты: diffuse, specular, ambient. Четыре вектора: к источнику l, к наблюдателю v, нормаль n, идеальное отражение r.
Ideal reflectorНормаль определяется локальной ориентацией; угол падения = углу отражения; три вектора компланарны: r = 2(l · n)n − l (все единичные).
- Diffuse (Lambertian)
- Идеальный рассеиватель: свет рассеивается одинаково во все стороны. Отражение ∝ вертикальной составляющей падающего света: ~cos θᵢ = l · n. Коэффициенты kr, kg, kb — сколько каждой компоненты отражается.
- Specular
- Большинство поверхностей ни идеально диффузные, ни зеркальные. Гладкие дают specular highlights около направления идеального отражения. Phong: член убывает с ростом угла φ между v и r:
I_r ~ ks I cos^α φ. α = shininess: 100–200 — металл, 5–10 — пластик.
- Ambient
- Результат многократного взаимодействия с окружением; зависит от цвета источников и материала: добавляем
ka Ia.
- Distance terms
- Свет точечного источника ∝ 1/d²; добавляем множитель
1/(a + bd + cd²) к diffuse и specular; константа и линейный член смягчают эффект.
Phong modelДля каждого источника и каждой цветовой компоненты (без distance terms): I = kd Id (l · n) + ks Is (v · r)^α + ka Ia. Вклады всех источников складываются.
- У каждого источника отдельные diffuse, specular, ambient (без физического обоснования, ради гибкости) × R, G, B → 9 коэффициентов: Idr, Idg, Idb, Isr, Isg, Isb, Iar, Iag, Iab.
- Материал: 9 absorption coefficients kdr…kab + shininess α.
Too intense: I > 1
MIN(I, 1) — объект может менять цвет, уходя в белый: (0.1, 0.4, 0.8) + (0.5, 0.5, 0.5) = (0.6, 0.9, 1.0).
- Перенормировать все I в 0…1: без пересвета, но надо посчитать все I до рендеринга, картинка может быть слишком яркой, контраст сбит.
- Image processing по исходным I — лучше, но дорого.
Modified Phong (Blinn) model
Specular-член Phong требует новый reflection vector и view vector для каждой вершины. Blinn предложил аппроксимацию через halfway vector:
Halfway vectorh = (l + v)/|l + v|; заменяем (v · r)^α на (n · h)^β, β подбирают под блеск. Если векторы компланарны, угол ψ между n и h — половина угла между r и v. Это modified Phong / Blinn lighting model, он в стандарте OpenGL.
Нормали
l и v задаёт приложение, r считается из l и n. Проблема — n: способ зависит от представления поверхности. OpenGL оставляет это приложению (кроме deprecated GLU quadrics и Bézier surfaces).
- Plane
- ax + by + cz + d = 0; по трём точкам:
n = (p₂ − p₀) × (p₁ − p₀), нормализовать.
- Sphere, implicit
- f(x, y, z) = 0 → нормаль = gradient. f(p) = p·p − 1 →
n = [∂f/∂x, ∂f/∂y, ∂f/∂z]ᵀ = p.
- Parametric
- x = cos u sin v, y = cos u cos v, z = sin u. Касательная плоскость задаётся ∂p/∂u и ∂p/∂v →
n = ∂p/∂u × ∂p/∂v. Так же для quadrics и parametric polynomial surfaces (Bézier patches).
Shading в OpenGL
Нужны normals, material properties, lights. Старые state-функции (glNormal, glMaterial, glLight) deprecated — считаем в приложении или шлём атрибуты в шейдеры.
- Normalization: косинусы через dot product; удобнее единичные векторы, но длину меняют трансформации (scaling не сохраняет длину) → в GLSL есть
normalize().
- Point light: позиция + RGBA для diffuse, specular, ambient:
vec4 diffuse0 = vec4(1,0,0,1); vec4 light0_pos = vec4(1.0, 2.0, 3.0, 1.0);
- Позиция в homogeneous coordinates: w = 1 — конечная точка, w = 0 — параллельный источник с заданным направлением. Distance terms обычно квадратичные:
1/(a + b·d + c·d²).
- Spotlight: direction, cutoff, attenuation ∝ coseφ.
- Global ambient: ambient зависит от цвета источников (красный свет в белой комнате даёт красный ambient, который исчезает при выключении); глобальный ambient удобен для тестов.
- Moving lights: источники — геометрические объекты, на них действует model-view. Можно двигать свет вместе с объектами, фиксировать объекты и двигать свет, фиксировать свет и двигать объекты, или двигать всё независимо.
- Material: соответствует членам модели; w-компонента = opacity (alpha):
ambient(0.2,0.2,0.2,1), diffuse(1,0.8,0,1), specular(1,1,1,1), shine = 100.
- Front and back faces: у каждой грани две стороны; заднюю часто не видно, если важно — обрабатываем в шейдере.
- Transparency: A в RGBA делает поверхность полупрозрачной, но по умолчанию всё непрозрачно; позже включим blending.
Polygonal shading
Per-vertex shading: расчёт в каждой вершине; цвета вершин становятся shades и идут в vertex shader как атрибут, либо шейдер сам считает по параметрам. По умолчанию shades интерполируются (varying) → smooth shading; uniform даёт один цвет → flat shading.
У треугольника одна нормаль; shades в вершинах почти одинаковые (идентичные для далёкого наблюдателя или без specular). Для сферы хотим разные нормали в вершинах: если центр в начале, n = p — тогда smooth shading работает (но силуэт остаётся многоугольным).
Mesh shading (Gouraud): нормаль вершины = среднее нормалей соседних граней n = (n₁+n₂+n₃+n₄)/|n₁+n₂+n₃+n₄|. Алгоритм (SMF-файл: v… и f…): массив нормалей размера массива вершин → для каждой грани unit normal и прибавить к её вершинам → нормализовать все.
| Gouraud shading | Phong shading |
| Нормали | усреднённые нормали вершин | усреднённые нормали вершин |
| Где модель освещения | в каждой вершине (vertex shader) | в каждом фрагменте (fragment shader) |
| Что интерполируется | shades (цвета) | нормали (и векторы L, E) |
| Результат | на сильно изогнутых поверхностях видны рёбра | гладко |
| Стоимость | дешевле | намного дороже; раньше не было в real time, теперь через fragment shaders |
Обоим нужны структуры данных mesh, чтобы получить нормали вершин.
Шейдеры освещения
Per-vertex (Gouraud)
vec3 pos = (ModelView*vec4(vPosition,1)).xyz;
vec3 L = normalize(LightPosition - pos);
vec3 E = normalize(-pos);
vec3 H = normalize(L + E);
vec3 N = normalize(ModelView*vec4(vNormal,0)).xyz;
float d = max(dot(L,N), 0.0);
float s = pow(max(dot(N,H),0.0), Shininess);
vec3 spec = s*SpecularProduct;
if(dot(L,N) < 0.0) spec = vec3(0.0);
color = AmbientProduct + d*DiffuseProduct + spec;
gl_Position = Projection*ModelView*vPosition;
// fragment: frag_color = vec4(color, 1.0);
Per-fragment (Phong)
// vertex shader: outputs interpolated
fN = vNormal;
fE = EyePosition - vPosition.xyz;
fL = (LightPosition.w != 0.0)
? LightPosition.xyz - vPosition.xyz
: LightPosition.xyz;
// fragment shader
vec3 N = normalize(fN), E = normalize(fE),
L = normalize(fL), H = normalize(L + E);
// same ambient + diffuse + specular,
// specular = 0 if dot(L,N) < 0
frag_color = vec4(ambient+diffuse+specular, 1.0);
AmbientProduct, DiffuseProduct, SpecularProduct = цвет источника × свойство материала, считаются в приложении и передаются uniform'ами.