De eerste versie van Flight Simulator verscheept in 1980 voor de Apple II en, verbazingwekkend genoeg, was het in 3D! Dat was een opmerkelijke prestatie. Het is nog verbazingwekkender als je bedenkt dat al het 3D met de hand is gedaan, het resultaat van nauwkeurige berekeningen en pixelcommando's op een laag niveau. Toen Bruce Atwick de vroege versies van Flight Simulator aanpakte, waren er niet alleen geen 3D-frameworks, maar waren er helemaal geen frameworks! Die versies van de game waren meestal in assembly geschreven, slechts een enkele stap verwijderd van enen en nullen die door een CPU stroomden.
Toen we Flight Simulator (of Flight Arcade zoals we het noemen) opnieuw wilden bedenken voor het web en wilden laten zien wat er mogelijk is in de nieuwe Microsoft Edge-browser en EdgeHTML-renderingengine, konden we niet anders dan nadenken over het contrast van het maken van 3D en nu oude Flight Sim, nieuwe Flight Sim, oude Internet Explorer, nieuwe Microsoft Edge. Moderne codering lijkt bijna luxueus terwijl we 3D-werelden in WebGL modelleren met geweldige frameworks zoals Babylon.js. Het laat ons focussen op problemen van zeer hoog niveau.
In dit artikel deel ik onze benadering van een van deze leuke uitdagingen: een eenvoudige manier om realistisch ogend grootschalig terrein te creëren.
Opmerking: Interactieve code en voorbeelden voor dit artikel bevinden zich ook in Flight Arcade / Learn.
De meeste 3D-objecten worden gemaakt met modelleertools en dat is niet zonder reden. Het maken van complexe objecten (zoals een vliegtuig of zelfs een gebouw) is moeilijk in code. Modelleringstools zijn bijna altijd logisch, maar er zijn uitzonderingen! Een van die gevallen kan zijn als de glooiende heuvels van het eiland Flight Arcade. We gebruikten een techniek die we eenvoudiger en mogelijk nog intuïtiever vonden: een hoogteprofiel.
Een heightmap is een manier om een standaard tweedimensionaal beeld te gebruiken om het hoogtereliëf van een oppervlak zoals een eiland of ander terrein te beschrijven. Het is een vrij gebruikelijke manier om met hoogtegegevens te werken, niet alleen in games, maar ook in geografische informatiesystemen (GIS) die worden gebruikt door cartografen en geologen.
Om je te helpen een idee te krijgen hoe dit werkt, bekijk je de hoogtekaart in deze interactieve demo. Probeer te tekenen in de afbeeldingseditor en bekijk het resulterende terrein.
Het concept achter een hoogtekaart is vrij eenvoudig. In een afbeelding zoals hierboven is puur zwart de "vloer" en puur wit de hoogste piek. De grijsschaalkleuren daartussen geven overeenkomstige verhogingen weer. Dit geeft ons 256 niveaus van hoogteverschillen, dat is genoeg detail voor onze game. Echte toepassingen kunnen het volledige kleurenspectrum gebruiken om aanzienlijk meer detailniveaus op te slaan (2564 = 4.294.967.296 detailniveaus als u een alfakanaal opneemt).
Een heightmap heeft een aantal voordelen ten opzichte van een traditionele veelhoekige mesh:
Ten eerste zijn heightmaps veel compacter. Alleen de meest significante gegevens (de hoogte) worden opgeslagen. Het moet programmatisch worden omgezet in een 3D-object, maar dit is de klassieke handel: u bespaart nu ruimte en betaalt later met berekening. Door de gegevens als een afbeelding op te slaan, krijgt u nog een ruimtevoordeel: u kunt gebruikmaken van standaard technieken voor beeldcompressie en de gegevens klein maken (in vergelijking)!
Ten tweede zijn heightmaps een handige manier om terrein te genereren, visualiseren en bewerken. Het is behoorlijk intuïtief als je er een ziet. Het voelt een beetje als naar een kaart kijken. Dit bleek bijzonder nuttig te zijn voor Flight Arcade. We hebben ons eiland in Photoshop ontworpen en bewerkt! Dit maakte het heel eenvoudig om kleine aanpassingen door te voeren als dat nodig was. Toen we er bijvoorbeeld voor wilden zorgen dat de landingsbaan helemaal vlak was, hebben we ervoor gezorgd dat we over dat gebied in één kleur schilderen.
Je kunt de hoogtepatroon voor Flight Arcade hieronder bekijken. Kijk of je de "vlakke" gebieden kunt zien die we hebben gemaakt voor de landingsbaan en het dorp.
De hoogtekaart voor het eiland Flight Arcade. Het is gemaakt in Photoshop en is gebaseerd op het "grote eiland" in een beroemde eilandenketen in de Stille Oceaan. Durft iemand een poging te wagen?Een structuur die wordt toegewezen aan het resulterende 3D-net nadat de hoogtekaart is gedecodeerd. Daarover meer.We hebben Flight Arcade gebouwd met Babylon.js en Babylon gaf ons een vrij eenvoudig pad van heightmap naar 3D. Babylon biedt een API om een meshgeometrie te genereren van een heightmap-afbeelding:
var ground = BABYLON.Mesh.CreateGroundFromHeightMap ('your-mesh-name', '/path/to/heightmap.png', 100, // width of the ground mesh (x axis) 100, // depth of the ground mesh (z-as) 40, // aantal onderverdelingen 0, // min hoogte 50, // max. hoogtescène, onwaar, // bijwerkbaar? null // terugbellen wanneer raster gereed is);
De hoeveelheid detail wordt bepaald door de eigenschap van die onderverdeling. Het is belangrijk op te merken dat de parameter verwijst naar het aantal onderverdelingen aan elke kant van de hoogte kaartafbeelding, niet het totale aantal cellen. Als u dit aantal iets verhoogt, kan dit een groot effect hebben op het totale aantal hoekpunten in uw mesh.
In de volgende sectie zullen we leren hoe je de grond kunt structureren, maar wanneer je experimenteert met het maken van height-kaarten, is het handig om het draadframe te zien. Hier is de code om een eenvoudige wireframe-structuur toe te passen, zodat u gemakkelijk kunt zien hoe de heightmap-gegevens worden geconverteerd naar de hoekpunten van onze mesh:
// eenvoudig draadframemateriaal var materiaal = nieuw BABYLON.StandaardMaterial ('ground-material', scène); material.wireframe = true; ground.material = materiaal;
Zodra we een model hadden, was het in kaart brengen van een textuur relatief eenvoudig. Voor Flight Arcade hebben we eenvoudig een heel groot beeld gemaakt dat overeenkwam met het eiland in onze hoogtekaart. Het beeld wordt uitgerekt over de contouren van het terrein, zodat de textuur en de hoogtekaart gecorreleerd blijven. Dit was echt gemakkelijk om te visualiseren en, nogmaals, al het productiewerk werd gedaan in Photoshop.
De originele textuurafbeelding is gemaakt op 4096x4096. Dat is behoorlijk groot! (Uiteindelijk hebben we de grootte met een niveau teruggebracht naar 2048 x 2048 om de download redelijk te houden, maar alle ontwikkeling is gedaan met de afbeelding op volledige grootte.) Hier is een voorbeeld van een volledige pixel van de oorspronkelijke textuur.
Een volledige pixelsteekproef van de originele eilandtextuur. De hele stad is slechts ongeveer 300 px vierkant.Die rechthoeken stellen de gebouwen in de stad op het eiland voor. We merkten snel een discrepantie in het niveau van texturedetail dat we konden bereiken tussen het terrein en de andere 3D-modellen. Zelfs met onze gigantische eilandtextuur, was het verschil afleidend duidelijk!
Om dit te verhelpen, hebben we aanvullende details in de terreinextuur "gemengd" in de vorm van willekeurige ruis. Je kunt de voor- en er onder zien. Merk op hoe de extra ruis het uiterlijk van details in het terrein verbetert.
We hebben een aangepaste shader gemaakt om de ruis toe te voegen. Shaders geven je ongelooflijk veel controle over het weergeven van een WebGL 3D-scène, en dit is een goed voorbeeld van hoe een arcering nuttig kan zijn.
Een WebGL-shader bestaat uit twee hoofdonderdelen: de vertex en fragment shaders. Het belangrijkste doel van de hoekpuntshader is om hoekpunten naar een positie in het gerenderde frame te schikken. De fragment (of pixel) arcering bepaalt de resulterende kleur van de pixels.
Shaders worden geschreven in een taal op een hoog niveau, GLSL genaamd (Graphics Library Shader Language), die lijkt op C. Deze code wordt uitgevoerd op de GPU. Bekijk deze tutorial over hoe je je eigen aangepaste shader voor Babylon.js kunt maken voor een uitgebreide blik op hoe shaders werken, of bekijk deze beginnershandleiding voor het coderen van grafische shaders.
We veranderen niet hoe onze textuur op het maasveld wordt afgebeeld, dus onze vertex-shader is vrij eenvoudig. Het berekent alleen de standaardtoewijzing en wijst de doellocatie toe.
precisie mediump float; // Attributenattribuut vec3-positie; kenmerk vec3 normaal; kenmerk vec2 uv; // Uniformen uniforme mat4 worldViewProjection; // Variërend variërende vec4 vPosition; variërend vec3 vNormal; variërende vec2 vUV; void main () vec4 p = vec4 (position, 1.0); vPosition = p; v Normaal = normaal; vUV = uv; gl_Position = worldViewProjection * p;
Onze fragmentshader is iets gecompliceerder. Het combineert twee verschillende afbeeldingen: de basis- en overvloei-afbeeldingen. Het basisbeeld wordt afgebeeld over het volledige grondrooster. In Flight Arcade is dit het kleurenbeeld van het eiland. Het overvloeibeeld is het kleine ruisbeeld dat wordt gebruikt om de grond textuur en details te geven op korte afstanden. De arcering combineert de waarden van elk beeld om een gecombineerde textuur over het eiland te creëren.
De laatste les in Flight Arcade vindt plaats op een mistige dag, dus de andere taak van onze pixel shader is om de kleur aan te passen om mist te simuleren. De aanpassing is gebaseerd op hoe ver het hoekpunt van de camera is, waarbij verre pixels zwaarder worden "verdoezeld" door de mist. U ziet deze afstandsberekening in de calcFogFactor
functie boven de hoofd-shadercode.
#ifdef GL_ES precisie-highp-vlotter; #endif uniforme mat4 worldView; variërende vec4 vPosition; variërend vec3 vNormal; variërende vec2 vUV; // Refs uniform sampler2D baseSampler; uniforme sampler2D blendSampler; uniforme vlottermengsel ScaleU; uniforme float-blendScaleV; #define FOGMODE_NONE 0. #define FOGMODE_EXP 1. #define FOGMODE_EXP2 2. #define FOGMODE_LINEAR 3. #define E 2.71828 uniforme vec4 vFogInfos; uniforme vec3 vFogColor; float calcFogFactor () // verkrijgt afstand van camera tot vertex float fogDistance = gl_FragCoord.z / gl_FragCoord.w; float fogCoeff = 1.0; float fogStart = vFogInfos.y; float fogEnd = vFogInfos.z; float fogDensity = vFogInfos.w; if (FOGMODE_LINEAR == vFogInfos.x) fogCoeff = (fogEnd - fogDistance) / (fogEnd - mistStart); else if (FOGMODE_EXP == vFogInfos.x) fogCoeff = 1.0 / pow (E, fogDistance * fogDensity); else if (FOGMODE_EXP2 == vFogInfos.x) fogCoeff = 1.0 / pow (E, fogDistance * fogDistance * fogDensity * fogDensity); retourklem (fogCoeff, 0.0, 1.0); void main (void) vec4 baseColor = texture2D (baseSampler, vUV); vec2 blendUV = vec2 (vUV.x * blendScaleU, vUV.y * blendScaleV); vec4 blendColor = texture2D (blendSampler, blendUV); // vermenigvuldigingstype overvloeimodus vec4 color = baseColor * blendColor; // factor in mistkleur float fog = calcFogFactor (); color.rgb = fog * color.rgb + (1.0 - mist) * vFogColor; gl_FragColor = kleur;
Het laatste stukje voor onze aangepaste Blend-shader is de JavaScript-code die door Babylon wordt gebruikt. Het primaire doel van deze code is om de parameters voor te bereiden die worden doorgegeven aan onze vertex- en pixel shaders.
functie BlendMaterial (naam, scène, opties) this.name = name; this.id = naam; this.options = opties; this.blendScaleU = options.blendScaleU || 1; this.blendScaleV = options.blendScaleV || 1; this._scene = scène; scene.materials.push (deze); var assets = options.assetManager; var textureTask = assets.addTextureTask ('blend-material-base-task', options.baseImage); textureTask.onSuccess = _.bind (function (task) this.baseTexture = task.texture; this.baseTexture.uScale = 1; this.baseTexture.vScale = 1; if (options.baseHasAlpha) this.baseTexture.hasAlpha = waar;, dit); textureTask = assets.addTextureTask ('blend-material-blend-task', options.blendImage); textureTask.onSuccess = _.bind (function (task) this.blendTexture = task.texture; this.blendTexture.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE; this.blendTexture.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;, this); BlendMaterial.prototype = Object.create (BABYLON.Material.prototype); BlendMaterial.prototype.needAlphaBlending = function () return (this.options.baseHasAlpha === true); ; BlendMaterial.prototype.needAlphaTesting = function () return false; ; BlendMaterial.prototype.isReady = function (mesh) var engine = this._scene.getEngine (); // zorg ervoor dat texturen gereed zijn als (! this.baseTexture ||! this.blendTexture) return false; if (! this._effect) this._effect = engine.createEffect (// shadernaam "blend", // attributen die topologie van hoekpunten beschrijven ["position", "normal", "uv"], // uniformen ( externe variabelen) gedefinieerd door de shaders ["worldViewProjection", "world", "blendScaleU", "blendScaleV", "vFogInfos", "vFogColor"], // samplers (objecten gebruikt om texturen te lezen) ["baseSampler", "blendSampler "], // optionele tekenreeks definiëren" "); if (! this._effect.isReady ()) return false; return true; ; BlendMaterial.prototype.bind = function (world, mesh) var scene = this._scene; this._effect.setFloat4 ("vFogInfos", scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity); this._effect.setColor3 ("vFogColor", scene.fogColor); this._effect.setMatrix ("wereld", wereld); this._effect.setMatrix ("worldViewProjection", world.multiply (scene.getTransformMatrix ())); // Textures this._effect.setTexture ("baseSampler", this.baseTexture); this._effect.setTexture ("blendSampler", this.blendTexture); this._effect.setFloat ("blendScaleU", this.blendScaleU); this._effect.setFloat ("blendScaleV", this.blendScaleV); ; BlendMaterial.prototype.dispose = function () if (this.baseTexture) this.baseTexture.dispose (); if (this.blendTexture) this.blendTexture.dispose (); this.baseDispose (); ;
Babylon.js maakt het eenvoudig om een aangepast op shader gebaseerd materiaal te maken. Ons mengmateriaal is relatief eenvoudig, maar het maakte echt een groot verschil in het uiterlijk van het eiland toen het vliegtuig laag op de grond vloog. Shaders brengen de kracht van de GPU naar de browser en breiden de soorten creatieve effecten uit die u op uw 3D-scènes kunt toepassen. In ons geval was dat de finishing touch!
Microsoft heeft veel gratis leren over veel open-source JavaScript-onderwerpen en we zijn op een missie om nog veel meer met Microsoft Edge te maken. Hier zijn enkele om te bekijken:
En enkele gratis tools om aan de slag te gaan: Visual Studio Code, Azure Trial en testtools voor meerdere browsers, allemaal beschikbaar voor Mac, Linux of Windows.
Dit artikel maakt deel uit van de web dev tech-serie van Microsoft. We zijn verheugd om te delen Microsoft Edge en het nieuwe EdgeHTML-renderingengine met jou. Download gratis virtuele machines of test op afstand op uw Mac, iOS, Android of Windows-apparaat @ http://dev.modern.ie/.