📜 ⬆️ ⬇️

No canvas

Perhaps it is better to transfer to “I am promoting” or “JavaScript” (suggestions are accepted). Or maybe you should leave it as it is. However...

3D with z-buffer, sub-pixel precision and Gouraud lighting in javascript? Yes, anyone can do it using canvas !

It can be long and tasty to describe the benefits of canvas, but the article is not about that; no less interesting
See what the canvas is bad for.
')

So what?



I started a blog in which I’m going to write regularly about creating various effects and javascript games that do not use canvas:

A small heretical article on the seed - how to make a 3D + z-buffer + subpixel + gouraud shading using canvas


Step 0

First of all, you need to take care of IE users by offering them support for the canvas tag in the form of a Chrome Frame: prescribe the meta , connect the google script, create the no-canvas block and edit the styles for the Google iframe (by default it appears in the center of the page)
< 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 .

The main function (init is called by timeout, because otherwise chrome (dev version) sometimes does not draw the background of the body to the end):
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 .

Step 1

All rendering will be carried out in a 128x128 buffer, and then it will be output with a 3-fold increase (using 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 .

Step 2

Imagine a shape as an object and write a cheat box function that will create cubes with normals right away:
{<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 .

Step 3

Perform all the black work - turn the object (along with the normals - this is faster than counting the normals again) and project it in 2D (I was too lazy to read the light level normally, so I used the scientific method tm to find the magic formula and magic coefficients 0.7 and 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.nx*rm.ozx + pt.ny*rm.ozy + pt.nz*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 .

Step 4

Let's write the procedure for drawing horizontal lines (not the most optimal) and triangles (also not a champion in speed):
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 = (cx - ax) / (cy - ay);<br> var dxr = (bx - ax) / (by - ay);<br> ....<br> var y = ay;<br><br> while (y < by)<br> {<br> // (y - ay) - <br> // <br> xl = sx + dxl * (y - ay);<br> xr = sx + dxr * (y - ay);<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 .

Step 5

It remains to write the loop function and run it through 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 .

Epilogue

The full text of the script is available at http://zame-dev.org/projects/nocanvas/0000/index.html (just look at the source code of the page)

Read blog @ subscribe to RSS

Source: https://habr.com/ru/post/82004/


All Articles