От шрифтов до анимационных фильмов, кривые и поверхности составляют фундаментальные строительные блоки многих геометрических конструкций. В ходе этого сообщения в блоге я объясню, как эта модель маски может быть очень гладкой, несмотря на то, что она описывается ограниченным количеством мелких точек которые вы можете перетаскивать, чтобы изменить форму маски:
Более того, мы увидим, как такие поверхности являются естественным продолжением плоских двумерных кривых, подобных приведенной ниже:
В этой статье я буду постоянно прыгать между кривыми и поверхностями, чтобы подчеркнуть, как идеи, которые мы развиваем для волнистых линий, могут быть перенесены на трехмерные оболочки, которые мы можем формировать.
Прежде чем строить сложные кривые и поверхности, мы сначала должны решить, как мы хотим контролировать их форму. Есть много способов описать и слепить эти объекты, но некоторые из них лучше других. В демонстрации ниже я построил систему, которая позволяет изменять форму кривой контролируя ее спиральность и ] размер :
Хотя с этой системой весело играть, она довольно непрактична. Мы не можем контролировать, где именно заканчивается кривая, и мы также имеем очень ограниченное влияние на то, как она поворачивается. Например, с помощью этих элементов управления невозможно создать S-образную кривую.
В идеале мы должны иметь дело с системой, которая позволила бы нам напрямую влиять на расположение кривой, а также обеспечивала бы удобный контроль над всей ее формой. Это функции, которые предоставляют контрольные точки . Их визуальное размещение позволяет очень легко определить форму кривой. В демонстрации ниже вы можете перетащить каждую контрольную точку, показанную как чтобы изменить форму кривой кривой :
Контрольные точки часто визуализируются соединительными линиями, соединяющими их с соседями. Это помогает избежать неоднозначности, когда две разные точки меняют местами свои позиции, из-за чего кривая может выглядеть совершенно по-разному.
Обратите внимание, что все контрольные точки, кажется, влияют на форму кривой в окрестности точки. Мы можем визуализировать воздействие данной точки, используя красный цвет. В демонстрации ниже вы можете нажать щелкнуть контрольную точку, чтобы увидеть, как она влияет на локальную форму — красный участок кривой, тем больше на нее влияет эта контрольная точка. Серые секции вообще не перемещаются при перетаскивании выбранной точки:
С помощью контрольных точек мы можем легко изменить, где кривая начинается, заканчивается и как она течет в середине. Причем форма линий, соединяющих контрольные точки, примерно напоминает форму полученной кривой. Эта система контрольных точек проста, но очень эффективна.
До сих пор я довольно расплывчато говорил о правилах, определяющих форму получающейся кривой и степени, в которой на нее влияют контрольные точки. Я попытаюсь объяснить эти основные принципы, построив эту систему с нуля. Мы также увидим, как эти идеи распространяются на поверхности, но прежде, чем мы построим эти более сложные элементы, мы начнем с простейшей кривой — линейного сегмента.
В демонстрации ниже вы можете перетащить контрольные точки A и B чтобы изменить их положение. Ползунок управляет виртуальным красным пером которое используется для рисования сегмента между этими двумя точками:
Хотя эта демонстрация является самым примитивным приложением для рисования в мире, мы уже можем сделать несколько важных наблюдений. Ползунок управляет прогрессом нашего рисунка — чем более продвинутый ползунок, тем дальше мы продвигаемся в рисовании. При прогрессе 0,5 мы прошли половину рисунка, а при прогрессе 0,9 мы в основном закончили.
Кроме того, точка на отрезке, очень близкая к точке B как показано кончиком пера, очень мало перемещается при перетаскивании точки A но перемещается на лот, когда перетаскивается точка B .
Мы можем визуализировать «вес» данной контрольной точки на кривой, используя красный оттенок, который мы видели ранее. Вы можете нажать щелкнуть контрольные точки, чтобы изменить, какая из них выбрана:
Для такого линейного сегмента функция взвешивания довольно проста. Чем ближе мы к контрольной точке, тем она эффективнее. Давайте попробуем обозначить эти зависимости цифрами.
Есть много способов указать влияние контрольной точки на расстоянии, но самый простой подход — предположить, что эти веса изменяются пропорционально этому расстоянию. По мере того, как мы продвигаемся по кривой с помощью ползунка, мы уменьшаем вес точки A и увеличиваем вес точки B :
Надеюсь, это имеет интуитивный смысл. Когда мы полностью проходим кривую, вес точки A равен 0, и мы можем перемещать ее сколько угодно, не влияя на положение конца кривой.
Мы можем описать эти маленькие сюжеты с помощью некоторых уравнений. Слово «прогресс» сделало бы формулы очень длинными, поэтому я заменю его традиционно используемой буквой t :
t = прогресс
Положение любой точки P на линейном отрезке для данного прогресса t :
P = A × (1 — t) + B × t
Мы будем часто видеть эти термины (1 — t) и t поэтому, чтобы их было легче различать, я отмечу их и я помещу маленькие треугольники с правой стороны, чтобы быстро увидеть, идет ли этот участок вверх или вниз по мере продвижения — эти треугольники соответствуют формам графиков, которые мы только что видели:
Это уравнение описывает линейную интерполяцию и будет очень важно в наших дальнейших обсуждениях. Мы действительно можем проверить, что, например, прогресс вставки t равный 1, просто возвращает точку B .
Может быть не сразу понятно, что значит умножение точки на число, поэтому давайте немного рассмотрим это. В конечном итоге каждая точка имеет координаты x и y которые определяют ее положение относительно некоторого начала:
Когда мы умножаем точку на некоторое число, мы фактически индивидуально умножаем ее координаты x и y :
P x |
= |
A x | × | + | B x | × | ||
P y |
= |
A y | × | + | B y | × |
Мы можем увидеть этот процесс в визуальной форме, отслеживая координаты всех трех точек по мере изменения прогресса :
P x |
= |
× | (1,00 — | ) | + | × | = | |||||
P y |
= |
× | (1,00 — | ) | + | × | = |
Весь линейный сегмент в конечном итоге создается бесконечным количеством точек, построенных из всех возможных значений t между 0 и 1.
До сих пор мы имели дело с плоскими 2D-сегментами, но ничто не мешает нам выполнять те же операции в трехмерном пространстве. В демонстрации ниже вы можете управлять положением точек A и B в трехмерном пространстве, перетаскивая их. Вы также можете изменить угол обзора, перетащив в другое место. Ползунок управляет виртуальным пером, которое рисует линейный сегмент :
Перетаскивая эту точку в пространстве, мы создали простой линейный сегмент, размещенный в трех измерениях. Однако теперь мы можем пойти еще дальше, перетащив этот сегмент в пространстве, чтобы создать красную поверхность :
Поверхность, которую мы только что создали, довольно условна и не очень хорошо определена, но концепция очень многообещающая, поэтому давайте попробуем создать простую поверхность, которой мы можем легко управлять. Идея проста — мы можем разместить в пространстве два линейных сегмента, которые будут образовывать рельсы по которым другой перетащите сегмент слайды:
]
Обратите внимание, что во многих конфигурациях перетаскиваемый сегмент будет изменять свою длину по мере продвижения, но это действительно полезно — это позволяет нам создавать более разнообразные формы.
Важно отметить, что мы перемещаемся вдоль боковых сегментов пропорционально — когда мы на полпути перетаскивания, мы также на полпути по длине рельсов даже если рельсы сами по себе имеют разную длину. Вы можете думать о синих направляющих как о своего рода взаимосвязанных ползунках.
Давайте не будем забывать, что перетаскиваемый сегмент сам создается путем перетаскивания точки между конечными точками сегмента, поэтому вся поверхность состоит из точек, составленных из всех возможных значений двух разных прогрессий, одного вдоль рельсов и еще один вдоль перетаскиваемого участка :
Ползунки и, следовательно, прогрессии независимы друг от друга, поэтому мы можем представить себе выбор, который они предлагают, как квадрат 1 × 1:
Один ползунок управляет прогрессом t перетаскиваемого сегмента, а другой ползунок управляет прогрессом s внутри этого сегмента.
Такое расположение ползунков немного громоздко, поэтому я оставлю их оба в горизонтальном положении. Иногда я помещаю уменьшенную версию этого квадрата в нижний левый угол демонстрации — это упростит анализ того, как ползунки управляют размещением точки на поверхность:
Также может быть полезно увидеть, как различное расположение контрольных точек деформирует этот входной квадрат в трехмерном пространстве. По мере деформации поверхности линии остаются прямыми, но расстояние между ними может изменяться:
Попробуем математически выразить то, что мы построили. Позвольте мне показать начальную демонстрацию с разными отметками, помеченными для упрощения ссылки:
Конечные точки P 0 и P 1 перетаскиваемого сегмента — это просто интерполированные конечные точки рельсов AC и BD :
Тогда положение точки в сегменте — это просто еще одна линейная интерполяция:
После вставки значений мы действительно можем подтвердить, что окончательное положение точки P на этой поверхности зависит от двух параметров s и t :
P |
= |
( | A | × | + | C | × | ) | × | + | |||
( | B | × | + | D | × | ) | × |
Что мы можем расширить, чтобы увидеть функции, управляющие каждой точкой:
п | знак равно | A | × | × | + | ||
B | × | × | + | ||||
C | × | × | + | ||||
D | × | × |
Термины рядом с контрольными точками могут показаться немного сложными, но на самом деле они довольно просты, если их представить визуально. Они представляют собой двумерные эквиваленты одномерных весовых функций, которые мы уже видели. Я снова использую красноту и высоту, чтобы визуализировать, насколько важна эта контрольная точка на разных участках поверхности:
Обратите внимание, что весовые функции симметричны друг другу. В результате мы можем создать ту же самую поверхность, выбрав перетаскивание либо желтого либо синего ] сегмент в пространстве — они оба следуют одной и той же поверхности:
В то время как мы фактически просто тащим по воздуху прямую палку, поверхность, которую она создает, известная как гиперболический параболоид при некоторых углах обзора совсем не выглядит прямой — есть некоторые изгибы формы, которые можно найти на этой поверхности.
В общем случае любая точка, скользящая по поверхности, следует некоторой кривой в пространстве. Однако особенно интересная кривая появляется, когда точка перемещается по диагонали области входного квадрата — мы можем легко показать траекторию этой точки заблокировав два ползунка вместе:
Обратите внимание, что хотя точка всегда следует по одной и той же прямой линейной траектории на входном квадрате, для многих конфигураций путь прослеживается на поверхности образует непрямую кривую отличную от . Давайте попробуем написать уравнение этой кривой . Напомним, любую точку на поверхности, которую мы рассматривали, можно описать с помощью следующих уравнений:
п | знак равно | A | × | × | + | ||
B | × | × | + | ||||
C | × | × | + | ||||
D | × | × |
Однако, поскольку ползунки заблокированы вместе, s и t одинаковы, поэтому получаем:
п | знак равно | A | × | × | + | ||
B | × | × | + | ||||
C | × | × | + | ||||
D | × | × |
Обратите внимание, что точки B и C имеют одинаковый вес, поэтому мы можем связать их вместе:
п | знак равно | A | × | × | + | ||
(B + C) | × | × | + | ||||
D | × | × |
Мы можем еще больше упростить ситуацию, если сделаем точки B и C перекрывающими друг друга. Обратите внимание, что это делает всю поверхность плоской, а кривая хорошо изолирована:
Поскольку точки B и C одинаковы, это дает окончательную упрощенную форму:
п | знак равно | 1 | × | A | × | × | + | ||
2 | × | B | × | × | + | ||||
1 | × | D | × | × |
Обратите внимание, что мы имеем дело только с тремя точками, поэтому с этого момента я буду называть третью точку C вместо D . Построенная нами кривая известна как квадратичная кривая Безье . Здесь он нарисован только в двух измерениях:
Мне очень приятно, что мы обнаружили эту плавную кривую, скрывающуюся на поверхности, полученную путем перетаскивания прямого сегмента в пространстве.
Давайте посмотрим, насколько каждая точка влияет на форму кривой, когда мы продвигаемся через нее. Вы можете нажать щелкнуть контрольную точку, чтобы выбрать ее:
Здесь можно сделать несколько интересных наблюдений:
- Когда прогресс равен 0, весовая функция контрольной точки A достигает 1, а все остальные исчезают, поэтому кривая гарантированный запуск в точке A
- Аналогично, когда прогресс равен 1, весовая функция контрольной точки C достигает 1, а все остальные исчезают, поэтому кривая гарантирована до конец в точке C
- Когда прогресс находится между этими двумя экстремумами, на положение точек на кривой влияют все 3 контрольные точки
Также стоит отметить, что «скорость» движения по кривой больше не является постоянной, как это было для линейного сегмента. Мы можем визуализировать, как равномерные шаги прогресса отображаются в точках кривой:
Для некоторых конфигураций, когда ползунок находится на полпути, кривая определенно не наполовину начерчена. В некоторых приложениях, например когда ожидается, что объект будет двигаться по кривой с постоянной скоростью, это несоответствие между «входным» прогрессом и фактическим увеличением расстояния по кривой может быть очень важным, но для наших визуальных сценариев использования это не имеет значения.
Квадратичные кривые Безье, к сожалению, довольно ограничены. Например, невозможно описать какую-либо петлю такой простой кривой. Чтобы лучше контролировать форму кривой, нам нужно каким-то образом ввести дополнительные контрольные точки, которые дадут нам более точную настройку формы кривой.
Глядя на уравнения и весовые функции, может быть немного сложно понять, как мы могли бы обобщить кривую, чтобы ввести больше точек. Давайте еще раз посмотрим на это уравнение:
п | знак равно | 1 | × | A | × | × | + | ||
2 | × | B | × | × | + | ||||
1 | × | C | × | × |
Мы можем разделить удвоенную часть B и немного переставить вещи, чтобы получить:
п | знак равно | ( | A | × | + | B | × | ]) | × | + | |||
( | B | × | + | C | × | ]) | × |
Может показаться, что мы только усугубили ситуацию, но на самом деле мы сильно упростили ситуацию. Обратите внимание, что значения в скобках представляют собой просто линейную интерполяцию между точками A и B а также B и C . Мы можем назвать эти новые точки AB и BC :
AB | знак равно | A | × | + | B | × | ||
до н.э | знак равно | B | × | + | C | × |
И последняя точка P — это просто линейная интерполяция двух:
Давайте посмотрим на то, что мы получили, используя некоторые визуальные эффекты:
По сути, мы интерполируем точки на имеющихся краях, чтобы создать новый край, на котором мы затем интерполируем снова, чтобы получить точку на кривой . Теперь, если мы введем еще одну контрольную точку, мы можем просто повторить эту идею, выполняя повторяющиеся интерполяции. Обратите внимание, что на каждом этапе у нас на одну точку меньше и на одно ребро меньше:
Эта кривая известна как кубическая кривая Безье. Если мы просчитаем все сделанные интерполяции, мы получим следующую компактную форму:
п | знак равно | 1 | × | A | × | × | × | ] | + | ||
3 | × | B | × | × | × | ] | + | ||||
3 | × | C | × | × | × | ] | + | ||||
1 | × | D | × | × | × | ] |
Обратите внимание, как секции (1 — t) преобразуются в секции t для последующих точек. Все эти умножения треугольных функций приводят к созданию следующего набора весов для контрольных точек:
Идеи, лежащие в основе двумерных кубических кривых Безье, естественным образом распространяются и на третье измерение — кривая может закручиваться и поворачиваться в пространстве:
Мы здесь не только для того, чтобы говорить о кривых, поэтому давайте попробуем посмотреть, как мы можем продолжить кривые Безье на поверхности.
Когда мы создавали простую поверхность, мы скользили по прямому сегменту по двум другим линейным сегментам. Мы можем расширить эту идею, сдвинув прямой отрезок по двум кубическим кривым Безье :
Хотя сама поверхность теперь более разнообразна на фиолетовых краях трудно скрыть прямолинейность желтого сегмента . Чтобы сделать вещи более разнообразными, мы могли бы заменить этот сегмент одной кубической кривой Безье, которую мы будем растягивать по ходу движения, но, опираясь на предыдущие идеи, мы можем сделать что-то гораздо более интересное.
Во-первых, мы представим набор четырех кубических кривых Безье в трехмерном пространстве. Затем мы можем использовать эти четыре кривые как рельсы, по которым четыре контрольные точки могут скользить. Эти контрольные точки в свою очередь, определяют новую кривую Безье которая, в свою очередь, отслеживает конечную поверхность при перемещении в космосе:
Обратите внимание, что четыре черные контрольные точки нельзя напрямую контролировать — они меняют свое положение, когда скользят по пурпурным рельсам ]которая, в свою очередь, продолжает изменять перетянутую кривую Безье . Этот процесс формирует кубическую поверхность Безье или кубический участок Безье . Эта новая поверхность определяется 16 контрольными точками, которые могут быть расположены в связанной сетке 4 × 4:
Как мы уже видели, каждая точка на этой поверхности сначала определяется путем оценки по набору четырех кривых Безье с последующей интерполяцией по полученным ] новая кривая :
Каждая точка входного квадрата соответствует некоторой точке на поверхности. Поверхность искажает прямые линии на входном квадрате в кубические кривые Безье в трехмерном пространстве:
Уравнение, управляющее этой поверхностью, настолько массивно, что я просто представлю части используя их треугольные символы:
п | знак равно | P 11 | × | × | × | × | × | × | + | ||||||
P 12 | × | × | × | × | × | × | + | ||||||||
P 13 | × | × | × | × | × | × | + | ||||||||
P 14 | × | × | × | × | × | × | + | ||||||||
P 21 | × | × | × | × | × | × | + | ||||||||
P 22 | × | × | × | × | × | × | + | ||||||||
P 23 | × | × | × | × | × | × | + | ||||||||
P 24 | × | × | × | × | × | × | + | ||||||||
P31 | × | × | × | × | × | × | + | ||||||||
P32 | × | × | × | × | × | × | + | ||||||||
P33 | × | × | × | × | × | × | + | ||||||||
P34 | × | × | × | × | × | × | + | ||||||||
P41 | × | × | × | × | × | × | + | ||||||||
P42 | × | × | × | × | × | × | + | ||||||||
P43 | × | × | × | × | × | × | + | ||||||||
P44 | × | × | × | × | × | × |
The result of all of those 16 sets of triangular multiplications are 16 weighting functions that define how important the given control point is to the shape of the surface in the point’s vicinity:
Notice the symmetry in all those functions – ultimately there are only three unique shapes here, the others are their reflections and rotations.
While relatively flexible, simple cubic curves and surfaces have their limitations. For example, a cubic Bézier curve can have at most two “bends” – it’s impossible to make it fit the gray curve:
One approach to solve this problem is to build more complicated Bézier curves by increasing the number of control points. Thankfully, our method of linearly interpolating the linear interpolations that we used to upgrade quadratic Bézier curves to cubic ones allows us to add arbitrary number of control points. In the demonstration below you can see how a Bézier curve with eight control points is built:
On every interpolation step we reduce the number of intermediate points by one and after the 7th step we’re down to our final point that defines the path. With a single Bézier curve with that many control points we can finally try to fit the shape:
Bézier curves with more control points provide better flexibility, but unfortunately they come with their own problems. Firstly, notice that to fit the shape the control points of the curve have to be put quite far apart making them slightly less convenient to use.
Moreover, changes to any of the control points causes the entire curve to move. In the demonstration below I’ve enlarged a small section in the beginning of a curve. When you move the selected 6th point, the section of the curve near the 2nd control point changes as well:
When we look at the plot of the weight that each point has as we progress through the curve we can notice that, to some extent, they all contribute almost the entire curve:
Single Bézier curves with many control points lack the property of local controlmaking them not as useful for expressing more complicated shapes, so for better or for worse we have to rethink our approach to designing complicated shapes.
Instead of using a single curve with many control points, we can use multiple curves with few control points. In the demonstration below I put three cubic Bézier curves that we can arrange to create more complex shapes:
While flexible, this setup is a bit cumbersome to use, as these three curves have gaps unless we connect their end points. We can automate this connection process by having the last control point of the previous curve always match the first point of the next curve:
This solves the problem of gaps but if we’re aiming for nice, smooth shapes then we still have some sharp corners to account for. To remove the kinks we have to ensure that the tangency of neighboring curves is continuous as well.
I’ve discussed tangency on this blog before, but, as a quick recap, a tangent is a local “straight ahead” direction. It can be easily found by sliding two beads on a curve. As the beads get closer, the line spanned through their centers approaches the tangent direction:
To ensure that the tangency of neighboring cubic Bézier curves is consistent the next and previous neighbors of the joining control point have to lie on the same straight line. Many design tools tie the control points of neighboring curves together giving a single control axis that maintains the tangency. That entire utility is usually known as a pen or vector tool:
The curves we’ve built in this section are known as splines which are piecewise polynomial curves – they’re constructed from different pieces, each one described by a polynomial function.
When it comes to designing complex surfaces, we can employ the same idea of using multiple cubic Bézier patches to form more complicated shapes. With the most basic setup those have the same continuity problems that individual curves had:
Notice how much work it is to have the two surfaces without any gaps. Thankfully, just like we did for curves, the edge points of neighboring patches can be tied together to make the seam water tight.
When it comes to tangency, the concept of a single tangent direction doesn’t make that much sense for surfaces – at a given point the surface “goes” in many directions. To account for that we define an entire tangent plane. Similarly to what we did for curves we can take three beads and three straight wires attached to them and then slide those beads close to the point on the surface:
As the beads come to the target point infinitely close the three wires define the tangent plane. That plane also has a perpendicular direction which we call a normal direction of the surface at that point – I’ve marked that direction with a red arrow:
For two surfaces to be connected without a sharp edge we need the normals at the joining edge to be aligned in the same direction:
Note that even when the surfaces don’t have any gaps it still requires a lot of adjustments to keep them connected and free of sharp edges. But that’s not even the end of the difficulties that joined patches create.
It would be reasonable to think that removal of sharp edges is all it takes to create pretty surfaces, but when the surfaces are reflective we still have some work to do. In the demonstration below you can see a mirror-polished surface reflecting a red sphere. By dragging the slider you can bend that surface into an arc of a circle, the black curve below shows a cross section of the mirror’s shape:
Notice that as the mirror pulls away from the flat shape the reflection of the sphere gets narrower. To get an intuitive understanding of why this happens we have to look at this situation from the side. The black line represents the mirror and the red line shows a reflection of the sphere in that mirror:
When the surface curves, its normal directions spread out. In simple terms, a curved mirror “sees” more of its environment but since the mirror’s area stays the same, the reflection of the sphere has to shrink to fit in.
When we bend the surface we’re actually changing the curvature of the profile of the surface. Roughly speaking, curvature defines how quickly a curve changes its direction.
In the demonstration below I’ve marked curvy sections of the curve with different colors – the higher the curvature the more intense the color. Notice that this curve can bend in two different directions, so I’m using orange and blue to distinguish these cases. By dragging the slider you can witness how quickly the local direction of the curve, shown as a black arrowchanges in the areas of large curvature:
A straight line has no curvature and the arrow never turns, while a tight turn has a high curvature and the arrow turns very rapidly when passing through it. To show a more precise measure of a curvature we can use an osculating circle which conceptually is a circle that locally “fits” into the curve:
Curvature κ is the inverse of the radius R of that circle:
κ = 1
/
R
That radius R is also known as the radius of curvature. Note that the inverse relation makes sense – a small curvature implies a not very curvy region which in turn implies large radius of curvature. A curvature of a curve is also often visualized using a curvature comb:
Comb’s teeth are protruding in the normal direction of the curve and the higher a tooth’s length, the larger the curvature in that region.
Let’s look at two reflective surfaces next to each other. I made their backs have different colors so that it’s easier to see where they join. The top slider controls the position of the sphere and the bottom one controls how closely the curvatures of the two surfaces match:
Notice that the connection doesn’t have any sharp edges. However, for different values of the curvature match the reflection of the sphere at the connection can look weirdly deformed into an UFO‑like shape, or it can be quite smooth. If we analyze the situation from a side profile with a curvature comb visible we can get a clearer picture of what’s going on:
This view explains why in some cases the transition between the arched and the flat part of the surface has a visible jump in the sphere’s reflection – there is a sudden transition between the area where the curvature is zero and the reflected image is not squeezed at all, and the area of non-zero curvature where the image is squeezed a lot. When we fix that sudden jump, the reflected image becomes gradually squeezed as it enters the curved part.
When curvatures between connected surfaces don’t match, the reflections may look very weird. Curvature continuity is critical for achieving high quality reflective surfaces which are very commonly used e.g. on cars.
Even when surfaces are very far from being mirror-polished a rapid change in curvature may be observable:
Notice that in some orientations you can see a fairly sharp falloff in the shading on the surface. That transition gets much smoother if we make the curvatures vary more smoothly.
Even though we were looking at light reflected off surfaces, we’ve actually been talking about the curvature of the curve defining the profile of those surfaces. The notion of the curvature of a surface itself is a bit more complex, but in many cases it involves analyzing curvatures of curves living on that surface. Let’s try to look for some of those curves.
For every point on a surface we can take a normal direction and use it as pivot on which a new plane can rotate:
Notice that this plane slices the surface creating a curve at the cross section. We can then find the curvature of that curve at that point and show the osculating circle, or at least a part of it:
These curvatures are known as normal curvatures. Notice that at a given point a surface will have many curvatures, one for every direction of slicing. However, at every point there is a direction for which that curvature takes the maximum value, and a direction for which the curvature takes the minimum value. Note that the minimum value can be the most negative. Those two maxima are known as principal curvatures. I’ve shown them in the demonstration below:
For some cylindrical shapes, one of the curvatures will always be 0, but for saddle-like surfaces one will be positive and one negative. Moreover, the directions of two principal curvatures are always perpendicular to each other, but the reason for that is a bit complicated. The product of the two principal curvatures is known as Gaussian curvature.
While the curvature discontinuity was easy to see on reflective surfaces, it’s quite hard to notice on regular 2D curves. However, in some cases we can actually feel it.
Trains and subway carts ride on railroads that can’t go on straight forever – at some point they have to change direction. One could naively think that a turn of a train’s track consists of an arc of a circle joining two straight segments. In the demonstration below I put a little train riding on that track. You can control the time using the slider:
The red arrow shows a centripetal force that makes the train and thus its passengers change direction. That force F depends on the mass m of the object, a square of its velocity Vand the radius of curvature R of the traveled path:
F = m × V2
/
R
On the straight part the radius of curvature is infinite so the force is 0, but when the train enters the circular part of our simple track the centripetal force suddenly appears leading to a very unpleasant jerk. That jerk is caused by the track’s curvature suddenly changing from a zero to a non-zero value. Real train tracks use transition curves that smoothly vary the curvature between the straight and circular parts to avoid a sudden increase in forces which makes the ride much smoother.
Let’s sum up the different classes of continuities that we’ve discussed so far:
G0 position continuity
G1 tangency continuity
G2 curvature continuity
We can visualize them on two Bézier curves with curvature combs visible. Notice that as you enable higher degrees of continuity the movement of some of the control points becomes restricted:
While different tricks and affordances can be employed to maintain these continuities, we should look for a better way to design complex curves and surfaces – a method that has all these continuities built-in.
Let’s take a step back and look at the weights of the single Bézier curve with eight control points that we’ve already dismissed:
The problem with that approach was that the weight of each control point had a very far reaching range – it spans the entire progress of the curve. Let’s try to minimize how far reaching the impact of each point is by replacing it with simple triangular functions:
The weight of a control point is now non-zero only in a small range of the entire progress of the curve – each point has a minimized reach. Unfortunately, the resulting curve is not very impressive. While it doesn’t have any gaps, it forms just a plain polyline. The beauty of this approach, however, is that we can try using better base functions with limited range.
One could be tempted to use an arbitrary shape for the individual weighting functions, for example, some smoother parabolas:
The results are completely janky. We’ve not only created some weird arcs, but also their shape and “sidedness” depends on where on screen the control points are.
To understand why this happens we have to look at the sum of all the weighting functions over the entire range, I’ve visualized it using a thick yellow line on top of the plot:
Notice the resulting curve behaves weirdly in the regions where the weighting functions add up to more than 1.0, I’ve depicted that 1.0 margin with a dashed line. In those faulty ranges we no longer take a proper weighted average of some points, causing the broken behavior.
In fact, all the curves we’ve discussed so far had the property that all their weighting functions added up to 1 within the defined range forming a so called partition of unity. That concept is critical for the design of the weighting functions, as it makes the resulting curve behave as expected when all the control points are moved around as a group.
We could try to normalize the janky parabolas to make them nice, but in practice we’ll use so called B‑spline functions that are very well behaved. The most primitive B‑spline function of order 1 has a value of 1.0 in a small range and 0.0 everywhere else as seen below. Higher orders of B‑spline functions are generated from two lower order functions, with a simple procedure that you can witness by dragging the slider:
In fact, our triangular function is a B‑spline function of order 2. The next order forms a quadratic B‑spline. Here’s a curve built using quadratic B‑splines as base functions:
The curve no longer has any unpredictable behavior and doesn’t have any sharp corners. In fact it has both position (G0), and tangency (G1) continuity. The fourth order B‑spline, known as a cubic B‑spline, is even better – its curvature is also continuous making the entire curve G2 continuous:
The weighting functions of B‑splines have limited range so every control point only modifies the curve locally which solves the problems we’ve seen with complicated Bézier curves. However, you’ve probably noticed that for both quadratic and cubic B‑splines the curve no longer reaches the first and last control points. In some cases it’s not a problem, e.g. when the control points form a closed loop:
To solve the problem for open curves we can’t just allow the progress to go out of range since the weighting functions don’t add up to 1.0 there and we’ve already seen the problems that would cause. Instead, we have to modify the functions inside that valid range.
Firstly, notice that the functions we’ve built were designed over a repeating, uniform interval that I’ve been marking using small triangles at the bottom. Those triangles represent so called knots on which the individual weighting functions are built.
However, nothing prevents us from adjusting those intervals as we please, making their distribution non-uniform. In the demonstration below you can witness how the B‑spline rules create different functions when the distance between knots changes:
We no longer can simply shift the base function to the right and instead we have to build the individual functions all the way up from order 1 because each function may be defined over different interval of knots. With those rules in place, you can witness what happens to the end points of the curve as we adjust the position of the knots:
When the first four and last four knots overlap, a cubic B‑spline curve extends all the way to the boundary control points as desired. Additionally, the position of all the other knots can be adjusted too. In the demonstration below you can see what happens to the curve as we perturb the position of the interior knots as well:
By moving three knots of a cubic B‑spline curve into a single location, we can achieve a sharp corner reigned by just a single control point. With uniformly distributed knots we’d need to overlap three consecutive control points to get a sharp corner.
The curves we’ve been playing with are known as a non-uniform B‑splines. However, the world of B‑splines has one more trick up its sleeve.
So far each of the weighting functions was equally important, but we can easily break that assumption by making some functions more important than others by changing their relative weights. For example, witness what happens to the curve as we modify the weight of the 4th control point that I’ve marked with a letter D:
As we change the weight of that function the shapes of all the others are adjusted as well. This ensures that all the weighting functions add up to 1 over the entire valid range which maintains the partition of unity. By combining the non-uniform knot distribution with arbitrary weights we create non-uniform rational B‑splines, also known as NURBS. Notice that the weight of an individual function actually affects the ratio of weights, thus the rational name.
Interestingly enough, only rational B‑splines can represent an arc of a circle exactly. Plain B‑spline and Bézier curves can only approximate them.
The ideas behind the B‑spline curves naturally extend to surfaces as well. For example, here’s the simplest uniform and non-rational B‑spline surface defined by a mesh of 4×4 control points:
We can also build more complicated shapes by creating bigger rectangular grids of control points. When we tweak the position of the knots, we can make the surface go all the way to the edges of the control mesh:
While I’m not going to demonstrate it here, one can also adjust the weights of individual weighting functions which fully exploits all the features that NURBS surfaces offer.
NURBS curves and surfaces are very powerful and they have a lot of flexibility, but they still have their limitations when it comes to modeling shapes. Firstly, it’s quite difficult to cut holes in NURBS surfaces. One approach is to simply ignore some parts of the input square. In the demonstration below you can control the radius of the red circle that defines the region where we’re not drawing the surface:
This operation is a bit cumbersome because achieving the desired shape of the hole on the surface requires tweaking the hole’s shape on the input square so that it looks right after it’s deformed by the surface.
You may have also noticed that all the meshes of control points we’ve been dealing with were rectangular in nature. This heavily limits the types of surfaces we can create. For example, it’s impossible to express a surface like the one below using a single B‑spline surface without having to somehow manually stitch it at the edges:
However, the entire point of using B‑spline curves and surfaces was to ensure automatic continuity and smoothness in the first place. Let’s go back to the drawing board for the last time to devise a scheme that will help to solve all these problems.
Instead of an actual drawing board let’s start with a clean sheet of paper instead. Those usually come in rectangular shapes, but if we wanted a piece of paper to have a nice rounded corner we’d have to do some work. One of the easiest way to do it is to repeatedly cut off the sharp corners:
After just three iterations the corner is quite round already. We can use this corner cutting idea to chip away at the control polygon of a curve as well. In the demonstration below you can control how many times we did the corner cutting procedure and the relative distance at which the cut happens:
If we cut the corners too little or too much the final curve will still look quite jaggy, but for intermediate values the resulting polyline starts to look very smooth – even the sharpest corners are chiseled down after a few iterations. This concept of corner cutting was originally devised by George Chaikin and the resulting curves are known as subdivision curves.
A particularly interesting curve is generated when the cut happens 1/4 of the way into the straight segments. As it was later discovered, that scheme actually converges to a curve that is equivalent to quadratic B‑spline curve defined by the same initial control points:
After just a few steps, the subdivision curve matches the B‑spline curve in the background. This is a fantastic news, as it gives us yet another way to think about simple quadratic B‑spline curves – they’re obtained by repeatedly cutting off corners of their control polygon.
The cutting scheme we’ve used so far placed two new points on an existing edge, but that’s not the only choice we have. For instance, we can place a new point on an existing edge and place another new point as a refinement of the existing vertex:
In this scheme the new edge point Enew lands right in the center of the edge, and the new vertex point Vnew is a weighted average of the original vertex V and its previous and next neighbors Vprev and Vnext. We can express that dependence using equations:
Vnew = × Vprev + × V + × Vnext
Enew = 0.5 × Vprev + 0.5 × Vnext
In some sense we’re still cutting corners, but our method is a bit more sophisticated now. Once again, there exists a set of weights that’s particularly important. The curve it generates in limit is a cubic B‑spline curve:
However, the beauty of subdivision curves doesn’t end here. Firstly, we can modify the subdivision rules for the “tail” control points so that their new vertex points don’t move at all – this makes the curve stick to its end points. Moreover, observe that we’re making the new vertex position be a weighted average of the positions of the old vertex and its two neighbors, but we can easily extend that scheme to include all its neighbors. This allows us to handle branches and loops very easily:
In the vicinity of those branchy points the curve is no longer a B‑spline curve, as those can’t fork, but in the “regular” areas we still use the same subdivision rules that gave us the well-behaved cubic B‑splines functions. The subdivision curves extend the power of B‑splines curves into more arbitrary connectivity. The real magic starts when we employ subdivision rules for surfaces.
Inspired by Chaikin’s original work for corner cutting in a quadratic B‑spline, Ed Catmull and James Clark have extended these ideas to surfaces by creating Catmull–Clark subdivision surfaces. First, let’s see them in action starting with a simple mesh of 4×4 vertices and 9 quadrilateral faces:
After a few iterations the surface looks awfully familiar to the simplest cubic B‑spline patch we’ve seen before. In fact, after infinitely many subdivisions that surface does turn into a cubic B‑spline surface.
The brilliance of Catmull and Clark was to come up with a subdivision scheme of a simple grid that in limit approaches the well defined B‑spline surfaces. Let’s try to understand the gist of their method.
Starting with a single cubic B‑spline patch defined by a 4×4 control grid, they wanted to find an equivalent 5×5 control grid that would define the same surface. In other words, given the 16 control points defining the gray surfacewe’re looking for positions of the 25 new control points that would define a surface with the same shape:
The arithmetical procedures needed to be done aren’t particularly important here, but ultimately the new control points can be classified into three distinct groups that end up corresponding to original facesedgesand vertices. Their exact creation rules are as follows:
- new face points – average of the vertices forming the face
- new edge points – average of the two new face points from neighboring faces and two existing end points
- new vertex points – weighted average of: the existing vertexmidpoints of all the edges touching that vertex, and all the newly created face points from faces touching that existing vertex
On those new points new faces are created and we once again have a set of vertices and faces, but this time they’re more refined. If we repeat that scheme over and over again the created faces of the control mesh will actually create a cubic B‑spline surface that the original control points defined:
The crucial invention here is that nothing about the rules for creating the new subdivided control mesh relied on how the mesh itself is connected! If you recall, NURBS surfaces were limited to rectangular grids. Catmull-Clark subdivision scheme also works when the vertices of the initial mesh have different number of neighbors – in the mesh below each vertex has 3, 4, or 5 neighbors:
Moreover, the initial faces also don’t necessarily have to be four-sided, the subdivision rules also work for meshes whose faces are triangles or more complicated polygons:
To express holes in a subdivision surface all we need to do is to not create faces in some regions of its defining mesh, like in this mask from the very first demonstration in this article:
This example also has modified the subdivision rules on the edges to use cubic Chaikin subdivision rules. This ensures that the surface doesn’t recede from the edges of the initial control mesh like we’ve seen happen for a simple subdivided patch.
Subdivision surfaces make it very convenient to describe perfectly smooth surfaces using relatively simple meshes. To achieve sharper edges one can either move the points closer together, or use extensions of Catmull-Clark subdivision scheme that allow specifying edge sharpness.
A Primer on Bézier Curves by Mike Kamermans is probably the best resource on the web dedicated to Bézier curves. Over the course of the extremely detailed article the author goes over a myriad of topics related to the curves, some very theoretical and some very pragmatic. The site truly is one of the internet’s gems.
For a more advanced set of videos on topics related to curves and surfaces I recommend Keenan Crane’s series on Discrete Differential Geometry. Keenan is a professor at Carnegie Mellon University and over the course of his lectures he does an excellent job of explaining the concepts with well made visual cues, while avoiding bogging the viewer down with too much formalism.
Finally, Curves and Surfaces for CAGD by Gerald Farin is a very good textbook on all the topics I’ve discussed here any many more. The author does a much deeper dive into the underlying mathematics and the formulas are accompanied by well written explanations.
Ed Catmull later co-founded Pixar, and ever since the short film Geri’s Game, subdivision surfaces have been used in all of Pixar’s movies. Catmull and Clark’s invention made it easier for great storytellers to express themselves through computer graphics.
I find it fascinating that simple rules of repeated linear interpolations of straight segments end up creating delightful Bézier curves. Every letter you read on this website was crafted using Bézier curves, the primary building blocks of fonts.
Designers who shape exteriors of everyday objects or tweak the shapes used in 2D graphics focus on the visual form of their creations, but their creative freedom is unlocked by carefully composed mathematical formulas that power various design tools. I think it’s charming that simple and unassuming equations end up creating beautiful curves and surfaces.