Как стать автором
Обновить

No Canvas

Время на прочтение7 мин
Количество просмотров2.6K
Возможно, это лучше перенести в «Я пиарюсь» или «JavaScript» (принимаются предложения). А может, стоит оставить всё как есть. Тем не менее...

3D с z-buffer-ом, субпиксельной точностью и освещением по Гуро на javascript? Да кто угодно сможет это сделать, используя canvas!

Можно долго и вкусно описывать преимущества канваса, но статья не про это; не менее интересно
посмотреть, чем же канвас плох.


Так чем же?


  • Он ни в какую не работает в Обозревателе Интернета одной большой известной корпорации: эмулируя канвас через VML мы отрезаем самые вкусные пиксельные манипуцляции, а эмуяция пиксельных вкусняшек на flash безбожно тормозит из-за того, что где-то в недрах ExternalInterface разработчики из Adobe вставили пару функций sleep :)
  • Только Google Chrome (aka Chromium) может обеспечить достаточную скорость исполнения джаваскрипта. Во всех остальных браузерах вкусный и сексуальный эффект рискует превратиться в слайд-шоу.
  • И, наконец, самое главное — это неспортивно! Я уверен, что пока есть люди пишущие сапёров на bat файлах, тетрисы на sed и боярские диалекты C++, программирование ради самого процесса программирования будет интересовать массы :)

Я ненормальный псих завёл блог, в котором собираюсь регулярно писать о создании различных эффектов и игр на javascript, не использующих канвас:
  • раз в две недели я буду писать о каком-либо новом эффекте;
  • раз в два месяца я буду рассказывать делать playable demo какой-нибудь игры (как водится, не использующую canvas);
  • и, наконец, раз в полгода я буду делать по игре (ну, по крайней мере, я буду очень стараться не сорвать сроки)

Небольшая еретическая статья на затравку — как сделать 3D + z-buffer + subpixel + gouraud shading используя канвас


Шаг 0

Для начала, необходимо позаботится о пользователях IE, предложив им поддержку тэга canvas в виде Chrome Frame: прописываем meta, подключаем гуглоскрипт, создаём блок no-canvas и правим стили для гугловского iframe (по умолчанию он появляется в центре страницы)
<head><br> <meta http-equiv="X-UA-Compatible" content="chrome=1" /><br> <style type="text/css" media="screen"><br>.chrome-install {<br> position: relative;<br> margin: 0;<br> padding: 0;<br> top: 0;<br> left: 0;<br>}<br> </style><br> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script><br></head><br><body onload="main()"><br> <div id="no-canvas" style="display:none;"><br>  <h2>&lt;canvas&gt; support required.</h2><br>  <div id="chrome-install-placeholder"></div><br> </div><br> <div id="canvas-enabled"><br>  <canvas id="canvas" width="384" height="384"></canvas><br> </div><br></body><br><br>* This source code was highlighted with Source Code Highlighter.

