Pull to refresh

Dart + WebGL так ли вкусны печеньки на тёмной стороне

Reading time5 min
Views20K

История развития веб-приложений очень сильно напоминает жизнь Энакена Скайуокера. Лет 15 назад они были слабыми и неуклюжими. Но вот прошло время, тестостерон бьёт в голову и наш “Энакен” падает в раскалённую лаву требований к качеству и функциональности браузерных программ. Своей единственной уцелевшей джаваскриптой он подгребает под себя землю. Здесь внимательный читатель заметит, что так мы далеко не уползём. Неожиданный, а на самом деле вполне закономерный сюжетный поворот и на сцене появляется спаситель Dart. Давайте попробуем встать на его сторону.

Почему Dart, просто потому, что он типизированный и у него си-подобный синтаксис. К тому же Google разрабатывает VirtualMachine для Dart, что в перспективе может дать значительное преимущество в производительности этого языка.

Почему WebGL, потому, что эта технология, как и Dart, находится на передовой, и 3D в браузере, на мой взгляд, это очень интересная тема.

Ожидать от браузерной технологии качества настольных игр не имеет смысла. WebGL это OpenGL 2.0, а значит у нас в руках только вершинные и фрагментные шейдеры, а это значит, что качество картинки будет отставать от настольных игр примерно на 5 лет. Для мастеров своего дела это не преграда, танки онлайн это доказали, а в руках создателей первых танков даже этого не было.

Для начала нам потребуется IDE Dart editor. Его можно скачать с официального сайта www.dartlang.org
В основе этой IDE лежит, многим привычный, Eclipse, поэтому, желательно, скачивать версию той битности, какой у вас установлена Java. Иначе не удастся запустить.

Создадим новый проект: File->New application.
import 'dart:html';

void main() {
}

Это минимальная комплектация приложения на Dart. Остальное можно смело удалять.
Чтобы получить в свои руки элемент со страницы с определённым id, надо вызвать функцию querySelector вот так:
var someElement = querySelector(“#someid”);
Взглянем на минимальный html файл, который мы будем использовать для экспериментов:
<!DOCTYPE html>
<html>
<body>
<!-- Выделяем место для сообщений из кода-->
    <p id="status"></p>
<!-- Создаём холст для рисования-->
   <canvas id="canvas3d" width="800" height="600"></canvas>
<!-- Подключаем рабочий код для страницы -->
   <script src="Simple.dart" type="application/dart"></script>
   <script src="packages/browser/dart.js"></script>
 </body>
</html>


Вот так будет выглядеть код на Dart выводящий короткое сообщение на страницу:
import 'dart:html';

void main() {
    var status = querySelector('#status');
    status.innerHtml = '<p>Добро пожаловать в мир Dart.</p>';
}

Проверить работоспособность можно нажав кнопку запуска:

А скомпилировать для публикации в веб — нажав ПКМ на dart файл в окне с деревом файлов проекта и нажав “Run as JavaScript”:

Для вывода 3D графики нам надо получить 3D контекст от холста на котором мы будем рисовать.
//Получаем холст
canvas = document.querySelector('#canvas3d');
//Берём webGl контекст для вывода графики
gl = (canvas.getContext("webgl")!=null)?canvas.getContext("webgl") :  
canvas.getContext("experimental-webgl");

Если после этого запроса gl равен null, значит браузер не поддерживает webGl.
if (gl == null) 
{
    status.innerHtml = '<p>Простите, ваш браузер не поддерживает WebGl</p>';
}

После получения контекста, надо инициализировать его, передав в качестве параметров сдвиг по горизонтали, сдвиг по вертикали, ширину и высоту рамки отображения:
gl.viewport(0, 0, canvas.width, canvas.height);

Чтобы отобразить хоть что-то на холсте, надо загрузить два шейдера, один вершинный и один фрагментный(он же пиксельный).
//Пиксельный шейдер
String vsSource = """
    attribute vec3 aPosition;
    void main() 
    {
        gl_Position = vec4(aPosition, 1);
    }""";
   
// Фрагментный шейдер
String fsSource = """
    precision mediump float;
    uniform vec4 uColor;
    void main() {
        gl_FragColor = uColor;
    }""";
//Шейдеры написаны, теперь их надо скомпилировать и загрузить в контекст.
//Компилируем
WebGLShader vs = gl.createShader(WebGLRenderingContext.VERTEX_SHADER);
gl.shaderSource(vs, vsSource);
gl.compileShader(vs);
WebGLShader fs = gl.createShader(WebGLRenderingContext.FRAGMENT_SHADER);
gl.shaderSource(fs, fsSource);
gl.compileShader(fs);
   
// Загружаем шейдеры в GPU
WebGLProgram p = gl.createProgram();
gl.attachShader(p, vs);
gl.attachShader(p, fs);
gl.linkProgram(p);
gl.useProgram(p);
//Проверяем всё ли удачно скомпилировалось.
if (!gl.getShaderParameter(vs, WebGLRenderingContext.COMPILE_STATUS)) { 
    print(gl.getShaderInfoLog(vs));
}
   
if (!gl.getShaderParameter(fs, WebGLRenderingContext.COMPILE_STATUS)) { 
    print(gl.getShaderInfoLog(fs));
}
   
if (!gl.getProgramParameter(p, WebGLRenderingContext.LINK_STATUS)) { 
    print(gl.getProgramInfoLog(p));
}

Команда print выводит информацию в дебажную консоль используемой IDE. Если что-то пошло не так, то о причинах можно будет прочитать.
Теперь надо загрузить вершинный буфер в GPU:
//Задаём координаты в трехмерном пространстве
Float32List vertices = new Float32List.fromList([ -0.4, 0.4, 1.0,
                   0.4,  0.4, 1.0,
                  -0.4, -0.4, 1.0]);
//Создаём буфер и загружаем в него координаты
gl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, gl.createBuffer());
gl.bufferDataTyped(WebGLRenderingContext.ARRAY_BUFFER,
                      vertices, WebGLRenderingContext.STATIC_DRAW);
