Добро пожаловать в этот третий и последний выпуск нашей мини-серии WebGL Essentials. В этом уроке мы рассмотрим освещение и добавление 2D-объектов в вашу сцену. Здесь много новой информации, так что давайте окунемся!
Светлый
Освещение может быть самым техническим и сложным для понимания 3D-приложением. Твердое понимание освещения абсолютно необходимо.
Как работает свет?
Прежде чем мы перейдем к различным методам освещения и кодирования, важно знать, как свет работает в реальном мире. Каждый источник света (например, лампочка, солнце и т. Д.) Генерирует частицы, называемые фотонами. Эти фотоны прыгают вокруг объектов, пока они в конечном итоге не попадают в наши глаза. Наши глаза конвертируют фотоны, чтобы создать визуальную «картинку». Вот как мы видим. Свет также аддитивен, это означает, что объект с большим количеством цвета ярче, чем объект без цвета (черный). Черный — это полное отсутствие цвета, тогда как белый содержит все цвета. Это важное различие при работе с очень яркими или «пересыщающими» лампами.
Яркость — это только один принцип, который имеет несколько состояний. Отражение, например, может иметь различные уровни. Объект, как зеркало, может быть полностью отражающим, тогда как другие объекты могут иметь матовую поверхность. Прозрачность определяет, как объекты изгибают свет и вызывают преломление; один объект может быть полностью прозрачным, в то время как другие могут быть непрозрачными (или любой промежуточный этап).
Список продолжается, но я думаю, вы уже видите, что свет не так прост.
Если вы хотите, чтобы даже небольшая сцена имитировала реальный свет, она работала бы со скоростью около 4 кадров в час, и это на мощном компьютере. Чтобы обойти эту проблему, программисты используют приемы и приемы для симуляции полуреалистичного освещения с разумной частотой кадров. Вы должны придумать компромисс между реализмом и скоростью. Давайте посмотрим на некоторые из этих методов.
Прежде чем приступить к разработке различных методов, я хотел бы дать вам небольшой отказ от ответственности. Существует много противоречий по поводу точных названий для различных методов освещения, и разные люди будут давать вам разные объяснения того, что такое «Лучевое преобразование» или «Составление карты освещения». Поэтому, прежде чем я получу письмо с ненавистью, я хотел бы сказать, что я собираюсь использовать имена, которые я выучил; некоторые люди могут не согласиться с моими точными названиями. В любом случае, важно знать, каковы различные методы. Так что без дальнейших церемоний, давайте начнем.
Вы должны придумать компромисс между реализмом и скоростью.
Трассировка лучей
Трассировка лучей является одним из наиболее реалистичных методов освещения, но также и одним из более дорогостоящих. Трассировка лучей эмулирует настоящий свет; он излучает «фотоны» или «лучи» от источника света и отскакивает от них. В большинстве реализаций трассировки лучей лучи исходят от «камеры» и отражаются от сцены в противоположном направлении. Этот метод обычно используется в фильмах или сценах, которые могут быть воспроизведены заранее. Это не означает, что вы не можете использовать трассировку лучей в приложении реального времени, но это заставляет вас смягчать другие эффекты в сцене. Например, вам может потребоваться уменьшить количество «отскоков», которые должны выполнять лучи, или вы можете убедиться, что нет объектов, которые имеют отражающие или преломляющие поверхности. Трассировка лучей также может быть приемлемым вариантом, если в вашем приложении очень мало источников света и объектов.
Если у вас есть приложение в реальном времени, вы можете предварительно скомпилировать части своей сцены.
Если источники света в вашем приложении не перемещаются или перемещаются только в небольшой области за раз, вы можете предварительно скомпилировать освещение с помощью очень продвинутого алгоритма трассировки лучей и пересчитать небольшую область вокруг движущегося источника света. Например, если вы делаете игру, в которой огни не движутся, вы можете предварительно скомпилировать мир со всеми желаемыми огнями и эффектами. Затем вы можете просто добавить тень вокруг своего персонажа, когда он движется. Это дает очень качественный вид при минимальном объеме обработки.
Луч Кастинг
Приведение лучей очень похоже на трассировку лучей, но «фотоны» не отражаются от объектов и не взаимодействуют с различными материалами. В типичном приложении вы начинаете с темной сцены, а затем рисуете линии от источника света. Все, что свет поражает, горит; все остальное остается темным. Этот метод значительно быстрее, чем трассировка лучей, но при этом дает реалистичный эффект тени. Но проблема с лучевой отливкой заключается в ее ограниченности; вам не хватает места для работы при попытке добавить эффекты, такие как отражения. Как правило, вам приходится искать какой-то компромисс между приведением лучей и трассировкой лучей, баланс между скоростью и визуальными эффектами.
Основная проблема обоих этих методов заключается в том, что WebGL не дает вам доступа ни к одной из вершин, кроме активной в данный момент.
Это означает, что вы либо должны выполнять все на процессоре (в отличие от видеокарты), либо у вас есть второй шейдер, который вычисляет все освещение и сохраняет информацию в поддельной текстуре. Затем вам нужно будет распаковать данные текстуры обратно в информацию об освещении и сопоставить их с вершинами. Таким образом, текущая версия WebGL не очень подходит для этого. Я не говорю, что это невозможно, я просто говорю, что WebGL вам не поможет.
Shadow Mapping
Трассировка лучей также может быть приемлемым вариантом, если в вашем приложении очень мало источников света и объектов.
Гораздо лучшая альтернатива приведению лучей в WebGL называется теневым отображением. Это дает тот же эффект, что и наложение лучей, но использует другой подход. Shadow Mapping не решит все ваши проблемы, но WebGL для этого частично оптимизирован. Вы можете думать об этом как о взломе, но теневое отображение используется в реальных ПК и консольных приложениях.
Так что же вы спросите?
Вы должны понимать, как WebGL отображает свои сцены, чтобы ответить на этот вопрос. WebGL помещает все вершины в вершинный шейдер, который вычисляет окончательные координаты для каждой вершины после применения преобразований. Затем, чтобы сэкономить время, WebGL отбрасывает вершины, которые скрыты за другими объектами, и рисует только основные объекты. Если вы помните, как работает приведение лучей, оно просто направляет лучи света на видимые объекты. Таким образом, мы устанавливаем «камеру» нашей сцены в координаты источника света и направляем ее в направлении, в котором мы хотим, чтобы свет был направлен. Затем WebGL автоматически удаляет все вершины, которые не видны свету. Затем мы можем сохранить эти данные и использовать их при рендеринге сцены, чтобы узнать, какие из вершин подсвечены.
Эта техника звучит хорошо на бумаге, но у нее есть несколько недостатков:
- WebGL не позволяет вам получить доступ к буферу глубины; Вы должны проявить творческий подход к фрагментному шейдеру при попытке сохранить эти данные.
- Даже если вы сохраните все данные, вам все равно придется сопоставить их с вершинами, прежде чем они перейдут в массив вершин при рендеринге вашей сцены. Это требует дополнительного процессорного времени.
Все эти методы требуют значительных усилий с WebGL. Но я покажу вам очень простой метод создания рассеянного света, чтобы придать вашим объектам немного индивидуальности. Я бы не назвал это реалистичным светом, но он дает определение ваших объектов. Этот метод использует матрицу нормалей объекта для расчета угла света по сравнению с поверхностью объекта. Это быстро, эффективно и не требует взлома с WebGL. Давайте начнем.
Добавление света
Начнем с обновления шейдеров для включения освещения. Нам нужно добавить логическое значение, которое определяет, должен ли объект быть освещен. Затем нам нужна вершина фактических нормалей и преобразовать ее так, чтобы она выровнялась с моделью. Наконец, нам нужно создать переменную, чтобы передать окончательный результат фрагментному шейдеру. Это новый вершинный шейдер:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
<script id=»VertexShader» type=»x-shader/x-vertex»>
attribute highp vec3 VertexPosition;
attribute highp vec2 TextureCoord;
attribute highp vec3 NormalVertex;
uniform highp mat4 TransformationMatrix;
uniform highp mat4 PerspectiveMatrix;
uniform highp mxat4 NormalTransformation;
uniform bool UseLights;
varying highp vec2 vTextureCoord;
varying highp vec3 vLightLevel;
void main(void) {
gl_Position = PerspectiveMatrix * TransformationMatrix * vec4(VertexPosition, 1.0);
vTextureCoord = TextureCoord;
if (UseLights) {
highp vec3 LightColor = vec3(0.15, 0.15, 0.15);
highp vec3 LightDirection = vec3(0.5, 0.5, 4);
highp vec4 Normal = NormalTransformation * vec4(VertexNormal, 1.0);
highp float FinalDirection = max(dot(Normal.xyz, LightDirection), 0.0);
vLightLevel = (FinalDirection * LightColor);
} else {
vLightLevel = vec3(1.0, 1.0, 1.0);
}
}
</script>
|
Если мы не используем источники света, тогда мы просто передаем пустую вершину фрагментному шейдеру, и ее цвет остается прежним. Когда источники света включены, мы вычисляем угол между направлением света и поверхностью объекта, используя функцию точки на нормали, и умножаем результат на цвет источника света в качестве маски для наложения на объект.
Картина нормалей поверхности Олега Александрова.
Это работает, потому что нормали уже перпендикулярны поверхности объекта, а функция точки дает нам число, основанное на угле света к нормали. Если нормаль и свет почти параллельны, то функция точки возвращает положительное число, означающее, что свет направлен на поверхность. Когда нормаль и свет перпендикулярны, поверхность параллельна свету, и функция возвращает ноль. Что-либо выше 90 градусов между светом и нормалью приводит к отрицательному числу, но мы отфильтровываем это с помощью функции «max zero».
Теперь позвольте мне показать вам фрагментный шейдер:
01
02
03
04
05
06
07
08
09
10
11
|
<script id=»FragmentShader» type=»x-shader/x-fragment»>
varying highp vec2 vTextureCoord;
varying highp vec3 vLightLevel;
uniform sampler2D uSampler;
void main(void) {
highp vec4 texelColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
gl_FragColor = vec4(texelColor.rgb * vLightLevel, texelColor.a);
}
</script>
|
Этот шейдер почти такой же, как и в предыдущих частях серии. Разница лишь в том, что мы умножаем цвет текстуры на уровень освещенности. Это осветляет или затемняет различные части объекта, придавая ему некоторую глубину.
Это все для шейдеров, теперь давайте перейдем к файлу WebGL.js
и WebGL.js
два наших класса.
Обновление нашего фреймворка
Давайте начнем с класса GLObject
. Нам нужно добавить переменную для массива нормали. Вот как должна выглядеть верхняя часть вашего GLObject
:
01
02
03
04
05
06
07
08
09
10
|
function GLObject(VertexArr, TriangleArr, TextureArr, ImageSrc, NormalsArr) {
this.Pos = { X : 0, Y : 0, Z : 0};
this.Scale = { X : 1.0, Y : 1.0, Z : 1.0};
this.Rotation = { X : 0, Y : 0, Z : 0};
this.Vertices = VertexArr;
//Array to hold the normals data
this.Normals = NormalsArr;
//The Rest of GLObject continues here
|
Этот код довольно прост. Теперь вернемся к HTML-файлу и добавим массив нормали к нашему объекту.
В функцию Ready()
мы загружаем нашу 3D-модель, мы должны добавить параметр для массива нормали. Пустой массив означает, что модель не содержит никаких данных нормали, и нам придется рисовать объект без света. Если массив нормали содержит данные, мы просто передадим их в объект GLObject
.
Нам также необходимо обновить класс WebGL
. Нам нужно связать переменные с шейдерами сразу после загрузки шейдеров. Давайте добавим вершину нормали; теперь ваш код должен выглядеть так:
01
02
03
04
05
06
07
08
09
10
11
|
//Link Vertex Position Attribute from Shader
this.VertexPosition = this.GL.getAttribLocation(this.ShaderProgram, «VertexPosition»);
this.GL.enableVertexAttribArray(this.VertexPosition);
//Link Texture Coordinate Attribute from Shader
this.VertexTexture = this.GL.getAttribLocation(this.ShaderProgram, «TextureCoord»);
this.GL.enableVertexAttribArray(this.VertexTexture);
//This is the new Normals array attribute
this.VertexNormal = this.GL.getAttribLocation(this.ShaderProgram, «VertexNormal»);
this.GL.enableVertexAttribArray(this.VertexNormal);
|
Далее, давайте обновим PrepareModel()
и добавим некоторый код для буферизации данных нормали, когда они будут доступны. Добавьте новый код прямо перед оператором Model.Ready
внизу:
1
2
3
4
5
6
7
8
9
|
if (false !== Model.Normals) {
Buffer = this.GL.createBuffer();
this.GL.bindBuffer(this.GL.ARRAY_BUFFER, Buffer);
this.GL.bufferData(this.GL.ARRAY_BUFFER, new Float32Array(Model.Normals), this.GL.STATIC_DRAW);
Model.Normals = Buffer;
}
Model.Ready = true;
|
И последнее, но не менее важное: обновите фактическую функцию Draw
чтобы включить все эти изменения. Здесь есть пара изменений, так что терпите меня. Я собираюсь пройти шаг за шагом через всю функцию:
01
02
03
04
05
06
07
08
09
10
|
this.Draw = function(Model) {
if(Model.Image.ReadyState == true && Model.Ready == false) {
this.PrepareModel(Model);
}
if (Model.Ready) {
this.GL.bindBuffer(this.GL.ARRAY_BUFFER, Model.Vertices);
this.GL.vertexAttribPointer(this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0);
this.GL.bindBuffer(this.GL.ARRAY_BUFFER, Model.TextureMap);
this.GL.vertexAttribPointer(this.VertexTexture, 2, this.GL.FLOAT, false, 0, 0);
|
До здесь так же, как и раньше. Теперь перейдем к части нормалей:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
//Check For Normals
if (false !== Model.Normals) {
//Connect The normals buffer to the Shader
this.GL.bindBuffer(this.GL.ARRAY_BUFFER, Model.Normals);
this.GL.vertexAttribPointer(this.VertexNormal, 3, this.GL.FLOAT, false, 0, 0);
//Tell The shader to use lighting
var UseLights = this.GL.getUniformLocation(this.ShaderProgram, «UseLights»);
this.GL.uniform1i(UseLights, true);
} else {
//Even if our object has no normals data we still have to pass something
//So I pass in the Vertices instead
this.GL.bindBuffer(this.GL.ARRAY_BUFFER, Model.Vertices);
this.GL.vertexAttribPointer(this.VertexNormal, 3, this.GL.FLOAT, false, 0, 0);
//Tell The shader to use lighting
var UseLights = this.GL.getUniformLocation(this.ShaderProgram, «UseLights»);
this.GL.uniform1i(UseLights, false);
}
|
Мы проверяем, есть ли в модели данные нормалей. Если это так, он соединяет буфер и устанавливает логическое значение. Если нет, шейдеру все еще нужны какие-то данные, иначе он выдаст ошибку. Поэтому вместо этого я передал буфер вершин и установил для логического значения UseLight
значение false
. Вы можете обойти это, используя несколько шейдеров, но я подумал, что это будет проще для того, что мы пытаемся сделать.
1
2
3
4
5
|
this.GL.bindBuffer(this.GL.ELEMENT_ARRAY_BUFFER, Model.Triangles);
//Generate The Perspective Matrix
var PerspectiveMatrix = MakePerspective(45, this.AspectRatio, 1, 1000.0);
var TransformMatrix = Model.GetTransforms();
|
Опять же, эта часть функции остается прежней.
1
|
var NormalsMatrix = MatrixTranspose(InverseMatrix(TransformMatrix));
|
Здесь мы вычисляем матрицу преобразования нормалей. Я расскажу о MatrixTranspose()
и InverseMatrix()
через минуту. Чтобы вычислить матрицу преобразования для массива нормалей, вы должны транспонировать обратную матрицу регулярной матрицы преобразования объекта. Подробнее об этом позже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//Set slot 0 as the active Texture
this.GL.activeTexture(this.GL.TEXTURE0);
//Load in the Texture To Memory
this.GL.bindTexture(this.GL.TEXTURE_2D, Model.Image);
//Update The Texture Sampler in the fragment shader to use slot 0
this.GL.uniform1i(this.GL.getUniformLocation(this.ShaderProgram, «uSampler»), 0);
//Set The Perspective and Transformation Matrices
var pmatrix = this.GL.getUniformLocation(this.ShaderProgram, «PerspectiveMatrix»);
this.GL.uniformMatrix4fv(pmatrix, false, new Float32Array(PerspectiveMatrix));
var tmatrix = this.GL.getUniformLocation(this.ShaderProgram, «TransformationMatrix»);
this.GL.uniformMatrix4fv(tmatrix, false, new Float32Array(TransformMatrix));
var nmatrix = this.GL.getUniformLocation(this.ShaderProgram, «NormalTransformation»);
this.GL.uniformMatrix4fv(nmatrix, false, new Float32Array(NormalsMatrix));
//Draw The Triangles
this.GL.drawElements(this.GL.TRIANGLES, Model.TriangleCount, this.GL.UNSIGNED_SHORT, 0);
}
};
|
Вы можете легко просмотреть источник любого приложения WebGL, чтобы узнать больше.
Это остальная часть функции Draw()
. Это почти то же самое, что и раньше, но есть добавленный код, который соединяет матрицу нормалей с шейдерами. Теперь давайте вернемся к этим двум функциям, которые я использовал для получения матрицы преобразования нормалей.
Функция InverseMatrix()
принимает матрицу и возвращает ее обратную матрицу. Обратная матрица — это матрица, которая при умножении на исходную матрицу возвращает единичную матрицу. Давайте рассмотрим базовый пример алгебры, чтобы прояснить это. Инверсия числа 4 составляет 1/4, потому что когда 1/4 x 4 = 1
. «Один» эквивалент в матрицах является единичной матрицей. Следовательно, InverseMatrix()
возвращает единичную матрицу для аргумента. Вот эта функция:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
function InverseMatrix(A) {
var s0 = A[0] * A[5] — A[4] * A[1];
var s1 = A[0] * A[6] — A[4] * A[2];
var s2 = A[0] * A[7] — A[4] * A[3];
var s3 = A[1] * A[6] — A[5] * A[2];
var s4 = A[1] * A[7] — A[5] * A[3];
var s5 = A[2] * A[7] — A[6] * A[3];
var c5 = A[10] * A[15] — A[14] * A[11];
var c4 = A[9] * A[15] — A[13] * A[11];
var c3 = A[9] * A[14] — A[13] * A[10];
var c2 = A[8] * A[15] — A[12] * A[11];
var c1 = A[8] * A[14] — A[12] * A[10];
var c0 = A[8] * A[13] — A[12] * A[9];
var invdet = 1.0 / (s0 * c5 — s1 * c4 + s2 * c3 + s3 * c2 — s4 * c1 + s5 * c0);
var B = [];
B[0] = ( A[5] * c5 — A[6] * c4 + A[7] * c3) * invdet;
B[1] = (-A[1] * c5 + A[2] * c4 — A[3] * c3) * invdet;
B[2] = ( A[13] * s5 — A[14] * s4 + A[15] * s3) * invdet;
B[3] = (-A[9] * s5 + A[10] * s4 — A[11] * s3) * invdet;
B[4] = (-A[4] * c5 + A[6] * c2 — A[7] * c1) * invdet;
B[5] = ( A[0] * c5 — A[2] * c2 + A[3] * c1) * invdet;
B[6] = (-A[12] * s5 + A[14] * s2 — A[15] * s1) * invdet;
B[7] = ( A[8] * s5 — A[10] * s2 + A[11] * s1) * invdet;
B[8] = ( A[4] * c4 — A[5] * c2 + A[7] * c0) * invdet;
B[9] = (-A[0] * c4 + A[1] * c2 — A[3] * c0) * invdet;
B[10] = ( A[12] * s4 — A[13] * s2 + A[15] * s0) * invdet;
B[11] = (-A[8] * s4 + A[9] * s2 — A[11] * s0) * invdet;
B[12] = (-A[4] * c3 + A[5] * c1 — A[6] * c0) * invdet;
B[13] = ( A[0] * c3 — A[1] * c1 + A[2] * c0) * invdet;
B[14] = (-A[12] * s3 + A[13] * s1 — A[14] * s0) * invdet;
B[15] = ( A[8] * s3 — A[9] * s1 + A[10] * s0) * invdet;
return B;
}
|
Эта функция довольно сложна, и, честно говоря, я не до конца понимаю, почему математика работает. Но я уже объяснил суть этого выше. Я не придумал эту функцию; это было написано в ActionScript Робином Хиллиардом.
Следующая функция, MatrixTranspose()
, намного проще для понимания. Возвращает «транспонированную» версию своей входной матрицы. Короче говоря, он просто вращает матрицу на своей стороне. Вот код:
1
2
3
4
5
6
7
8
|
function MatrixTranspose(A) {
return [
A[0], A[4], A[8], A[12],
A[1], A[5], A[9], A[13],
A[2], A[6], A[10], A[14],
A[3], A[7], A[11], A[15]
];
}
|
Вместо горизонтальных рядов (т.е. A [0], A [1], A [2] …) эта функция идет вниз по вертикали (A [0], A [4], A [8] …) ,
Можно добавить эти две функции в файл WebGL.js
, и любая модель, содержащая данные нормалей, должна быть затенена. Вы можете поиграть с направлением света и цветом в вершинном шейдере, чтобы получить различные эффекты.
И еще одна тема, которую я хотел бы затронуть: добавление 2D-контента на нашу сцену. Добавление 2D-компонентов на 3D-сцену может иметь много преимуществ. Например, его можно использовать для отображения информации о координатах, мини-карты, инструкций для вашего приложения и продолжения списка. Этот процесс не так прост, как вы думаете, поэтому давайте проверим его.
2D VS 2.5D
HTML не позволит вам использовать API WebGL и 2D API с одного и того же холста.
Вы можете подумать: «Почему бы просто не использовать встроенный в HTML5 2D API холст?» Проблема в том, что HTML не позволит вам использовать API WebGL и 2D API с одного и того же холста. Как только вы назначите контекст холста для WebGL, вы не сможете использовать его с 2D API. HTML5 просто возвращает null
когда вы пытаетесь получить 2D-контекст. Так как же тогда обойти это? Хорошо, я дам вам два варианта.
2.5D
2.5D для тех, кто не знает, — это когда вы помещаете 2D-объекты (объекты без глубины) в 3D-сцену. Добавление текста в сцену является примером 2.5D. Вы можете взять текст с картинки и применить его как текстуру к трехмерной плоскости, или вы можете получить 3D-модель для текста и отобразить его на экране.
Преимущества этого подхода состоят в том, что вам не нужны два полотна, и рисование будет быстрее, если вы будете использовать только простые фигуры в своем приложении.
Но для того, чтобы делать такие вещи, как текст, вам нужно либо иметь изображения всего, что вы хотите написать, либо 3D-модель для каждой буквы (на мой взгляд, немного выше).
2D
Альтернатива — создать второй холст и наложить его поверх трехмерного холста. Я предпочитаю этот подход, потому что он кажется лучше подготовлен для рисования 2D-контента. Я не собираюсь начинать создавать новый 2D каркас, но давайте просто создадим простой пример, в котором мы отображаем координаты модели вместе с ее текущим вращением. Давайте добавим второй холст в файл HTML сразу после холста WebGL. Вот новый холст вместе с текущим:
1
2
3
4
5
6
7
|
<canvas id=»GLCanvas» width=»600″ height=»400″ style=»position:absolute; top:0px; left:0px;»>
Your Browser Doesn’t Support HTML5’s Canvas.
</canvas>
<canvas id=»2DCanvas» width=»600″ height=»400″ style=»position:absolute; top:0px; left:0px;»>
Your Browser Doesn’t Support HTML5’s Canvas.
</canvas>
|
Я также добавил немного встроенного CSS, чтобы наложить второй холст поверх первого. Следующим шагом является создание переменной для 2D-холста и получение его контекста. Я собираюсь сделать это в функции Ready()
. Ваш обновленный код должен выглядеть примерно так:
01
02
03
04
05
06
07
08
09
10
|
var GL;
var Building;
var Canvas2D;
function Ready(){
//Gl Declaration and Load model function Here
Canvas2D = document.getElementById(«2DCanvas»).getContext(«2d»);
Canvas2D.fillStyle=»#000″;
}
|
Вверху видно, что я добавил глобальную переменную для 2D-холста. Затем я добавил две строки внизу функции Ready()
. Первая новая строка получает 2D-контекст, а вторая новая строка устанавливает черный цвет.
Последний шаг — нарисовать текст внутри функции Update()
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
function Update(){
Building.Rotation.Y += 0.3
//Clear the Canvas from the previous draw
Canvas2D.clearRect(0, 0, 600, 400);
//Title Text
Canvas2D.font=»25px sans-serif»;
Canvas2D.fillText(«Building» , 20, 30);
//Object’s Properties
Canvas2D.font=»16px sans-serif»;
Canvas2D.fillText(«X : » + Building.Pos.X , 20, 55);
Canvas2D.fillText(«Y : » + Building.Pos.Y , 20, 75);
Canvas2D.fillText(«Z : » + Building.Pos.Z , 20, 95);
Canvas2D.fillText(«Rotation : » + Math.floor(Building.Rotation.Y) , 20, 115);
GL.GL.clear(16384 | 256);
GL.Draw(Building);
}
|
Мы начинаем с поворота модели вокруг своей оси Y, а затем очищаем 2D-холст от любого предыдущего содержимого. Далее мы устанавливаем размер шрифта и рисуем текст для каждой оси. Метод fillText()
принимает три параметра: текст для рисования, координату x и координату y.
Простота говорит сама за себя. Это может быть немного излишним, чтобы нарисовать простой текст; Вы могли бы просто написать текст в позиционированном элементе <div/>
или <p/>
. Но если вы делаете что-то вроде рисования фигур, спрайтов, индикатора здоровья и т. Д., То это, вероятно, ваш лучший вариант.
Последние мысли
В рамках трех последних уроков мы создали довольно хороший, хотя и базовый, 3D движок. Несмотря на свою примитивную природу, она дает вам прочную основу для работы. Двигаясь вперед, я предлагаю взглянуть на другие фреймворки, такие как three.js или glge, чтобы понять, что возможно. Кроме того, WebGL работает в браузере, и вы можете легко просмотреть источник любого приложения WebGL, чтобы узнать больше.
Я надеюсь, что вам понравился этот учебник, и, как всегда, оставляйте свои комментарии и вопросы в разделе комментариев ниже.