Moikka,
Olen aloitellut JavaScriptin ja ylipäätään ohjelmoinnin opettelua ja nyt kun hieman alkaa koodi taipua, niin tein perinteisen metapalloefektin. Lopputulos on minusta ihan hienon näköinen, mutta kun piirtoalueen kokoa kasvattaa, siitä tulee aivan tolkuttoman hidas. Olisiko kenelläkään antaa vinkkiä miten tätä voisi optimoida? Ongelma taitaa olla siinä, että tässä käsitellään joka ikinen pikseli joka kierroksella oli siinä tapahtunut jotain tai ei. En vain keksi miten voisin käsitellä vain ne pikselit, jossa jotain on tapahtunut. Ehkäpä se ei tällä menetelmällä olekaan mahdollista?
Piirtoalue käydään läpi kahdessa sisäkkäisessä silmukassa. Kokeilin, että auttaisiko, jos se käytäisiin läpi yhdessä silmukassa ja sijainti lasketaan kullekin pikselille erikseen, mutta se ei juuri parantanut suorituskykyä joten päätin käyttää kahta silmukkaa koska se oli minusta selkeämpi tapa. Jätin yhden silmukan piirtoalueen läpikäynnin kuitenkin kommentoituna koodiin jos jotakuta sattuu kiinnostamaan.
Koodi on alla ja täältä voi ihmetellä livenä: https://jsfiddle.net/mhpcLxsq/
<!DOCTYPE html> <html> <head> <title>Hellurei</title> <style type="text/css"> body {margin: 0px} canvas {position: absolute} </style> </head> <body> <div> <canvas id="canvas"> </canvas> </div> <script type="text/javascript" src="Metapallot.js"> </script> </body> </html>
// canvas ja piirtokonteksti const c = document.getElementById("canvas"); const width = c.width = window.innerWidth; const height = c.height = window.innerHeight; const ctx = c.getContext("2d"); // imagedata const imageData = ctx.createImageData(width,height); const data = imageData.data; const dataLength = data.length; // array palloja varten, ja pallojen lukumäärä const balls = []; const numBalls = 20; // satunnaislukujen arpoja function random(min, max){ var num = Math.floor(Math.random() * (max - min + 1) + min); return num; } // pallon sijainti, koko ja liikkeen suunta ja nopeus function ball() { this.posX = random(0, width); this.posY = random(0, height); this.dx = random(-4,4); this.dy = random(-10,10); this.radius = random(10, 100); } // pallon liikutus ball.prototype.move = function() { if (this.posX > width || this.posX < 0) { // jos on kohdattu piirtoalueen reuna x-akselilla, vaihdetaan suuntaa this.dx = -(this.dx); } if (this.posY > height || this.posY < 0 ) { // jos on kohdattu piirtoalueen reuna y-akselilla, vaihdetaan suuntaa this.dy = -(this.dy); } this.posX += this.dx; // liikutetaan palloja this.posY += this.dy; // liikutetaan palloja } // pallojen liikutus function updateLocation() { for (i=0; i<balls.length; i++) { balls[i].move(); } } // luodaan pallot ja työnnetään taulukkoon for (i=0; i<numBalls;i++) { var pallo = new ball(); balls.push(pallo); } // piiretään liikutellaan pallukoita function renderBalls(){ /* // piirtoalueen läpikäynti yhdessä silmukassa for (var index = 0; index < dataLength; index +=4 ){ // käydään piirtoalueen pikselit läpi, pituus var y = (index / 4) / width; // pikselin y positio var x = (index / 4) % width; // pikselin x positio var color = 0; // pikselin väri for (i=0; i<numBalls; i++) { color += balls[i].radius * 5000 / (Math.pow((x - balls[i].posX),2) + Math.pow((y - balls[i].posY),2)); if (color >= 255) break; } // annetaan pikselille väri data[index+0] = color*1.05; // r data[index+1] = color; // g data[index+2] = color*1.4; // b data[index+3] = 255; // a } */ // piirtoalueen läpikäynti kahdessa silmukassa for (var y=0; y<height; y++){ // käydään piirtoalueen pikselit läpi, pituus for (var x=0; x<width; x++){ // käydään piirtoalueen pikselit läpi, leveys var index = (x+y*width)*4; // pikselin indeksi var color = 0; // pikselin väri for (i=0; i<numBalls; i++) { color += balls[i].radius * 5000 / (Math.pow((x - balls[i].posX),2) + Math.pow((y - balls[i].posY),2)); // kaava metapalloja varten eli if (color >= 255) break; // jos väri menee yli 255, lopetetaan } // annetaan pikselille väri data[index+0] = color*1.05; // r data[index+1] = color; // g data[index+2] = color*1.4; // b data[index+3] = 255; // a } } // laitetaan kuvan data canvakselle ctx.putImageData(imageData, 0,0); } // metapallojen animointi function animate() { renderBalls(); updateLocation(); requestAnimationFrame(animate); } animate();
Yksinkertaisella matematiikalla voi arvioida maksiminopeutta. Koodi vie parhaimmillaankin noin 20 operaatiota/pikseli/pallo. Oletetaan 20 palloa ja 1000×1000 pikseliä ja 2 GHz:n kone. Ruudun päivitysnopeudeksi tulee 2 GHz / (20 × 20 × 1000 × 1000) = 5 Hz. Edes huippuoptimoinnilla ei pääse paljon tämän paremmaksi. JS:llä varmasti jää vielä hieman tästäkin. Käyttämällä GPU:ta ja jotain rinnakkaislaskentaa toki voi päästä parempiin tuloksiin, mutta se on vielä aika kokeellista hommaa nettisivuilla.
Nyt käytät silmukoissa globaalia i:tä, mikä on hidasta. Ainakin Chromiumissa koodia nopeuttaa noin 20%, jos laitat var-sanat puuttuviin kohtiin. Kannattaa laittaa use strict koodin alkuun, ettei näitä virheitä tule.
Kun silmukat kääntää niin päin, että ulommassa käydään läpi pallot ja sisemmässä pikselit, nopeutuu vielä 60%. Tämä edellyttää, että ensin lasketaan color-taulukko ja lopuksi vasta laitetaan siitä pikselit ruudulle.
Koska metapallot vaikuttavat ainakin vähän joka pikseliin, on vaikea optimoida koodia siltä kannalta. Jos alue on todella iso ja pallot todella pieniä, voi rajata kunkin pallon kohdalla pikselisilmukan kapeammaksi niin, että lopetetaan, kun pallon vaikutus väriin on häviävän pieni. Ainakaan näillä palloilla ja normaalin kokoisella ruudulla tästä ei vielä tule apua...
Ohessa on koodi korjattuna kahdella ensiksi mainitsemallani optimoinnilla:
var color = new Array(width*height); function renderBalls() { color.fill(0); for (var i = 0; i < balls.length; ++i) { for (var y = 0; y < height; ++y) { for (var x = 0; x < width; ++x) { var index = x + y * width; var dx = x - balls[i].posX, dy = y - balls[i].posY; color[index] += balls[i].radius / (dx*dx + dy*dy); } } } for (var y = 0; y < height; ++y) { for (var x = 0; x < width; ++x) { var index = x + y * width; var c = 5000 * color[index] data[4*index+0] = c * 1.05; data[4*index+1] = c; data[4*index+2] = c * 1.4; data[4*index+3] = 255; } } ctx.putImageData(imageData, 0,0); }
eikös tuosta saa vielä nuo alemmat loopit kokonaan pois, yhdistämällä data -asetukset suoraan ylempään looppiin?
Mietin vain mistä tuo nopeusetu tulee kahdella silmukalla, jos kerran index ja color[index] on jo tiedossa ylemmässä silmukassa.
**Edit** ähh jokainen koordinaattihan läpikäydään pallojen verran, jos ne yhdistetään samaan looppiin.
var color = new Array(width*height); function renderBalls() { color.fill(0); for (var i = 0; i < balls.length; ++i) { for (var y = 0; y < height; ++y) { for (var x = 0; x < width; ++x) { var index = x + y * width; var dx = x - balls[i].posX, dy = y - balls[i].posY; color[index] += balls[i].radius / (dx*dx + dy*dy); var c = 5000 * color[index] data[4*index+0] = c * 1.05; data[4*index+1] = c; data[4*index+2] = c * 1.4; data[4*index+3] = 255; } } } ctx.putImageData(imageData, 0,0); }
Kiitos vinkeistä!
Toinen tapa tehdä tämä olisi varmaan jokin sellainen, että pallolle piirrettäisiin liukuväri ja määriteltäisiin vaikutusalue (pallon säde?). Jos sitten pallot tulevat toistensa vaikutusalueelle, laskettaisiin väriarvot yhteen jolloin ne sulautuvat toisiinsa. Sillon ei tarvitsisi rullata koko ruudun pikseleitä läpi ja suorituskyky olisi varmaankin parempi.
Aivan varmasti olisi. Eli jos koko canvas alustetaan mustaksi, ja tämän jälkeen päälle piirretään vain pallojen pikselit. Toki jos pallojen piirtoalue täyttää koko ruudun, ei tilanne olennaisesti muutu, lisähyöty saadaan vain mustaksi jääneiden pikselien osalta.
kilipukki kirjoitti:
Toinen tapa tehdä tämä olisi varmaan jokin sellainen, että pallolle piirrettäisiin liukuväri – –
Värin haku muistista on hitaampaa kuin laskeminen. Turhien pikseleiden ohittamisen voi tehdä aivan hyvin nykyiseenkin silmukkaan, kun vain laskee y- ja x-silmukoihin raja-arvot pallon sijainnin ja koon mukaan. Käytännössä tuossa pallot täyttävät niin suuren osan piirtopinnasta, ettei optimoinnista ole juurikaan hyötyä.
Aihe on jo aika vanha, joten et voi enää vastata siihen.