//Указываем сколько вершин нарисовано
int numItems = 3;
// Устанавливаем позицию, которая будет передана в вершинный шейдер
int aPosition = gl.getAttribLocation(program, "aPosition");
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 3, WebGLRenderingContext.FLOAT, false, 0, 0);

//Очищаем холст заливая его новым цветом(RedGreenBlueAlpha)
gl.clearColor(0.9, 0.9, 0.9, 1);
gl.clear(WebGLRenderingContext.COLOR_BUFFER_BIT);

// Устанавливаем цвет, который будет передан фрагментному шейдеру
WebGLUniformLocation uColor = gl.getUniformLocation(program, "uColor");
// Как и для очистки холста он задаётся в формате RGBA
gl.uniform4fv(uColor, new Float32List.fromList([0.5, 0.9, 0.9, 1.0]));
   

Единственное, что нам осталось сделать, это дать команду отрисовки:
gl.drawArrays(WebGLRenderingContext.TRIANGLES, 0, numItems);

Теперь можно запустить проект и посмотреть на результат:

Скачать целый исходный файл dart вы можете здесь: dl.dropbox.com/u/41059365/Simple.dart
Целью данной статьи было показать что Dart может то же, что и JS, но удобнее. Статическая типизация и автодополнение сильно сокращают время разработки, позволяя не держать в голове ненужную информацию. Об этих и других плюшках хорошо рассказывают в видео, ссылка в первом комментарии.

Наш ситх всё еще молод, но он быстро меняется и взрослеет. WebGl крепнет вместе с ним, пусть и несколько медленее. Сравнивая Dart и JS хочется выразить глубочайшее почтение к тем, кто способен написать действительно сложные проекты на втором из них. Количество сторонних библиотек для Dart в несоизмеримо меньшем числе чем для JS, но язык и инструменты прилагающиеся к нему настолько хороши, что за новыми проектами не заржавеет. А пошаговая отладка кода, это сокровище для начинающих программистов. Печеньки на стороне Dart и вскоре он станет властелином всего интернета.
Tags:
Hubs:
Total votes 44: ↑34 and ↓10+24
Comments67

Articles