Это обзор процесса рендеринга. Не волнуйтесь, если вы не все сразу поймете, каждый шаг будет подробно описан в последующих уроках.
Все, что вы видите на экране вашего компьютера, даже текст, который вы читаете прямо сейчас (предполагая, что вы читаете это на электронном устройстве отображения, а не на распечатке) - это просто двумерный массив пикселей. Если вы сделаете скриншот чего-нибудь на экране и увеличите его, он будет выглядеть состоящим из блоков.
Рисунок 8. Изображение
Каждый из этих блоков представляет собой пиксель. Слово "pixel" происходит от термина "Picture Element". Каждый пиксель на экране имеет свой цвет. Двухмерный массив пикселей называется изображение (image).
Поэтому целью любой графики является определение того, какой цвет поместить в какие пиксели. Это определение делает текст похожим на текст, окна выглядят как окна и так далее.
Поскольку вся графика - это всего лишь двухмерный массив пикселей, как же работает 3D? 3D графика, таким образом, представляет собой систему производства цветов для пикселей, которые убеждают вас в том, что сцена, на которую вы смотрите, является 3D миром, а не 2D изображением. Процесс преобразования трехмерного мира в двухмерное изображение этого мира называется rendering..
Существует несколько методов рендеринга 3D мира. Процесс, используемый графическим оборудованием, работающим в реальном времени, например, в вашем компьютере, включает в себя очень много подделок. Этот процесс называется растеризация,, а система рендеринга, использующая растеризацию, называется растеризатор..
В растеризаторах все объекты, которые вы видите - пустые тела. Существуют техники, которые позволяют вам разрезать эти пустые тела, но это просто заменяет часть тела другим телом, которое показывает, как выглядит внутренняя часть. Все является телами.
Все эти тела сделаны из треугольников. Даже поверхности, которые кажутся круглыми, являются просто треугольниками, если присмотреться. Существуют техники, которые генерируют больше треугольников для объектов, которые кажутся ближе или больше, так что зритель почти никогда не сможет увидеть ограненный силуэт объекта. Но они всегда сделаны из треугольников.
Замечание
Некоторые растеризаторы используют плоские четырёхугольники: четырёхсторонние объекты, где все точки лежат в одной плоскости. Одной из причин, по которой аппаратные растеризаторы всегда используют треугольники, является то, что все линии треугольника гарантированно лежат в одной плоскости. Знание этого делает процесс растеризации менее сложным.
Объект состоит из серии смежных треугольников, определяющих внешнюю поверхность объекта. Такие серии треугольников часто называют геометрия, модель или меш (сетка). Эти термины используются взаимозаменяемо.
Процесс растеризации состоит из нескольких этапов. Эти этапы упорядочиваются в конвейер, в который сверху заходят треугольники, а снизу заполняется 2D изображение. Это одна из причин, по которой растеризация так поддаётся аппаратному ускорению: она работает с каждым треугольником по очереди, в определённом порядке. Треугольники могут подаваться в верхнюю часть конвейера, в то время как треугольники, отправленные ранее, могут еще находиться на какой-то фазе растеризации.
Порядок подачи треугольников и различных мешей в растеризатор может влиять на его производительность. Всегда помните, что независимо от того, как вы представляете данные треугольной сетки, растеризатор будет обрабатывать каждый треугольник в определенном порядке, рисуя следующий только после того, как предыдущий треугольник будет закончен.
OpenGL - это API для доступа к аппаратному растеризатору. Как таковой, он соответствует модели для растеризационных 3D рендеров. Растеризатор получает от пользователя последовательность треугольников, выполняет над ними операции и записывает пиксели на основе данных этого треугольника. Это является упрощением работы растеризации в OpenGL, но это полезно для наших целей.
Треугольники и вершины. Треугольники состоят из 3 вершин. Вершина представляет собой набор произвольных данных. Для простоты (подробнее об этом мы поговорим позже) скажем, что эти данные должны содержать точку в трехмерном пространстве. Она может содержать и другие данные, но должна иметь, по крайней мере, это. Любые 3 точки, находящиеся не на одной прямой, создают треугольник, поэтому наименьшая информация для треугольника состоит из 3 трехмерных точек.
Точка в 3D пространстве определяется 3 числами или координатами. Координата X, координата Y и координата Z. Они обычно пишутся в скобках, как (X, Y, Z).
Обзор растеризации
Растеризационный конвейер, особенно для современного оборудования, очень сложен. Это очень упрощенный обзор этого конвейера. Необходимо иметь простое понимание конвейера, прежде чем мы посмотрим на детали рендеринга с помощью OpenGL. Эти детали могут быть ошеломляющими без обзора с высоты.
Отсечение невидимых деталей. Первая фаза растеризации заключается в преобразовании вершин каждого треугольника в определенную область пространства. Все, что находится в этом объеме, будет отображено на выходном изображении, а все, что находится за пределами этой области, - нет. Эта область соответствует виду мира, который пользователь хочет отрисовать.
Объем, в который превращается треугольник, называется, в языке OpenGL, пространством отсечения. Позиции вершин треугольника в пространстве отсечения называются координатами отсечения.
Координаты отсечения немного отличаются от обычных положений. Положение в 3D пространстве имеет 3 координаты. Позиция в пространстве отсечения имеет четыре координаты . Первые три - это обычные позиции X, Y, Z; четвёртая называется W. Эта последняя координата на самом деле определяет, каковы размеры пространства отсечения для этой вершины.
Пространство отсечения на самом деле может быть разным для разных вершин в треугольнике. Это область 3D пространства в диапазоне [-W, W] в каждом из направлений X, Y и Z. Таким образом, вершины с разной координатой W находятся в другом кубе пространства отсечения, отличном от других вершин. Так как каждая вершина может иметь независимую составляющую W, то каждая вершина треугольника существует в собственном пространстве отсечения.
В пространстве отсечения положительное направление X - вправо, положительное направление Y - вверх, а положительное направление Z - в сторону от зрителя.
Процесс преобразования позиций вершин в пространство отсечения достаточно произволен. OpenGL предоставляет много гибкости на этом этапе. Мы подробно рассмотрим этот шаг в уроках.
Поскольку пространство отсечения является видимой преобразованной версией мира, любые треугольники, которые попадают за пределы этого региона, отбрасываются. Любые треугольники, которые частично находятся за пределами этой области, проходят процесс, называемый клиппинг. Это разбивает треугольник на несколько треугольников меньшего размера, так что все треугольники меньшего размера находятся полностью в пространстве клипа. Отсюда и название "пространство отсечения".
Нормализованные координаты. Пространство отсечения интересно, но неудобно. Величина этого пространства различна для каждой вершины, что делает визуализацию треугольника довольно затруднительной. Поэтому пространство отсечения трансформируется в более разумное пространство координат: нормализованные координаты устройства.
Этот процесс очень прост. X, Y и Z каждой вершины делятся на W, чтобы получить нормализованные координаты устройства. Вот и все.
Пространство нормализованных координат устройства - это, по сути, просто пространство отсечения, за исключением того, что диапазон X, Y и Z равен [-1, 1]. Направления одинаковы. Деление на W является важной частью проецирования 3D треугольников на 2D изображения; об этом мы поговорим в одном из следующих уроков.
Рисунок 9. Пространство нормализованных координат устройства
Куб обозначает границы нормализованного пространства координат устройства.
Трансформация окна. Следующим этапом растеризации является повторное преобразование вершин каждого треугольника. На этот раз они преобразуются из нормализованных координат устройства в координаты окна. Как следует из названия, координаты окна относятся к окну, внутри которого запущена OpenGL.
Несмотря на то, что они относятся к окну, они все равно являются трехмерными координатами. Х переходит вправо, Y - вверх, а Z - вдаль, точно так же, как и для пространства отсечения. Единственное отличие состоит в том, что границы этих координат зависят от окна просмотра. Следует также отметить, что в то время как они находятся в координатах окна, точность не теряется. Это не целочисленные координаты, это все равно значения с плавающей точкой, и поэтому они имеют точность, превышающую точность одного пикселя.
Границы для Z - [0, 1], 0 - самая близкая и 1 - самая дальняя. Положения вершин вне этого диапазона не видны.
Обратите внимание, что координаты окна имеют левое нижнее положение как начальная точка (0, 0). Это не соответствует тому, к чему пользователи привыкли в координатах окна, где левая верхняя точка - это начало координат. Есть трюки преобразования, которые вы можете воспроизвести, чтобы позволить вам работать в левом верхнем координатном пространстве, если вам это нужно.
Детали этого процесса будут подробно обсуждаться по мере прохождения обучения.
Преобразование сканирования. После преобразования координат треугольника в координаты окна, треугольник проходит процесс, называемый преобразование сканирования. Этот процесс берёт треугольник и разбивает его на части на основе расположения пикселей окна над выходным изображением, которое покрывает треугольник.
Рисунок 10. Треугольник после преобразования сканирования
На центральном изображении показана цифровая сетка выходных пикселей; окружности представляют центр каждого пикселя. Центр каждого пикселя представляет собой образец: дискретное расположение в области пикселя. Во время преобразования сканирования треугольник будет выдавать фрагмент для каждого пиксела, находящегося в пределах 2D области треугольника.
На изображении справа показаны фрагменты, образовавшиеся в результате преобразования сканирования треугольника. Это создает грубое приближение общей формы треугольника.
Очень часто происходит рендеринг треугольников с общими краями. OpenGL дает гарантию, что до тех пор, пока позиции разделяемых рёберных вершин являются идентичными, при преобразовании сканирования не будет разрывов.
Рисунок 11. Преобразование сканирования общих граней
To make it easier to use this, OpenGL also offers the guarantee that if you pass the same input vertex data through the same vertex processor, you will get identical output; this is called the invariance guarantee. So the onus is on the user to use the same input vertices in order to ensure gap-less scan conversion.
Scan conversion is an inherently 2D operation. This process only uses the X and Y position of the triangle in window coordinates to determine which fragments to generate. The Z value is not forgotten, but it is not directly part of the actual process of scan converting the triangle.
The result of scan converting a triangle is a sequence of fragments that cover the shape of the triangle. Each fragment has certain data associated with it. This data contains the 2D location of the fragment in window coordinates, as well as the Z position of the fragment. This Z value is known as the depth of the fragment. There may be other information that is part of a fragment, and we will expand on that in later tutorials.
Fragment Processing. This phase takes a fragment from a scan converted triangle and transforms it into one or more color values and a single depth value. The order that fragments from a single triangle are processed in is irrelevant; since a single triangle lies in a single plane, fragments generated from it cannot possibly overlap. However, the fragments from another triangle can possibly overlap. Since order is important in a rasterizer, the fragments from one triangle must all be processed before the fragments from another triangle.
This phase is quite arbitrary. The user of OpenGL has a lot of options of how to decide what color to assign a fragment. We will cover this step in detail throughout the tutorials.
Direct3D Note
Direct3D prefers to call this stage “pixel processing” or “pixel shading”. This is a misnomer for several reasons. First, a pixel's final color can be composed of the results of multiple fragments generated by multiple samples within a single pixel. This is a common technique to remove jagged edges of triangles. Also, the fragment data has not been written to the image, so it is not a pixel yet. Indeed, the fragment processing step can conditionally prevent rendering of a fragment based on arbitrary computations. Thus a “pixel” in D3D parlance may never actually become a pixel at all.
Fragment Writing. After generating one or more colors and a depth value, the fragment is written to the destination image. This step involves more than simply writing to the destination image. Combining the color and depth with the colors that are currently in the image can involve a number of computations. These will be covered in detail in various tutorials.
Colors
Previously, a pixel was stated to be an element in a 2D image that has a particular color. A color can be described in many ways.
In computer graphics, the usual description of a color is as a series of numbers on the range [0, 1]. Each of the numbers corresponds to the intensity of a particular reference color; thus the final color represented by the series of numbers is a mix of these reference colors.
The set of reference colors is called a colorspace. The most common color space for screens is RGB, where the reference colors are Red, Green and Blue. Printed works tend to use CMYK (Cyan, Magenta, Yellow, Black). Since we're dealing with rendering to a screen, and because OpenGL requires it, we will use the RGB colorspace.
Note
You can play some fancy games with programmatic shaders (see below) that allow you to work in different colorspaces. So technically, we only have to output to a linear RGB colorspace.
So a pixel in OpenGL is defined as 3 values on the range [0, 1] that represent a color in a linear RGB colorspace. By combining different intensities of these 3 colors, we can generate millions of different color shades. This will get extended slightly, as we deal with transparency later.
Shader
A shader is a program designed to be run on a renderer as part of the rendering operation. Regardless of the kind of rendering system in use, shaders can only be executed at certain points in that rendering process. These shader stages represent hooks where a user can add arbitrary algorithms to create a specific visual effect.
In terms of rasterization, as outlined above, there are several shader stages where arbitrary processing is both economical for performance and offers high utility to the user. For example, the transformation of an incoming vertex to clip space is a useful hook for user-defined code, as is the processing of a fragment into final colors and depth.
Shaders for OpenGL are run on the actual rendering hardware. This can often free up valuable CPU time for other tasks, or simply perform operations that would be difficult if not impossible without the flexibility of executing arbitrary code. A downside of this is that they must live within certain limits that CPU code would not have to.
There are a number of shading languages available to various APIs. The one used in this tutorial is the primary shading language of OpenGL. It is called, unimaginatively, the OpenGL Shading Language, or GLSL. for short. It looks deceptively like C, but it is very much not C.