CpuStaticViewer — настольное приложение для визуализации трёхмерных моделей формата Wavefront OBJ с интерактивным управлением сценой и параметрами отображения. Графический вывод реализован через OpenGL 3.3 Core в контексте QOpenGLWidget, при этом матричные преобразования (аффинные преобразования и проекции) вычисляются на CPU с использованием собственного матричного класса.
Ключевая идея проекта: детерминированный и контролируемый конвейер преобразований (CPU) + минимальный набор данных на GPU (VBO/EBO) + настраиваемые режимы отрисовки (грани/рёбра/вершины).
- Загрузка
.obj(вершиныv, граниf). - Формирование:
- массива вершин
vertices_xyz(XYZ), - списка уникальных рёбер
edge_indices(каждое ребро — пара индексов), - списка треугольников
tri_indices(после триангуляции граней).
- массива вершин
- Информационная строка (главный интерфейс) отображает:
- имя текущего файла,
- количество вершин,
- количество рёбер.
- Грани (заливка)
- включение/выключение,
- цвет заливки,
- режим прозрачности (в текущей конфигурации UI — дискретно 0/1).
- Рёбра
- включение/выключение,
- цвет,
- толщина (с учётом ограничений платформы, см. §7.2),
- пунктир: параметры «шаг» и «заполнение».
- Вершины
- режим: отсутствуют / круг / квадрат,
- размер,
- цвет.
- Фон
- выбор цвета фона.
- Снимок экрана
- сохранение текущего кадра в BMP/JPEG через
grabFramebuffer().
- сохранение текущего кадра в BMP/JPEG через
- Повороты модели вокруг осей X/Y/Z.
- Перемещение «камеры» (через матрицу вида).
- Два режима проекции: перспективный и ортографический.
- (Опционально, при подключении UI) масштабирование модели (увеличение/уменьшение) через кнопки и поле ввода.
Ниже приведены бинды, соответствующие текущей логике
GlWidget.
I / K— вращение вокруг X (±)J / L— вращение вокруг Y (±)U / O— вращение вокруг Z (±)
W / S- в перспективной проекции: движение по оси Z (приближение/удаление),
- в ортографической проекции: изменение масштаба видимости (zoom).
A / D— смещение по XQ / E— смещение по Y
P— переключение типа проекции (ортографическая / перспективная)Z— переключение заливки граней (on/off)
- Проекция — тип проекции, точные углы поворота (spinbox), (при включении) масштаб модели.
- Рёбра — пунктир, толщина, параметры пунктира, цвет рёбер.
- Вершины — режим, размер, цвет вершин.
- Фон — цвет фона и параметры заливки (если размещены в этой вкладке).
- Файл — загрузка модели и сохранение изображения.
В главной части окна (вне панели настроек) отображаются:
- имя открытого файла,
- количество вершин,
- количество рёбер.
Поведение:
- при открытии панели настроек (любой вкладкой) строка скрывается,
- при закрытии панели строка отображается вновь.
Все преобразования выполняются в однородных координатах: вершина (\mathbf{p}=(x,y,z,1)^\mathsf{T}). Это позволяет описывать поворот, перенос и масштаб одной матрицей (4\times4):
[ \mathbf{p}' = \mathbf{M},\mathbf{p}. ]
Итоговая матрица передаётся в OpenGL как uniform uMVP.
Повороты реализованы стандартными матрицами:
-
Вокруг X: [ \mathbf{R}_x(\alpha)= \begin{pmatrix} 1&0&0&0\ 0&\cos\alpha&-\sin\alpha&0\ 0&\sin\alpha&\cos\alpha&0\ 0&0&0&1 \end{pmatrix} ]
-
Вокруг Y: [ \mathbf{R}_y(\beta)= \begin{pmatrix} \cos\beta&0&\sin\beta&0\ 0&1&0&0\ -\sin\beta&0&\cos\beta&0\ 0&0&0&1 \end{pmatrix} ]
-
Вокруг Z: [ \mathbf{R}_z(\gamma)= \begin{pmatrix} \cos\gamma&-\sin\gamma&0&0\ \sin\gamma&\cos\gamma&0&0\ 0&0&1&0\ 0&0&0&1 \end{pmatrix} ]
Композиция поворотов соответствует следующей форме (как в коде Rz*(Ry*Rx)):
[ \mathbf{R} = \mathbf{R}_z , (\mathbf{R}_y , \mathbf{R}_x). ]
Равномерный масштаб задаётся диагональной матрицей:
[ \mathbf{S}(s)= \begin{pmatrix} s&0&0&0\ 0&s&0&0\ 0&0&s&0\ 0&0&0&1 \end{pmatrix}. ]
При добавлении масштаба матрица модели может быть записана как: [ \mathbf{M} = \mathbf{R},\mathbf{S}. ]
Положение камеры реализовано переносом мира на (-\mathbf{c}): [ \mathbf{V}=\mathbf{T}(-c_x,-c_y,-c_z), \quad \mathbf{T}(t_x,t_y,t_z)= \begin{pmatrix} 1&0&0&t_x\ 0&1&0&t_y\ 0&0&1&t_z\ 0&0&0&1 \end{pmatrix}. ]
Это соответствует концепции: «двигаем не камеру, а сцену в противоположную сторону».
OpenGL ожидает матрицу в column-major виде. Поэтому перед отправкой в glUniformMatrix4fv формируется массив float[16] в нужном порядке (например, через GetColMajor(MVP)), а GL_FALSE означает отсутствие транспонирования на стороне драйвера.
Финальная матрица строится по классической схеме: [ \mathbf{MVP} = \mathbf{P},(\mathbf{V},\mathbf{M}), \quad \mathbf{p}_{clip} = \mathbf{MVP},\mathbf{p}. ]
Перспективная матрица строится по параметрам:
- вертикальный угол обзора (fov_y),
- аспект (a=\frac{width}{height}),
- near/far плоскости (n, f).
Обозначим: [ t=\tan\left(\frac{fov_y}{2}\right), \quad s=\frac{1}{t}. ]
Типичная матрица перспективы для OpenGL (NDC (z\in[-1,1])):
[ \mathbf{P}_{persp}= \begin{pmatrix} \frac{s}{a}&0&0&0\ 0&s&0&0\ 0&0&\frac{f+n}{n-f}&\frac{2fn}{n-f}\ 0&0&-1&0 \end{pmatrix}. ]
Практическая особенность: при слишком малом расстоянии до камеры возможны артефакты; поэтому вводится нижняя граница для cam_z_, чтобы не «пролетать» через near-plane.
Ортографическая проекция задаётся симметричным объёмом: [ x\in[-w,w],\quad y\in[-h,h],\quad z\in[n,f], ] где (w=\text{half_w}), (h=\text{half_h}).
Для OpenGL (NDC (z\in[-1,1])) матрица симметричной ортографической проекции:
[ \mathbf{P}_{ortho}= \begin{pmatrix} \frac{1}{w}&0&0&0\ 0&\frac{1}{h}&0&0\ 0&0&\frac{2}{n-f}&\frac{f+n}{n-f}\ 0&0&0&1 \end{pmatrix}. ]
Отдельная особенность управления: в ортографическом режиме W/S изменяют half_w, то есть масштабируют видимый объём без перспективных искажений.
- Грани:
VAO + VBO + EBO(индексы треугольников), отрисовкаglDrawElements(GL_TRIANGLES, ...). - Рёбра: отдельный
VAO/VBO(либо индексы, либо предразвёрнутые вершины линий), отрисовкаglDrawArrays(GL_LINES, ...). - Вершины: отрисовка
GL_POINTSсGL_PROGRAM_POINT_SIZE.
На системах с Core Profile (особенно macOS) ширина линий > 1 может:
- игнорироваться драйвером,
- давать нестабильный результат.
Если требуется гарантированно «толстый каркас», корректным универсальным решением является отрисовка рёбер как геометрии (ленты из треугольников).
Текущая модель пунктира, построенная в метрике модели, закономерно зависит от масштаба/перспективы: визуальная длина штриха на экране меняется при зуме/поворотах. Для строго стабильного пунктира рекомендуется screen-space подход (период в пикселях).
v-строки формируют массив вершин.f-строки формируют грани, из которых извлекаются:- рёбра (последовательные пары индексов, замыкание последнего с первым),
- треугольники (триангуляция многоугольников).
- Qt 6 (предпочтительно) или Qt 5 (fallback в CMake)
- Компилятор с поддержкой C++20
- OpenGL 3.3 Core