Szécsi László 3D Grafikus Rendszerek 4. labor
Geometria JSON-ból IndexedTrianglesGeometry.js a QuadGeometry.js mintájára a konstruktor kapjon egy mesh-leíró objektumot (ahogy az a JSON fileból jön) azért nem a filenevet adjuk át, mert egy fileban több mesh lehet vertices tulajdonság: 3n db koordináta folyotonos tömbben normals tulajdonság: 3n db érték folyotonos tömbben texturecoords tulajdonság: 2n db értéket tartalmazó folyotonos tömbök tömbje (de tipikusan csak egy textúrakoordináta-készlet van) faces tulajdonság: 3 indexet tartalmazó tömböcskék tömbje egy folytonos tömböt lehet belőle gyártani így: [].concat.apply([], jsonObject.faces)
Index buffer az indexek száma nem fix a konstruktorban el kell tárolni tulajdonságként rajzoláskor fel lehet használni
MultiMesh.js - betöltés var MultiMesh = function( gl, jsonModelFileUrl, materials) { this.meshes = []; var request = new XMLHttpRequest(); request.open("GET", jsonModelFileUrl); var theMultiMesh = this; request.onreadystatechange = function () { // next slide }; request.send();
MultiMesh.js – betöltés után if (request.readyState == 4) { var meshesJson = JSON.parse(request.responseText).meshes; for (var i = 0; i < meshesJson.length; i++) { theMultiMesh.meshes.push( new Mesh( new IndexedTrianglesGeometry(gl, meshesJson[i]), materials[i] )); }
MultiMesh.js – rajzolás MultiMesh.prototype.draw = function(gl){ for (var i = 0; i < this.meshes.length; i++) { this.meshes[i].draw(gl); } };
Skálázni, forgatni kell
3D kamera PerspectiveCamera object must set view, projection matrices moveable by mouse and WASD keys
Kamera paraméterek var PerspectiveCamera = function() { this.position = new Vec3(0.0, 0.0, 0.0); this.ahead = new Vec3(0.0, 0.0, -1.0); this.right = new Vec3(1.0, 0.0, 0.0); this.up = new Vec3(0.0, 1.0, 0.0); this.yaw = 0.0; this.pitch = 0.0; this.fov = 1.0; this.aspect = 1.0; this.nearPlane = 0.1; this.farPlane = 1000.0;
Tagváltozók mozgatáshoz this.speed = 0.5; this.isDragging = false; this.mouseDelta = new Vec2(0.0, 0.0);
Mátrixok this.viewMatrix = new Mat4(); this.projMatrix = new Mat4(); this.rayDirMatrix = new Mat4(); this.viewProjMatrix = new Mat4(); this.updateViewMatrix(); this.updateProjMatrix(); this.updateRayDirMatrix(); };
Világ felfelé iránya PerspectiveCamera.worldUp = new Vec3(0, 1, 0);
View mátrix számítása PerspectiveCamera.prototype.updateViewMatrix = function(){ this.viewMatrix.set( this.right.x , this.right.y , this.right.z , 0, this.up.x , this.up.y , this.up.z , 0, -this.ahead.x , -this.ahead.y , -this.ahead.z , 0, 0 , 0 , 0 , 1).translate(this.position).invert(); this.viewProjMatrix.set(this.viewMatrix).mul(this.projMatrix); };
Proj mátrix számítása PerspectiveCamera.prototype.updateProjMatrix = function() { var yScale = 1.0 / Math.tan(this.fov * 0.5); var xScale = yScale / this.aspect; var f = this.farPlane; var n = this.nearPlane; this.projMatrix.set( xScale , 0 , 0 , 0, 0 , yScale , 0 , 0, 0 , 0 , (n+f)/(n-f) , -1, 0 , 0 ,2*n*f/(n-f), 0); this.viewProjMatrix.set(this.viewMatrix). mul(this.projMatrix); };
RayDir mátrix számítása PerspectiveCamera.prototype.updateRayDirMatrix = function(){ // önállóan megoldandó feladat // de ráér // az env mapping háttérhez kell csak };
Mozgatás: yaw, pitch drag PerspectiveCamera.prototype.move = function(dt, keysPressed) { if(this.isDragging){ this.yaw -= this.mouseDelta.x * 0.002; this.pitch -= this.mouseDelta.y * 0.002; if(this.pitch > 3.14/2.0) { this.pitch = 3.14/2.0; } if(this.pitch < -3.14/2.0) { this.pitch = -3.14/2.0; this.mouseDelta = new Vec2(0.0, 0.0);
Mozgatás: főirányok a szögekből this.ahead = new Vec3( -Math.sin(this.yaw)*Math.cos(this.pitch), Math.sin(this.pitch), -Math.cos(this.yaw)*Math.cos(this.pitch) ); this.right.setVectorProduct( this.ahead, PerspectiveCamera.worldUp ); this.right.normalize(); this.up.setVectorProduct(this.right, this.ahead); }
Mozgatás gombokkal if(keysPressed.W) { this.position.addScaled(this.speed * dt, this.ahead); } if(keysPressed.S) { this.position.addScaled(-this.speed * dt, this.ahead); if(keysPressed.D) { this.position.addScaled(this.speed * dt, this.right); if(keysPressed.A) { this.position.addScaled(-this.speed * dt, this.right); if(keysPressed.E) { this.position.addScaled(this.speed * dt, PerspectiveCamera.worldUp); if(keysPressed.Q) { this.position.addScaled(-this.speed * dt, PerspectiveCamera.worldUp);
Mátrixok frissítése this.updateViewMatrix(); this.updateRayDirMatrix(); };
Eseménykezelők – meg is kell hívni őket! PerspectiveCamera.prototype.mouseDown = function() { this.isDragging = true; this.mouseDelta.set(); }; PerspectiveCamera.prototype.mouseMove = function(event) { this.mouseDelta.x += event.movementX; this.mouseDelta.y += event.movementY; event.preventDefault(); PerspectiveCamera.prototype.mouseUp = function() { this.isDragging = false;
Képméretarány állítása – ezt is meg kell hívni! // ar: canvas.clientWidth / canvas.clientHeight PerspectiveCamera.prototype.setAspectRatio = function(ar) { this.aspect = ar; this.updateProjMatrix(); };
Rakjuk össze! ortho kamera lecserélése mélységteszt bekapcsolása eseményfigyelők bekötése GameObject2D jó lehet még de figyeljünk rá, hogy a modelViewProjMatrix beállítása rajzoláshoz összhangban legyen a PerspectiveCamera tulajdonságaival GameObject3D csak a forgatásban térne el
3D transzformáló vertex shader kell neki a modelViewProjMatrix továbbra is de kell egy modelMatrix uniform is nincs benne a kameratrafó modellből világkoordintákba transzformál új varying kimenet: worldPos és még kell egy modelMatrixInverse uniform a normálvektorok transzformálásához ezt most bal oldalról szorozzuk, mert az inverz transzponálttal kell a normálvektort transzformálni új varying kimenet: worldNormal
Fragment shader a 3D trafó ellenőrzéséhez rajzoljuk ki a worldNormal-t színként
Várt eredmény abs(normal)-lal
Egyszerű árnyaló FS legyen egy fényirány-vektor normálvektor és fényirány közötti szög koszinusza az irradiancia texúrából olvasott színt moduláljuk ezzel
Várt eredmény
Environment mapping doboztextúra betöltése új FS ami kiszámolja az ideális visszaverődési irányt és kiolvassa a textúrát
TextureCube.js - betöltés var TextureCube = function(gl, mediaFileUrls) { pendingResources[mediaFileUrls[0]] = ++pendingResources[mediaFileUrls[0]] || 1; this.mediaFileUrls = mediaFileUrls; this.glTexture = gl.createTexture(); this.loadedCount = 0; this.images = []; var theTexture = this; for(var i=0; i<6; i++){ this.images[i] = new Image(); this.images[i].onload = function() { theTexture.loaded(gl); } this.images[i].src = mediaFileUrls[i]; } };
TextureCube.js – erőforrás létrehozása TextureCube.prototype.loaded = function(gl){ this.loadedCount++; if(this.loadedCount < 6) return; gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.glTexture); for(var i=0; i<6; i++){ gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.images[i]); } gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.generateMipmap(gl.TEXTURE_CUBE_MAP); gl.bindTexture(gl.TEXTURE_CUBE_MAP, null); if( --pendingResources[this.mediaFileUrls[0]] === 0 ) { delete pendingResources[this.mediaFileUrls[0]]; };
Ez nem kell, ha van reflection // akkor kéne, ha direktben akarnánk rákötni egy Sampler2D // uniformra. Ha van Material-unk reflectionnel, akkor // a WebGLMath Sampler2D objektuma ezt intézi TextureCube.prototype.commit = function(gl, uniformLocation, textureUnit){ gl.uniform1i(uniformLocation, textureUnit); gl.activeTexture(gl.TEXTURE0 + textureUnit); gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.glTexture); };
Tükröző objektum létrehozása fragment shader gyártsuk le a TextureCube-ot, kössük be a fenti FS-t használó Material-ba, a GameObject használja ezt az anyagot // kell egy sampler uniform uniform samplerCube envmapTexture; // kiolvasni a tükörirányban textureCube( envmapTexture, reflect(-viewDir, normal)) this.skyCubeTexture = new TextureCube(gl, [ "media/posx512.jpg", "media/negx512.jpg", "media/posy512.jpg", "media/negy512.jpg", "media/posz512.jpg", "media/negz512.jpg",] ); this.slowpokeMaterial0. envmapTexture.set ( this.skyCubeTexture);
Várt eredmény higany pokemon visszaveri a környezetét de maga a környezet nem látszik
Sugárirány kiszámítása NDC-ből extra (EVP)-1 -et nevezzük rayDirMatrix-nak
Környezet megjelenítése háttérként extra teljes képernyős téglalapot kell rajzolni (hurrá!) új VS: kiszámolja a sugárirányt át kell adni a képernyőkoordinátából-világkoordináta- mínusz-szempozíció-számító mátrixot (a.k.a. rayDirMatrix) a kamera ezt kiszámolja nem transzformál (mert full viewport quad) z=0.99999, minden mőgé FS megkapja a VS-től varying-ben a sugárirányt ezzel címzi a textúrát visszaadja a kapott színt
Ne felejtsük el a GameObject adja át a rayDirMatrix-ot extra Ne felejtsük el a GameObject adja át a rayDirMatrix-ot az új shadereket vegyük be az index.HTML-be gyártsuk le szükséges shader, program, material objektumokat ugyanazt a doboztextúrát kössük be a háttér anyagába, mint amit a tükröző objektuméba legyen quadGeometry legyen egy mesh a quadGeometry és a fenti material használatával legyen egy gameObject a mesh-sel
extra Várt eredmény tükröző objektum és háttér
Házi feladat válasszon egyet az alábbiak közül: Phong-Blinn + diffúz árnyalás objektum körül keringő pontfényforrás 3D-ben forgó, ütköző objektumok ütközőgeometria: két gömb per objektum procedurális normal mapping zajfüggvény: random (de fix) irányok mentén koszinuszok összege normálvektor perturbációja zajfüggvény gradiensével env mappinggel kombinálva (háttér is kell)