Функция main (init вызывается по таймауту, потому что иначе хром (dev версия) иногда не прорисовывает background у body до конца):
function main()<br>{<br>  canvas = document.getElementById('canvas');<br><br>  if (typeof(canvas.getContext) == 'function')<br>  {<br>    ctx = canvas.getContext('2d');<br>    setTimeout(init, 100);<br>  }<br>  else<br>  {<br>    document.getElementById('canvas-enabled').style.display = 'none';<br>    document.getElementById('no-canvas').style.display = '';<br><br>    CFInstall.check({<br>      node: 'chrome-install-placeholder',<br>      className: 'chrome-install',<br>      destination: window.location.href<br>    });<br>  }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

Шаг 1

Всю отрисовку будем вести в буфере 128x128, а потом его выводить с 3-х кратным увеличением (используя putImageData).
var scr = [];<br>var zbuf = [];<br>var WDT = 128;<br>var HGT = 128;<br><br>function blit()<br>{<br>  var cd = ctx.createImageData(canvas.width, canvas.height);<br>  var data = cd.data;<br>  var ind = 0;<br><br>  for (var y = 0; y < HGT; y++)<br>  {<br>    var line = scr[y];<br><br>    for (var x = 0; x < WDT; x++)<br>    {<br>      data[ind++] = line[x][0]; data[ind++] = line[x][1]; data[ind++] = line[x][2]; data[ind++] = 255;<br>....<br>  }<br><br>  ctx.putImageData(cd, 0, 0);<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

Шаг 2

Представим фигуру в виде объекта и напишем читерскую функцию box, которая будет создавать кубы сразу с нормалями:
{<br>  points: [<br>    {x: point_x, y: point_y, z: point_z, n: { x: point_normal_x, y: point_normal_y, z: point_normal_z } },<br>    ....<br>  ],<br>  faces: [<br>    [point_1, point_2, point_3, { x: face_normal_x, y: face_normal_y, z: face_normal_z }],<br>    ....<br>  ],<br>  color: object_color,<br>  rot: [rot_x, rot_y, rot_z]<br>}<br><br>function box(size, cl, rot)<br>{<br>  var norm = 1 / Math.sqrt(3);<br><br>  return {<br>    points: [<br>      { x: -size, y: size, z: -size, n: { x: -norm, y: norm, z: -norm } },<br>      ....<br>    ],<br>    faces: [<br>      [ 0, 1, 2, { x: 0, y: 1, z: 0 } ],<br>      ....<br>    ],<br>    color: cl,<br>    rot: rot<br>  };<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

Шаг 3

Выполним всю чёрную работу — повернём объект (вместе с нормалями — это быстрее, чем считать нормали заново) и спроецируем его в 2D (мне было лень нормально считать уровень освещённости, по-этому я использовал метод научного тыкаtm для нахождения магической формулы и магических коеффициентов 0.7 и 3)
function project(pt, rm)<br>{<br>  var rot = {<br>    x: (pt.x*rm.oxx + pt.y*rm.oxy + pt.z*rm.oxz),<br>    y: (pt.x*rm.oyx + pt.y*rm.oyy + pt.z*rm.oyz),<br>    z: (pt.x*rm.ozx + pt.y*rm.ozy + pt.z*rm.ozz + ZPOS)<br>  };<br><br>  var l = 1 - (Math.cos(pt.n.x*rm.ozx + pt.n.y*rm.ozy + pt.n.z*rm.ozz) - 0.7) * 3;<br>  l = Math.max(0, Math.min(1, l));<br><br>  return {<br>    x: ((WDT / 2) + rot.x * (WDT / 2 - 1) / rot.z),<br>    y: ((HGT / 2) + rot.y * (HGT / 2 - 1) / rot.z),<br>    z: rot.z,<br>    l: l<br>  };<br>}<br><br>function draw_object(obj)<br>{<br>  var ax = (tm * obj.rot[0]);<br>  var ay = (tm * obj.rot[1]);<br>  var az = (tm * obj.rot[2]);<br><br>  var s1 = Math.sin(ax); var s2 = Math.sin(ay); var s3 = Math.sin(az);<br>  var c1 = Math.cos(ax); var c2 = Math.cos(ay); var c3 = Math.cos(az);<br><br>  var rm = {<br>    oxx: (c2 * c1),<br>    oxy: (c2 * s1),<br>    ....<br>  };<br><br>  var pr = [];<br><br>  for (var i = 0; i < obj.points.length; i++) {<br>    pr.push(project(obj.points[i], rm));<br>  }<br><br>  for (var i = 0; i < obj.faces.length; i++)<br>  {<br>    var face = obj.faces[i];<br>    var fz = (face[3].x*rm.ozx + face[3].y*rm.ozy + face[3].z*rm.ozz);<br><br>    if (fz <= 0) {<br>      triangle(pr[face[0]], pr[face[1]], pr[face[2]], obj.color);<br>    }<br>  }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

Шаг 4

Напишем процедуру рисования горизонтальных линий (не самую оптимальную) и треугольников (тоже не чемпион по скорости):
function hline(y, xl, xr, cl, ll, lr, zl, zr)<br>{<br>  // Растянем линию горизонтально - так как мы используем треугольники,<br>  // а не честные полигоны, то при субпиксельной отрисовке без этого хака<br>  // иногда проявляются артефакты<br>  xl -= 0.5;<br>  xr += 0.5;<br>  ....<br>}<br><br>// **в реальном коде используется изменённая функция**<br>function triangle(a, b, c, cl)<br>{<br>  .... сортировка вершин ....<br><br>  var dxl = (c.x - a.x) / (c.y - a.y);<br>  var dxr = (b.x - a.x) / (b.y - a.y);<br>  ....<br>  var y = a.y;<br><br>  while (y < b.y)<br>  {<br>    // (y - a.y) - не обязательно целочисленное<br>    // за счёт этого достигается субпиксельная точность<br>    xl = sx + dxl * (y - a.y);<br>    xr = sx + dxr * (y - a.y);<br>    ....<br>    hline(y, xl, xr, cl, ll, lr, zl, zr);<br>    y++;<br>  }<br><br>  ....<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

Шаг 5

Остаётся написать функцию loop и запустить её через setInterval.
function loop()<br>{<br>  tm = ((new Date()).valueOf() - st) / 1.5;<br><br>  draw_scene();<br>  blit();<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

Эпилог

Полный текст скрипта доступен по адресу http://zame-dev.org/projects/nocanvas/0000/index.html (просто загляните в исходный код страницы)

Читай блог @ подписывайся на RSS
Теги:
Хабы:
Всего голосов 71: ↑63 и ↓8+55
Комментарии42

Публикации

Истории

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань