Представлен быстрый алгоритм вычисления объёма простой, замкнутой, триангулированной 3D-сетки. Это предположение — прямое следствие теоремы о дивергенции. Дальнейшие обобщения на другие типы сеток возможны, но пока выходят за рамки рассмотрения.

Отправная точка — определение объёма как тройного интеграла по области от константы, равной единице:

V=R1dVV = \iiint_R 1 \mathrm{d}V

Пусть 𝐅\mathbf{F} — функция в 3\mathbb{R}^3 такая, что её дивергенция равна единице. Для целей данного вывода выбирается:

𝐅(x,y,z)=<x,0,0>\mathbf{F}(x, y, z) = <x, 0, 0>

Легко проверить, что

div𝐅=Fx+Fy+Fz=1+0+0=1\mathrm{div} \mathbf{F} = \frac{\partial F}{\partial x} + \frac{\partial F}{\partial y} + \frac{\partial F}{\partial z} = 1 + 0 + 0 = 1

Следовательно,

V=R1dV=Rdiv𝐅(x,y,z)dVV = \iiint_R 1 dV = \iiint_R \mathrm{div} \mathbf{F}(x, y, z) \mathrm{d}V

По теореме о дивергенции это равно поверхностному интегралу:

V=S𝐅(x,y,z)d𝐒V = \iint_S \mathbf{F}(x, y, z) \mathrm{d}\mathbf{S}

Этот поверхностный интеграл, определённый по поверхности S 3D-сетки, равен сумме своих кусочных треугольных частей. Пусть TiT_i обозначает поверхность ii-го треугольника сетки. Тогда,

V=i=0Ti𝐅(x,y,z)d𝐒V = \sum_{i = 0} \iint_{T_i} \mathbf{F}(x, y, z) \mathrm{d}\mathbf{S}

Пусть TinT_{in} представляет nn-ю вершину ii-го треугольника. Пусть Δ1\Delta_1 равно векторной разности между Ti1T_{i1} и Ti0T_{i0}, а Δ2\Delta_2 аналогично равно Ti2Ti0T_{i2} - T{i0}. Каждый отдельный треугольник TiT_i таким образом может быть параметризован как:

𝐫(u,v)=Ti0+uΔ1+vΔ2\mathbf{r}(u, v) = T_{i0} + u\Delta_1 + v\Delta_2

Тогда простое дифференцирование даёт:

𝐫u=Δ1\mathbf{r}_u = \Delta_1 𝐫v=Δ2\mathbf{r}_v = \Delta_2

Следовательно,

𝐫u×𝐫v=Δ1×Δ2\mathbf{r}_u \times \mathbf{r}_v = \Delta_1 \times \Delta_2

Таким образом, поверхностный интеграл можно переписать в терминах этой параметризации, подставив определение 𝐅\mathbf{F} где необходимо:

V=i=0Ti𝐅(x,y,z)(𝐫u×𝐫v)dAV = \sum_{i = 0} \iint_{T_i} \mathbf{F}(x, y, z) (\mathbf{r}_u \times \mathbf{r}_v) dA =i=0Ti𝐅(x,y,z)(̇Δi1×Δi2)dA= \sum_{i = 0} \iint_{T_i} \mathbf{F}(x, y, z) \dot (\Delta_{i1} \times \Delta_{i2}) dA =i=0Ti<x,0,0>(̇Δi1×Δi2)dA= \sum_{i = 0} \iint_{T_i} <x, 0, 0> \dot (\Delta_{i1} \times \Delta_{i2}) dA

Это векторное произведение постоянно на всём треугольнике и легко вычисляется по данным вершин. Достаточно вычислить только X-компоненту векторного произведения; остальные равны нулю из-за скалярного произведения с нулевыми компонентами 𝐅\mathbf{F}. VV таким образом можно переписать как:

V=i=0(Δi1×Δi2)xTixdAV = \sum_{i = 0} (\Delta_{i1} \times \Delta_{i2})_x \iint_{T_i} x dA

Далее рассматривается поверхностный интеграл TixdA\iint_{T_i} x dA. Раскрытие с помощью параметризации даёт:

TixdA=010uxdvdu=010u(Ti0x+uΔi1x+vΔi2x)dvdu\iint_{T_i} x dA = \int_{0}^{1} \int_{0}^{u} x dv du = \int_{0}^{1} \int_{0}^{u} (T_{i0x} + u \Delta_{i1x} + v \Delta_{i2x}) dv du

Этот интеграл можно вычислить напрямую, рассматривая данные вершин как константы:

0101u(Ti0x+uΔi1x+vΔi2x)dvdu\int_{0}^{1} \int_{0}^{1-u} (T_{i0x} + u \Delta_{i1x} + v \Delta_{i2x}) dv du =Ti0x0101udvdu+Δi1x0101uudvdu+Δi2x)0101uvdvdu= T_{i0x} \int_{0}^{1} \int_{0}^{1-u} dv du + \Delta_{i1x} \int_{0}^{1} \int_{0}^{1-u} u dv du + \Delta_{i2x}) \int_{0}^{1} \int_{0}^{1-u} v dv du =Ti0x(12)+Δi1x(16)+Δi2x(16)= T_{i0x} (\frac{1}{2}) + \Delta_{i1x} (\frac{1}{6}) + \Delta_{i2x} (\frac{1}{6}) =Ti0x(12)+(Ti1xTi0x)(16)+(Ti2xTi0x)(16)= T_{i0x} (\frac{1}{2}) + (T_{i1x} - T_{i0x})(\frac{1}{6}) + (T_{i2x} - T_{i0x})(\frac{1}{6}) =Ti0x(16)+(Ti1x)(16)+(Ti2x)(16)= T_{i0x} (\frac{1}{6}) + (T_{i1x})(\frac{1}{6}) + (T_{i2x})(\frac{1}{6}) =16(Ti0x+Ti1x+Ti2x)= \frac{1}{6}(T_{i0x} + T_{i1x} + T_{i2x})

Подставляя это в исходную сумму и вынося постоянный множитель 16\frac{1}{6} за пределы внутреннего цикла (чтобы избежать деления в цикле), получается следующая компактная формула для объёма:

V=16i=0(Δi1×Δi2)x(Ti0x+Ti1x+Ti2x)V = \frac{1}{6} \sum_{i = 0} (\Delta_{i1} \times \Delta_{i2})_x (T_{i0x} + T_{i1x} + T_{i2x})

Анализ производительности

Итоговый алгоритм не содержит ни численного интегрирования, ни дифференцирования. В отличие от распространённых наивных алгоритмов вычисления объёма, которые по сути эквивалентны рендерингу сетки с последующей выборкой из результата рендера — дорогостоящей операции, — здесь присутствует только один цикл, проходящий по треугольникам. Таким образом, алгоритм вычисления объёма имеет сложность O(n) относительно числа треугольников. Более того, вычисление для каждого отдельного треугольника столь же эффективно: при естественном раскрытии векторного произведения внутренняя часть содержит семь сложений и три умножения. Вне цикла требуется всего одно умножение. Таким образом, для сетки из nn треугольников алгоритму требуется 8n18n - 1 сложений и 3n+13n + 1 умножений, то есть 11n11n операций с плавающей точкой. Это очень быстро.

Для примерной оценки: если объём нужно вычислять каждый кадр в высокопроизводительном приложении на 60 кадров в секунду, без помощи GPU, используя только возможности CPU Raspberry Pi за $35, за один кадр можно обработать порядка 30 миллионов треугольников.

Мотивация

Скоро экзамен по векторному исчислению, и требуется к нему подготовиться. К тому же, кто не любит 3D-графику?!

Было бы (приятной) неожиданностью, если бы этот алгоритм оказался новым. Дополнительное исследование, проведённое после публикации, показало, что статья Efficient Feature Extraction for 2D/3D Objects in Mesh Representation авторства Ча Чжэн и Цухана Чена, судя по всему, описывает тот же алгоритм, хотя и с другим выводом. Было весело, пока это длилось!