normalized unity что это
Vector3
struct in UnityEngine
Success!
Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable.
Submission failed
For some reason your suggested change could not be submitted. Please try again in a few minutes. And thank you for taking the time to help us improve the quality of Unity Documentation.
Description
Representation of 3D vectors and points.
This structure is used throughout Unity to pass 3D positions and directions around. It also contains functions for doing common vector operations.
Besides the functions listed below, other classes can be used to manipulate vectors and points as well. For example the Quaternion and the Matrix4x4 classes are useful for rotating or transforming vectors and points.
Static Properties
Properties
magnitude | Returns the length of this vector (Read Only). |
normalized | Returns this vector with a magnitude of 1 (Read Only). |
sqrMagnitude | Returns the squared length of this vector (Read Only). |
this[int] | Access the x, y, z components using [0], [1], [2] respectively. |
x | X component of the vector. |
y | Y component of the vector. |
z | Z component of the vector. |
Constructors
Public Methods
Equals | Returns true if the given vector is exactly equal to this vector. |
Set | Set x, y and z components of an existing Vector3. |
ToString | Returns a formatted string for this vector. |
Static Methods
Angle | Returns the angle in degrees between from and to. |
ClampMagnitude | Returns a copy of vector with its magnitude clamped to maxLength. |
Cross | Cross Product of two vectors. |
Distance | Returns the distance between a and b. |
Dot | Dot Product of two vectors. |
Lerp | Linearly interpolates between two points. |
LerpUnclamped | Linearly interpolates between two vectors. |
Max | Returns a vector that is made from the largest components of two vectors. |
Min | Returns a vector that is made from the smallest components of two vectors. |
MoveTowards | Calculate a position between the points specified by current and target, moving no farther than the distance specified by maxDistanceDelta. |
Normalize | Makes this vector have a magnitude of 1. |
OrthoNormalize | Makes vectors normalized and orthogonal to each other. |
Project | Projects a vector onto another vector. |
ProjectOnPlane | Projects a vector onto a plane defined by a normal orthogonal to the plane. |
Reflect | Reflects a vector off the plane defined by a normal. |
RotateTowards | Rotates a vector current towards target. |
Scale | Multiplies two vectors component-wise. |
SignedAngle | Returns the signed angle in degrees between from and to. |
Slerp | Spherically interpolates between two vectors. |
SlerpUnclamped | Spherically interpolates between two vectors. |
SmoothDamp | Gradually changes a vector towards a desired goal over time. |
Operators
Is something described here not working as you expect it to? It might be a Known Issue. Please check with the Issue Tracker at issuetracker.unity3d.com.
Copyright ©2021 Unity Technologies. Publication Date: 2021-12-10.
Unity Vectors – What Every Game Developer Needs To Know About Vectors In Unity
Help Others Learn Game Development
As soon as you start with Unity game development you will be bombarded with vectors. Most of the time you will use Vector2 for 2D games and Vector3 for 3D games.
So what are vectors and how can we use them?
Definition Of A Vector
Vectors are a fundamental mathematical concept which allows you to describe a direction and magnitude.
In math, a vector is pictured as a directed line segment, whose length is the magnitude of the vector and the arrow is indicating the direction where the vector is going:
Vector2 And Vector3 In Unity
The values for the axis are located in the Position property of the Transform component of course:
The Magnitude Of The Vector
One of the things we use vectors for is to know the magnitude of the game object.
But what is magnitude of a vector?
The magnitude is the distance between the vectors origin (0, 0, 0) and its end point. If we imagine a vector as a straight line, the magnitude is equal to its length as we saw in the first image:
To calculate the magnitude in math, given a position vector → v = ⟨a,b⟩, the magnitude is found by magnitude = √a2 + b2 (square root of a squared + b squared).
In 3D it works the same way, except that we have one more axis in the calculation magnitude = √a2 + b2 + z2 (square root of a squared + b squared + z squared).
But in Unity we simply do this:
For What Do We Use The Magnitude Of A Vector
The magnitude of a vector is used to measure the speed of the vector. I say speed of the vector but its actually the speed of the game object.
For example, if we are moving the game object using its Transform component, we can limit the movement speed using the magnitude of the vector:
Squared Magnitude
One thing that we need to be careful here is that this will calculate the magnitude without using the square root in the operation, which means it will return the magnitude squared e.g. the magnitude will 2x of its actual value.
So if we want to compare a distance between two vectors or the speed of the current vector we need to compare the squared values:
The Direction Of The Vector
One of the most common informations we need in a game is the direction of vectors which indicates in which direction a specific game object is going. This is used to move characters in the game, to create enemy AI and so on.
To get the direction of the vector we need to normalize it, and in Unity we normalize a vector like this:
Using normalized or Normalize will give us the direction of the given vector.
Now you are probably asking what is the difference between the two?
Both lines of code will normalize(return the direction) the given vector, but using normalized on a vector will return a new version of the same vector that we can store in a new variable, and the original version of the vector will stay the same.
Using the Normalize function however, will normalize the vector is self e.g. it will change the original vector.
If you hover over the normalized word in the code you will see the following explanation:
This means that the returned vector has a magnitude of 1 e.g. the length of that vector is 1, because this is how directions are represented in Unity.
As you can see, X is positive on right side and negative on the left, Y is positive up and negative down, and Z is positive forward and negative backwards.
This is why you see the positive 1 and negative 1 values for the vectors in the code example above.
What Is The Difference Between Vector Magnitude And Vector Normalize
From all the examples we saw so far for the vector’s magnitude and normalization, we can conclude that there is a difference between them and they are both used for different purposes.
The magnitude returns a float, its a single dimensional value expressing vector length. It looses directional information but provides the length information which we can use to control the speed of the vector.
Normalization is a bit of an opposite operation – it returns vector direction, guaranteeing that the magnitude of the resulting vector is one. This means it looses the magnitude information but preserves the direction information which we can use to know where the game object is moving or to move a game object in a specific direction.
Математика в Gamedev по-простому. Векторы и интегралы
Всем привет! Сегодня хотелось бы поговорить о математике. Математика очень интересная наука и она может сильно пригодиться при разработке игр, да и в целом при работе с компьютерной графикой. Многие (особенно новички) просто не знают о том, как она применяется при разработке. Существует множество задач, не требующих глубокого понимания таких понятий как: интегралы, комплексные числа, группы, кольца и др, но благодаря математике вы можете решать многие интересные задачи. В этой статье мы рассмотрим векторы и интегралы. Если интересно, добро пожаловать под кат. Иллюстрирующий Unity проект, как всегда, прилагается.
Векторная математика.
Векторы и векторная математика являются необходимыми инструментами для разработки игр. Многие операции и действия завязаны на ней целиком. Забавно, что для реализации класса, который отображает стрелочку вектора в Unity, уже потребовалось большинство типовых операций. Если вы хорошо разбираетесь в векторной математике данный блок вам будет неинтересен.
Векторная арифметика и полезные функции
Аналитические формулы и прочие детали легко нагуглить, так что не будем тратить на это время. Сами операции будут проиллюстрированы гиф-анимациями ниже.
Важно понимать, что любая точка в сущности является вектором с началом в нулевой точке.
Гифки делались с помощью Unity, так что нужно было бы реализовывать класс, отвечающий за отрисовку стрелочек. Стрелка вектора состоит из трех основных компонент – линии, наконечника и текста с именем вектора. Для отрисовки линии и наконечника я воспользовался LineRenderer. Посмотрим на класс самого вектора:
Так как мы хотим, чтобы вектор был определённой длинны и точно соответствовал точкам, которые мы задаём, то длинна линии рассчитывается по формуле:
В данной формуле (_VectorEnd — _VectorStart).normalized – это направление вектора. Это можно понять из анимации с разницей векторов, приняв что _VectorEnd и _VectorStart – это вектора с началом в (0,0,0).
Дальше разберём две оставшиеся базовые операции:
Нахождение нормали (перпендикуляра) и середины вектора – это очень часто встречающиеся задачи при разработке игр. Разберём их на примере размещения подписи над вектором.
Для того, чтобы разместить текст перпендикулярно вектору нам понадобится нормаль. В 2D графике нормаль находится достаточно просто.
Вот мы и получили нормаль к отрезку.
Дальше остаётся поместить его в середину вектора и поднять по нормали на расстояние, которое будет смотреться красиво.
В коде использованы локальные позиции, чтобы можно была возможность двигать получившуюся стрелочку.
Но это было про 2D, а что же с 3D?
В 3D плюс-минус всё тоже самое. Отличается только формула нормали, так как нормаль уже берётся не к отрезку, а к плоскости.
В данном примере контролла нормаль к плоскости используется, чтобы сместить конечную точку траектории право, чтобы планету не загораживал интерфейс. Нормаль в 3д графике – это нормализованное векторное произведение двух векторов. Что удобно, в Юнити есть обе эти операции и мы получаем красивую компактную запись:
Думаю, многим, кто думает, что математика не нужна и зачем вообще это знать, стало чуть понятнее какие задачи с помощью неё можно решать просто и элегантно. Но это был простой вариант, который должен знать каждый разработчик игр не стажёр. Поднимем планку — поговорим об интегралах.
Вообще у интегралов очень много применений, таких как: физические симуляции, VFX, аналитика и многое другое. Я не готов сейчас детально описывать все. Хочется описать простой и визуально понятный. Поговорим про физику.
Допустим есть задача – двигать объект в определённую точку. К примеру, чтобы при вхождении в определённый триггер, должны вылетать книги с полок. Если вы хотите двигать равномерно и без физики, то задача тривиальна и не требует интегралов, но когда книги выталкивает с полки призрак, такое распределение скорости будет смотреться совсем не так.
Что такое интеграл?
По сути это площадь под кривой. Но что это означает в контексте физики? Допустим у вас есть распределение скорости по времени. В данном случае площадь под кривой – это путь который пройдёт объект, а это как раз то, что нам и нужно.
Если перейти от теории к практике, то в Unity есть замечательный инструмент под названием AnimationCurve. С помощью него можно задать распределение скорости с течением времени. Создадим вот такой класс.
Метод GetApproxSquareAnimCurve – это и есть наше интегрирование. Мы делаем его простейшим численным методом, просто идём по значениям фукнции и суммируем их определённое число раз. Я выставил 1000 для верности, в целом можно подобрать оптимальнее.
Благодаря этой площади мы дальше уже знаем, какое относительное расстояние. А дальше сравнив два пройденных пути мы получаем коэффициент скорости speedK, который отвечает за то, чтобы мы прошли заданное расстояние.
Идеальные карты нормалей для Unity (и других программ)
… и как разобраться, что не так с вашим конвейером создания контента.
Допустим, у вас есть потрясающая высокополигональная модель, над которой поработал художник, и вы хотите поместить её в игру. Художник показывал скриншоты модели в своей программе и они выглядят потрясающе. Даже после запекания карт нормалей для низкополигональной версии она всё равно выглядит отлично.
Узрите величие этой традиционной модели для тестирования карт нормалей!
Затем художник помещает её в игру, и она выглядит… немного не так.
Разве эти грани не должны быть плоскими?
Поверхности, которые должны быть плоскими, всё равно немного скруглены, некоторые даже вдавлены, и хуже того, по ним идут странные полосы. Знакомая ситуация? Некоторые из вас вероятно с ней сталкивались, думали: «ну ладно, наверно, лучше уже не получится», и двигались дальше. Возможно, накладывали немного грязи, чтобы замаскировать самые тяжёлые случаи.
Но на самом деле что-то не так в вашем конвейере создания контента. Это не всегда очевидно, но проблему можно решить!*
*Если считать, что в некоторых случаях вы можете вносить изменения в конвейер контента.
Предисловие
Эта статья посвящена тому, как экспортировать идеальные карты нормалей из других приложений для использования в Unity 5.3 и выше. В ней не говорится о том, как получить качественные карты нормалей, для этого есть множество других туториалов, поэтому я не буду касаться данной темы.
Базис касательного пространства
Проблема заключается в том, как работают карты нормалей касательного пространства. В частности, как работает касательное пространство (или касательный базис). Я не буду слишком вдаваться в технические аспекты того, что они делают. Я, как и другие авторы, уже рассказывал об этом, но если вкратце, то касательное пространство определяет, что должна представлять ориентация направления в карте нормали. Какое направление на карте нормалей на самом деле является «вперёд», «вверх» или «вправо». Грубо говоря, эта ориентация является ориентацией UV-координат текстуры и нормалей вершин.
Более важно понимать то, что программа, используемая для запекания карты нормалей из highpoly-меша в lowpoly-меш, должна вычислять своё касательное пространство точно так же, как и программа, в которой будут использоваться карты нормалей. Так как разные программы могут использовать касательное пространство по-разному, запечённые для меша карты нормалей необязательно будут одинаковыми для всех приложений. Даже для приложений, использующих одинаковое касательное пространство!
Познаём своего врага
Переворот из-за очевидной ошибки
Самая распространённая ошибка — проблема «OpenGL против Direct3D». Также они называются картами нормалей Y+ и Y-. Я включил их сюда просто для полноты обзора. Вероятнее всего, большинство людей, сталкивавшихся с этой проблемой, уже знают о ней, или, по крайней мере, научились решать её. Так как карты нормалей сгенерированы в одной ориентации, они по сути бесполезны для использования в приложении, рассчитанном на использование другой.
Карты нормалей в ориентации Direct3D в движке Unity, который ожидает ориентации OpenGL
Это можно понять по тому, что освещение кажется инвертированным или падающим с другой стороны, но только в некоторых направлениях освещения. Некоторые углы или грани могут выглядеть совершенно нормально, пока не переместишь источник освещения.
И с этой проблемой всё достаточно понятно. Большинство приложений, запекающих карты нормалей, имеют красивую большую кнопку или меню для переключения между OpenGL / Direct3D или параметр ориентации Y. Узнайте, какую ориентацию использует целевое приложение и запекайте карты в ней. Unity использует OpenGL и Y+. Unreal использует Direct3D и Y-. Это чётко можно понять, взглянув на саму текстуру карты нормалей. Если кажется, что в зелёном канале они освещены «сверху», то это ориентация OpenGL. Если снизу, то это Direct3D.
Странности с sRGB
Ещё одна распространённая ошибка — использование текстуры, помеченной как sRGB-текстура. Это изменяет способ сэмплирования текстуры GPU, заставляя его преобразовывать цветовые значения из пространства sRGB, из которого люди обычно выбирают значения цветов, в линейное цветовое пространство. Для карт нормалей этого делать не нужно. Если в Unity задать для текстуры тип «Normal map», то движок автоматически отключит эту опцию, но можно также отключить опцию «sRGB (Color Texture)» вручную, и текстура заработает как карта нормалей. По крайней мере, в Unity 2017.1 и более новых версиях.
Карта нормалей с типом текстуры Default и включенным sRGB
Распознать эту проблему можно по тому, что карты нормалей сильно смещены в одном направлении. В этой модели почти каждая грань выглядит так, как будто направлена на источник освещения. Очевидно, что так будет не всегда. В более детальной карте нормалей все части будут реагировать на освещение почти правильно, но слегка сместятся на какой-то угол. Иногда это может выглядеть, как будто вся поверхность меша искажена.
Слишком иной, чтобы быть нормальным
Ещё одна распространённая проблема заключается в использовании разных нормалей мешей для запекания и в игровом ассете. Как и в случае с касательными, чтобы всё работало, они должны в точности соответствовать. Эта ошибка часто возникает, если художники экспортируют меши с неправильными нормалями вершин. При этом любое приложение, открывающее меш, должно будет вычислять их самостоятельно. Не существует единственно верного способа решения, всё зависит от меша, поэтому убедитесь, что вы (или художники) экспортируете модель с окончательными нормалями!
Карты нормалей, запечённые на сглаженном меше, отображаемые как на меше с резкими рёбрами
Карты нормалей, запечённые на меше с резкими рёбрами, отображаемые как на сглаженном меше
Это ещё одна проблема, которую достаточно легко заметить. Если модель должна выглядеть так, как будто имеет резкие рёбра, но затенение изгибается от этих рёбер, то это значит, что вы запекли карту нормалей со сглаженными нормалями, но рендерятся они на меше с резким ребром. Если затенение изгибается по направлению к ребру, это значит, что карта была запечена с резким ребром и рендерится на плавном ребре. На самом деле в одной модели могут присутствовать оба случая, потому что можно указывать плавность или резкость отдельных рёбер, и даже степень плавности. Но по сути, они гарантированно будут ошибочными, если позволить другому приложению гадать, как должно быть. Представленная выше модель — крайний случай, и часто всё не так очевидно, потому что крупные части модели могут выглядеть совершенно нормально. Но в общем случае, если у вас есть несколько рёбер, выглядящих особенно странно, то вероятно это вызвано несоответствием нормалей вершин.
MikkTSpace против Autodesk
Менее заметная проблема — при запекании карт нормалей 3ds Max или Maya, в движке Unity они всегда будут неправильными. Они будут неправильными и в Unreal, да и практически в любой другой программе, за исключением той, в которой запекались. Карты нормалей 3ds Max не работают в Maya, и наоборот! Каждое из этих приложений вычисляет касательное пространство собственным уникальным способом, не соответствующим другим приложениям. При самостоятельном использовании они выглядят идеально, и если вы генерируете карты нормалей для использования в этих программах, то старайтесь запекать карты нормалей в них. Почти никакая другая программа не способна запекать правильные карты нормалей для этих приложений, поэтому у вас как бы нет выбора.
Единственное решение в этом случае — не запекать карты нормалей при помощи этих приложений, если вы хотите использовать их где-то ещё. Если вам нужна бесплатная программа, то воспользуйтесь xNormal. Это стандартный для отрасли инструмент. Если где-то в конвейере создания контента вы используете Substance, то воспользуйтесь ею.
3ds Max запёк карту нормалей, используемую в Unity
Проще всего заметить эту проблему — увидеть, что плоские грани имеют небольшие вмятины или кривизну. В некоторых случаях это может быть очень незаметно, особенно в органических объектах. Поэтому проблемы такого рода часто остаются незамеченными.
Многие приложения, не принадлежащие Autodesk, перешли к способу вычисления касательного пространства, который называется MikkTSpace. И xNormal, и Substance, а также Blender, Unity, Unreal, Godot, Marmoset, Houdini, Modo и некоторые другие, по умолчанию используют MikkTSpace или, по крайней мере, каким-то образом поддерживают его, что сильно повышает их совместимость. По сути, за последний десяток лет он фактически стал отраслевым стандартом во всех случаях, когда используются карты нормалей касательного пространства. Предположительно, на каком-то этапе 3ds Max добавит их поддержку, но непонятно, планируют ли разработчики позволить запекать карты нормалей при помощи MikkTSpace или просто обеспечат поддержку просмотра/рендеринга с их помощью.
Экспорт мешей и Substance Painter
Ещё одна проблема с 3ds Max и Maya заключается в том, что при экспорте мешей из них есть опция экспорта с «касательными и бинормалями» (tangents and binormals). Они не будут находиться в настоящих касательных MikkTSpace, но будут основаны на них. Некоторые приложения по умолчанию принудительно пересчитывают правильное пространство MikkTSpace (например, xNormal, Unity и Unreal), но другие приложения используют касательные меша (при их наличии) «как они есть» и вычисляют MikkTSpace, только если меш не имеет касательных. Например, Substance Painter использует касательные меша при их наличии так, как они есть, то есть Painter создаёт карты нормалей, очень похожие на запечённые 3ds Max! Похожие, но не совсем такие, поэтому если импортировать их обратно в 3ds Max, то они не будут выглядеть так же хорошо, как запечённые в Max.
MikkTSpace против MikkTSpace?
Следующая проблема заключается в том, что существует не одна версия MikkTSpace! Да, это не был бы стандарт, если бы каким-то образом не вносил путаницу. MikkTSpace определяет и как данные должны храниться в меше для каждой вершины, и как данные используются при рендеринге. Все приложения с поддержкой MikkTSpace вычисляют одинаковые данные меша для вершин. Но существует два разных способа использования этих данных при рендеринге. MikkTSpace определяет, как вычислить вектор касательной и знак для каждой вершины на основании ориентации UV. Но при использовании касательного пространства касательная к двум точкам (bitangent) пересчитывается или для каждой вершины, или для каждого пикселя. Этот параметр тоже должен быть одинаковым и для запекания, и для просмотра.
xNormal и Substance имеют опцию для запекания карт нормалей при помощи обоих способов. Эти опции называются «compute tangent space per fragment» в Substance и «compute binormal in the pixel shader» в xNormal. Не нужно использовать её, если выполняете запекание для встроенных способов рендеринга Unity. Однако, возможно стоит делать это для SRP, в зависимости от выбранного типа!
«Pixel shader binormals» приложения xNormal со встроенным упреждающим рендерингом (forward renderer) Unity
Визуально это похоже на использование совершенно иного касательного пространства, но обычно гораздо менее выражено. Некоторые грани могут выглядеть так, как будто нормаль немного искажена, а не будут иметь большую вмятину. Решение здесь очень просто — не выбирайте эту опцию, если вы запекаете для встроенных способов рендеринга Unity. Ставьте флажок, если запекаете для HDRP движков Unreal или Unity.
Во многих источниках говорится, что Blender и Unity идентичны, но на самом деле это никогда не является правдой! Blender, как и Unity, использует MikkTSpace и ориентацию OpenGL, но, как и Unreal, использует бинормали для каждого фрагмента (fragment binomals).
Хорошая новость заключается в том, что сейчас шейдеры LWRP, URP и Shader Graph используют бинормали для каждой вершины, а встроенные шейдеры HDRP — для каждого пикселя. В будущем обновлении Shader Graph (7.2.0) будет выполнен переход на попиксельное вычисление, а спустя какое-то время на него перейдут и встроенные шейдеры URP. А стандартные способы рендеринга можно изменять при помощи собственных шейдеров, если вы выберете такой маршрут.
Триангулируй, триангулируй, триангулируй!
Последняя проблема — это разные способы триангуляции. Если экспортировать меши без предварительной триангуляции, то нет гарантий, что другие приложения триангулируют модель точно так же. Это тоже вредит картам нормалей! Поэтому триангулируйте меши, или с помощью параметров экспорта, или через модификатор триангуляции.
Запечённые в Substance Painter карты нормалей, перенесённые в Unity на меше, экспортированном из 3ds Max как четырёхугольники (quads)
Это проявляется очень похоже на совершенно неправильное касательное пространство, как будто карты нормалей были запечены в 3ds Max. Но в общем случае они немного резче, почти сгибаются, а не выглядят как плавная вогнутость. В этом меше видна X-образная форма на внешних гранях. Эта проблема может оказаться сложной и раздражающей, когда в конвейере производства контента с моделью работает несколько человек в разных приложениях, потому что часто бывает полезно не триангулировать меш и сохранять красивые полигоны. В таком случае приходится или смириться с неудобствами, или сделать так, чтобы последний инструмент в конвейере перед экспортом модели в игру также использовался для генерации меша, применяемого для запекания нормалей. Как и касательное пространство с нормалями вершин (которые являются частью касательного пространства), триангуляция тоже должна точно совпадать.
Список для создания идеальных карт нормалей для Unity
Итак, вот советы по генерации карт нормалей касательного пространства для lowpoly-мешей из исходных highpoly-моделей для использования в Unity:
Ура! Идеальная карта нормалей!
Ну, последнее, мы просто проигнорируем. Настолько «идеальная», насколько можно получить со сжатыми форматами текстур с 8 битами на канал. Карта нормалей с дизерингом может выглядеть немного лучше, чем полосы, но, к сожалению, эту опцию предоставляет не так много инструментов. В противном случае можно использовать 16-битные карты нормалей. Но это уже тема для другой статьи.
Подробнее о MikkTSpace
На полуофициальном сайте mikktspace.com хорошо объясняются основные преимущества MikkTSpace. В частности, это пространство хорошо работает и для пересчёта, и для переноса данных касательных между программами с разной обработкой мешей, сохраняя при этом целостность результатов. Текст сайта — это копия уже несуществующей страницы вики Blender, написанной самим Мортеном С. Миккельсеном. К сожалению, в нём упоминается только версия с касательными к двум точкам для каждого пикселя, но не версия для каждой вершины, по умолчанию используемая Unity и xNormal. Так как в Blender используются производные от попиксельных касательных, логично, что упомянуты только они.
Вот комментарии об этих двух опциях из оригинального файла mikktspace.h:
Для карт нормалей достаточно использовать следующую упрощённую версию касательной к двум точкам, генерируемой на уровне пикселей/вершин.
bitangent = fSign * cross(vN, tangent);
Чтобы избежать визуальных ошибок (искажений/нежелательных резких рёбер в освещении) при использовании сэмплируемых карт нормалей, сэмплер карты нормалей должен быть точно противоположен преобразованию пиксельного шейдера.
Вероятно, наиболее эффективным преобразованием, которое мы можем выполнить в пиксельном шейдере, достигается прямым использованием «разнормализованной» интерполированной касательной, касательной к двум точкам и нормали вершины: vT, vB и vN.
пиксельный шейдер (быстрое преобразование обратно)
vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );
где vNt — это нормаль касательного пространства. Сэмплер карты нормалей должен аналогично использовать интерполированную и «разнормализованную» касательную, касательную к двум точкам и нормаль вершины, чтобы соответствовать пиксельному шейдеру.
Если вы решите воссоздать касательную к двум точкам в пиксельном, а не в вершинном шейдере, как описано выше, то делайте это тоже в сэмплере карты нормалей.
Итак, в исходном примере касательная к двум точкам воссоздаётся в вершинном шейдере и передаётся в пиксельный шейдер как ещё одно интерполированное значение. Именно так в настоящее время обрабатывают карты нормалей встроенные шейдеры Unity. Пример с выполнением этой задачи в пиксельном шейдере — это скорее экспромт, а не «основной» пример. Кроме того, поскольку реализация для Unity 5.3 (и xNormal?) была создана самим Мортеном С. Миккельсеном, и в обоих используются касательные для каждой вершины, это является ещё одним доказательством нашей теории. Но тогда в Blender использовались попиксельные касательные, и Мортен тоже имел к этому отношение. Вероятно, на самом деле это была просто опция, вызвавшая небольшие изменения в готовых шейдерах Unity, и в то время для графики реального времени ни один из способов не считался намного лучше другого.
Преимущество попиксельной реализации заключается в устранении двух интерполированных значений ценой небольших лишних затрат АЛУ (математики пиксельного шейдера). Это значит, что вместо трёх значений Vector3 (касательной, касательной к двум точкам и нормали) нам нужны только Vector4 и Vector3 (касательная со знаком и нормаль). На современных GPU это немного быстрее. И это определённо быстрее при выполнении в ЦП, так как это можно использовать для рендереров не в реальном времени. С этой точки зрения логично, что бОльшая часть индустрии решила воссоздавать касательную к двум точкам в пикселе, а не в вершине.
Забавно, что когда я писал эту статью, Мортен продвигал переход в Unity SRP на попиксельные касательные к двум точкам! Это просто неудачное стечение обстоятельств, что у встроенных способов рендеринга Unity осталось наследие неудобного выбора.
Касательное пространство в разных приложениях
Хотя эта статья в основном рассматривает Unity, поскольку сегодня этот движок стал мейнстримом, я хотел, чтобы моё исследование было более подробным. Поэтому я создал список различных приложений с информацией об использовании касательного пространства.
Unity (5.3+)
Ориентация: OpenGL Y+
Касательное пространство: MikkTSpace
Вычисление касательных к двум точкам для MikkTSpace: для вершин или для пикселей*
Примечания: по умолчанию движок принудительно переопределяет касательные меша при импорте, чтобы использовать MikkTSpace. Для опытных пользователей он имеет опции для использования касательных меша, но это не гарантирует совместимости с картами нормалей, запечёнными в других инструментах, особенно при использовании встроенных шейдеров. Могут быть заменены на полностью собственные шейдеры. А для своих встроенных шейдеров HDRP использует попиксельные касательные к двум точкам. На момент написания статьи было намечено, что шейдеры URP и Shader Graph тоже перейдут на попиксельные касательные к двум точкам. До Unity 5.3 движок использовал касательные Lengyel, которые по-прежнему можно использовать, выбрав в импортере мешей опцию Legacy Tangents, но встроенные шейдеры больше не поддерживают это касательное пространство.
Дополнение: до URP 7.2.0 обработка карт нормалей в Shader Graph была совершенно поломана! В ней не используется правильно ни MikkTSpace, ни даже касательное пространство Lengyel, и исправить это внутри Shader Graph невозможно. По возможности обновитесь до 7.2.0.
Unreal Engine 4
Ориентация: Direct3D Y-
Касательное пространство: MikkTSpace
Вычисление касательных к двум точкам для MikkTSpace: попиксельное
Примечания: по умолчанию движок при импорте переопределяет касательные меша, используя пространство MikkTSpace. Для опытных пользователей он имеет опции использования касательных меша, но это не гарантирует совместимости с картами нормалей, запечёнными в других инструментах.
Godot
Ориентация: OpenGL Y+
Касательное пространство: MikkTSpace
Вычисление касательных к двум точкам для MikkTSpace: для каждой вершины
Примечания: если данных о касательных не существует, может при импорте вычислять касательные меша в MikkTSpace. Документация движка предлагает экспортировать меши с данными о касательных, но поскольку большинство инструментов не экспортирует правильные касательные мешей, я бы игнорировал этот совет. Даже если вы используете исходные касательные меша, то нет гарантии, что карты нормалей будут выглядеть правильно.
PlayCanvas
Ориентация: OpenGL Y+
Касательное пространство: Lengyel/Schüler*
Вычисление касательных к двум точкам: для каждой вершины/на основе производных
Примечания: предоставляет несколько способов обработки касательного пространства. Сгенерированные кодом касательные не используют MikkTSpace. Имеет опции использования касательной к двум точкам для каждой вершины, которые или нормализуют векторы касательного пространства в пиксельном шейдере «fastTbn», не нормализуя касательное пространство во фрагментном шейдере (что нам нужно, поскольку это соответствует реализации MikkTSpace с вычислением для каждой вершины), или используют основанную на производных TBN, позаимствованную у Кристиана Шулера; она полностью игнорирует касательные меша и вычисляет касательное пространство в пиксельном шейдере. Сейчас по умолчанию используются карты нормалей на основе производных, для которых, насколько я знаю, нет инструментов запекания точных карт нормалей. Возможно, можно импортировать меши с существующими нормалями и данными касательных MikkTSpace, а затем использовать опцию материала «fastTbn», чтобы это соответствовало встроенным способам рендеринга Unity, но я считаю, что такое должно делаться в коде, а не через интерфейс редактора.
Filament
Ориентация: OpenGL Y+
Касательное пространство: MikkTSpace
Вычисление касательных к двум точкам для MikkTSpace: для каждой вершины
Примечания: в зависимости от производительности в будущем движок может перейти к попиксельным касательным к двум точкам.
3ds Max
Ориентация: Direct3D Y-*
Касательное пространство: собственное
Опции экспорта мешей: экспортируйте файлы FBX со включенным «Triangulate», но с отключенным «Tangents and Binormals». Нормали нужно экспортировать всегда. «Split per-vertex Normals» можно оставить отключенным.
Примечания: по умолчанию 3ds Max предполагает, что в картах нормалей используется ориентация Direct3D, но имеет опции для переворота ориентации X и Y при использовании текстур карт нормалей на материалах. Запекание всегда использует ориентацию Direct3D. Поддержка MikkTSpace появится в будущих версиях.
Ориентация: OpenGL Y+
Касательное пространство: собственное
Опции экспорта мешей: экспортируйте файлы FBX со включенным «Triangulate», но с отключенным «Tangents and Binormals». Нормали экспортируются всегда. «Split per-vertex Normals» можно оставить отключенным.
Примечания: некоторые люди говорили мне, что Maya в частично уже поддерживает MikkTSpace, но я не знаю, в какой степени.
Blender (2.57+)
Ориентация: OpenGL Y+
Касательное пространство: MikkTSpace
Вычисление касательных к двум точкам для MikkTSpace: для каждого пикселя
Опции экспорта мешей: экспортируйте файлы FBX со значением Smoothing «Normals Only». Можно экспортировать с «Tangent Space» или без него, если целевое приложение тоже использует MikkTSpace, например, как Unity или Unreal.
Примечания: в Blender нет опции триангуляции при экспорте в FBX, поэтому предварительно используйте модификатор Triangulate. Запечённые в Blender карты нормалей не будут на 100% совместимыми с Unity, но если перевернуть зелёный канал в стороннем приложении или в настроенном материале, то они идеально работают в Unreal.
xNormal (3.17.5+)
Ориентация: настраиваемая
Касательное пространство: MikkTSpace*
Вычисление касательных к двум точкам для MikkTSpace: настраиваемое (для каждой вершины или пикселя)
Опции запекания карт нормалей: имеет опции переворота всех трёх осей карты нормалей при экспорте. Для Unity нужны X+ Y+ Z+. Также есть опция использования касательных к двум точкам для каждой вершины или пикселя (то есть фрагмента). По умолчанию xNormal использует касательные к двум точкам для каждой вершины, но это можно изменить в меню Plugins Manager (значок вилки питания в левом нижнем углу), нажав на «Tangent basis calculator», «Mikk — TSpace» и щёлкнув на Configure. При этом появится всплывающее окно с единственной опцией «Compute binormal in the pixel shader». Она должна быть выключена для Unity и включена для Unreal.
Примечания: хотя xNormal имеет поддержку только MikkTSpace, её система плагинов теоретически поддерживает любое касательное пространство. Пока единственный знакомый мне плагин касательного пространства предназначен для запекания касательного пространства Unity 4 и уже бесполезен. То есть если подходить реалистично, приложение поддерживает только MikkTSpace.
Substance Painter
Ориентация: настраиваемая (OpenGL или Direct3D)
Касательное пространство: MikkTSpace
Вычисление касательных к двум точкам для MikkTSpace: настраиваемое (для вершин или для пикселей)
Опции запекания карт нормалей: имеет опции запекания в ориентации OpenGL или Direct3D. И опцию для использования касательных к двум точкам для каждой вершины или пикселя (фрагмента), что настраивается параметром «Compute tangent space per fragment». При экспорте для встроенных способов рендеринга Unity нужно выбрать OpenGL и отключить Compute tangent space per fragment в проекте и параметрах экспорта, а при экспорте в Unreal или Unity HDRP сделать обратное.
Примечания: Substance Painter по умолчанию использует те данные касательных, которые использует импортированный меш. Если только вы не выполняете экспорт из Blender, вам это не нужно! Но для отключения этой функции нет никаких опций. Перед импортом в Substance Painter экспортируйте меши без касательных. Ранее в документации Substance ошибочно утверждалось, что вычисления касательных к двум точкам
в Blender соответствуют Unity, но теперь ошибку исправили!
Marmoset Toolbag 3
Ориентация: настраиваемая
Касательное пространство: настраиваемое
Вычисление касательных к двум точкам для MikkTSpace: попиксельное
Примечания: похоже, что программа имеет опции для просмотра или запекания карт нормалей практически в любой существующей конфигурации. В том числе и готовые настройки для касательных пространств 3ds Max и Maya! Однако опция касательного пространства «Mikk / xNormal» использует попиксельные касательные к двум точкам, и это невозможно изменить. То есть Marmoset Toolbag 3 не может правильно просматривать или запекать карты нормалей для встроенных способов рендеринга Unity. Возможно в будущих версиях ситуация изменится, потому что разработчики знают о проблеме.
3DCoat
Houdini (16.0.514+)
Ориентация: настраиваемая
Касательное пространство: настраиваемое*
Вычисление касательных к двум точкам для MikkTSpace: попиксельное
Примечания: поддерживает свой собственный базис касательных или MikkTSpace, как для просмотра, так и для запекания, но только в попиксельной версии. Похоже, по умолчанию не использует MikkTSpace для рендеринга, но начиная с Houdini 17.5. меши можно модифицировать встроенными нодами для использования касательных MikkTSpace.
Modo (10+?)
Ориентация: настраиваемая
Касательное пространство: настраиваемое
Вычисление касательных к двум точкам для MikkTSpace: настраиваемое (вершинное или попиксельное)*
Примечания: похоже, что поддерживает рендеринг и запекания в различных касательных пространствах, а также опции попиксельных касательных к двум точкам для экспорта. Я не тестировал это.
Knald
Ориентация: настраиваемая
Касательное пространство: MikkTSpace
Вычисление касательных к двум точкам для MikkTSpace: попиксельное
Примечания: по умолчанию использует MikkTSpace и переопределяет касательные импортированных мешей, но при необходимости есть опция использования существующих касательных меша. Ориентацию карт нормалей можно переворачивать параметром, применяемым ко всему проекту.
В заключение
Важный аспект заключается в том, что если низкополигональная модель, на которую вы выполняете запекание — это просто плоскость (например для текстуры с тайлингом), то всё написанное несущественно и вы можете запекать при помощи любого инструмента… если только будете сохранять всё в правильной ориентации или в дальнейшем переворачивать соответствующие каналы текстуры.
Кроме того, большинство перечисленных в статье проблем заметно только на твердотельных моделях. Органические формы могут скрывать самые малозаметные различия, например, различие вершинных и попиксельных касательных к двум точкам.