Эта статья является продолжением предыдущих статей WebGL. Если вы их не читали, я предлагаю начать там и работать в обратном направлении.
Частый вопрос: “как рисовать текст в WebGL”. Первое, что нужно спросить себя - какая у вас цель рисования текста. Вы в браузере, браузер отображает текст. Поэтому ваш первый ответ должен быть - использовать HTML для отображения текста.
Давайте сначала сделаем самый простой пример: Вы просто хотите нарисовать какой-то текст поверх вашего WebGL. Мы можем назвать это текстовым наложением. По сути, это текст, который остается в том же положении.
Простой способ - создать HTML элемент или элементы и использовать CSS, чтобы они перекрывались.
Например: Сначала создайте контейнер и поместите в него и canvas, и HTML для наложения.
<div class="container">
<canvas id="canvas" width="400" height="300"></canvas>
<div id="overlay">
<div>Время: <span id="time"></span></div>
<div>Угол: <span id="angle"></span></div>
</div>
</div>
Затем настройте CSS так, чтобы canvas и HTML перекрывались
.container {
position: relative;
}
#overlay {
position: absolute;
left: 10px;
top: 10px;
}
Теперь найдите эти элементы во время инициализации и создайте или найдите области, которые хотите изменить.
// ищем элементы, которые хотим изменить
var timeElement = document.querySelector("#time");
var angleElement = document.querySelector("#angle");
// Создаем текстовые узлы, чтобы сэкономить время браузера
// и избежать выделений памяти.
var timeNode = document.createTextNode("");
var angleNode = document.createTextNode("");
// Добавляем эти текстовые узлы туда, где они должны быть
timeElement.appendChild(timeNode);
angleElement.appendChild(angleNode);
Наконец, обновляем узлы при рендеринге
function drawScene(time) {
var now = time * 0.001; // конвертируем в секунды
...
// конвертируем вращение из радиан в градусы
var angle = radToDeg(rotation[1]);
// показываем только 0 - 360
angle = angle % 360;
// устанавливаем узлы
angleNode.nodeValue = angle.toFixed(0); // без десятичных знаков
timeNode.nodeValue = now.toFixed(2); // 2 десятичных знака
И вот этот пример
Обратите внимание, как я поместил span внутри div специально для частей, которые хотел изменить. Я предполагаю, что это быстрее, чем просто использовать div без span и говорить что-то вроде
timeNode.nodeValue = "Время " + now.toFixed(2);
Также я использую текстовые узлы, вызывая node = document.createTextNode()
и позже node.nodeValue = someMsg
.
Я также мог бы использовать someElement.innerHTML = someHTML
. Это было бы более гибко, потому что вы могли бы
вставлять произвольные HTML строки, хотя это может быть немного медленнее, поскольку браузер должен создавать
и уничтожать узлы каждый раз, когда вы его устанавливаете. Что лучше - решать вам.
Важный момент, который нужно усвоить из техники наложения, заключается в том, что WebGL работает в браузере. Помните использовать функции браузера, когда это уместно. Многие программисты OpenGL привыкли к тому, что им приходится рендерить каждую часть своего приложения на 100% с нуля, но поскольку WebGL работает в браузере, у него уже есть множество функций. Используйте их. Это имеет много преимуществ. Например, вы можете использовать CSS стили для легкого придания этому наложению интересного стиля.
Например, вот тот же пример, но с добавлением стиля. Фон закруглен, буквы имеют свечение вокруг них. Есть красная граница. Вы получаете все это практически бесплатно, используя HTML.
Следующая наиболее распространенная вещь, которую хочется сделать - это позиционировать какой-то текст относительно того, что вы рендерите. Мы можем сделать это и в HTML.
В этом случае мы снова создадим контейнер с canvas и другим контейнером для нашего движущегося HTML
<div class="container">
<canvas id="canvas" width="400" height="300"></canvas>
<div id="divcontainer"></div>
</div>
И настроим CSS
.container {
position: relative;
overflow: none;
}
#divcontainer {
position: absolute;
left: 0px;
top: 0px;
width: 400px;
height: 300px;
z-index: 10;
overflow: hidden;
}
.floating-div {
position: absolute;
}
Часть position: absolute;
делает так, чтобы #divcontainer
позиционировался в абсолютных терминах относительно
первого родителя с другим стилем position: relative
или position: absolute
. В данном случае
это контейнер, в котором находятся и canvas, и #divcontainer
.
left: 0px; top: 0px
делает так, чтобы #divcontainer
выравнивался со всем. z-index: 10
делает
его плавающим над canvas. И overflow: hidden
делает так, чтобы его дочерние элементы обрезались.
Наконец, .floating-div
будет использоваться для позиционируемого div, который мы создаем.
Итак, теперь нам нужно найти divcontainer, создать div и добавить его.
// ищем divcontainer
var divContainerElement = document.querySelector("#divcontainer");
// создаем div
var div = document.createElement("div");
// назначаем ему CSS класс
div.className = "floating-div";
// создаем текстовый узел для его содержимого
var textNode = document.createTextNode("");
div.appendChild(textNode);
// добавляем его в divcontainer
divContainerElement.appendChild(div);
Теперь мы можем позиционировать div, устанавливая его стиль.
div.style.left = Math.floor(x) + "px";
div.style.top = Math.floor(y) + "px";
textNode.nodeValue = now.toFixed(2);
Вот пример, где мы просто заставляем div подпрыгивать.
Итак, следующий шаг - мы хотим разместить его относительно чего-то в 3D сцене. Как мы это делаем? Мы делаем это точно так же, как мы просили GPU сделать это, когда мы рассматривали перспективную проекцию.
До этого примера мы научились использовать матрицы, как их умножать, и как применять матрицу проекции для преобразования их в пространство отсечения. Мы передаем все это в наш шейдер, и он умножает вершины в локальном пространстве и преобразует их в пространство отсечения. Мы можем сделать всю математику сами в JavaScript. Затем мы можем умножить пространство отсечения (-1 до +1) в пиксели и использовать это для позиционирования div.
gl.drawArrays(...);
// Мы только что закончили вычислять матрицу для рисования нашей
// F в 3D.
// выбираем точку в локальном пространстве 'F'.
// X Y Z W
var point = [100, 0, 0, 1]; // это передний верхний правый угол
// вычисляем позицию в пространстве отсечения
// используя матрицу, которую мы вычислили для F
var clipspace = m4.transformVector(matrix, point);
// делим X и Y на W точно так же, как это делает GPU.
clipspace[0] /= clipspace[3];
clipspace[1] /= clipspace[3];
// конвертируем из пространства отсечения в пиксели
var pixelX = (clipspace[0] * 0.5 + 0.5) * gl.canvas.width;
var pixelY = (clipspace[1] * -0.5 + 0.5) * gl.canvas.height;
// позиционируем div
div.style.left = Math.floor(pixelX) + "px";
div.style.top = Math.floor(pixelY) + "px";
textNode.nodeValue = now.toFixed(2);
И вуаля, верхний левый угол нашего div идеально выровнен с верхним правым передним углом F.
Конечно, если вы хотите больше текста, создайте больше div.
Вы можете посмотреть исходный код последнего примера, чтобы увидеть детали. Один важный момент - я просто предполагаю, что создание, добавление и удаление HTML элементов из DOM медленно, поэтому пример выше создает их и держит их рядом. Он скрывает неиспользуемые, а не удаляет их из DOM. Вам нужно будет профилировать, чтобы знать, быстрее ли это. Это был просто метод, который я выбрал.
Надеюсь, понятно, как использовать HTML для текста. Далее мы покроем использование Canvas 2D для текста.