KEMBAR78
HTML5 game dev with three.js - HexGL | PDF
The making of HexGL
• Thibaut Despoulain (@bkcore – bkcore.com)

• 22 year-old student in Computer Engineering

• University of Technology of Belfort-Montbéliard (France)

• Web dev and 3D enthousiast

• The guy behind the HexGL project
• Fast-paced, futuristic racing game

• Inspired by the F-Zero and Wipeout series

• HTML5, JavaScript, WebGL (via Three.js)

• Less than 2 months

• Just me.
• JavaScript API

• OpenGL ES 2.0

• Chrome, FireFox, (Opera, Safari)

• <Canvas> (HTML5)
• Rendering engine
• Maintained by Ricardo Cabello (MrDoob) and Altered Qualia
• R50/stable

• + : Active community, stable, updated frequently

• - : Documentation
• First « real » game

• 2 months to learn and code

• Little to no modeling and texturing skills

• Physics? Controls? Gameplay?
• Last time I could have 2 months free

• Visibility to get an internship

• Huge learning opportunity

• Explore Three.js for good
• Third-party physics engine (rejected)
   – Slow learning curve

   – Not really meant for racing games
• Ray casting (rejected)
   – Heavy perfomance-wise

   – Needs Octree-like structure

   – > too much time to learn and implement
• Home-made 2D approximation
  – Little to no learning curve

  – Easy to implement with 2D maps

  – Pretty fast

  – > But with some limitations
• Home-made 2D approximation
  – No track overlap

  – Limited track twist and gradient

  – Accuracy depends on map resolution

  – > Enough for what I had in mind
• No pixel getter on JS Image object/tag

• Canvas2D to the rescue
Load data                   Draw it on a             Get canvas


                            Drawing




                                                     Getting
Loading




          texture with JS             Canvas using             pixels using
          Image object                2D context               getImageData()
• ImageData (BKcore package)
  – Github.com/Bkcore/bkcore-js

var a = new bkcore.ImageData(path, callback);
//…
a.getPixel(x, y);
a.getPixelBilinear(xf, yf);
// -> {r, g, b, a};
Game loop:
  Convert world position to pixel indexes
  Get current pixel intensity (red)
  If pixel is not white:
      Collision
      Test pixels relatively (front, left, right)
:end
Front


Left            Right




Track             Void
Front


         Front
                                               Current
                                    Gradient
Left                Right    Left



                                                Right
       Height map                     Tilt
Model
  .OBJ               Python               Three.js
Materials           converter           JSON model
 .MTL

 $ python convert_obj_three.py -i mesh.obj -o mesh.js
var scene = new THREE.Scene();
var loader = new THREE.JSONLoader();

var createMesh = function(geometry)
{
   var mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial());
   mesh.position.set(0, 0, 0);
   mesh.scale.set(3, 3, 3);
   scene.add(mesh);
};

loader.load("mesh.js", createMesh);
var renderer = new THREE.WebGLRenderer({
  antialias: false,
  clearColor: 0x000000
});


renderer.autoClear = false;
renderer.sortObjects = false;
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
renderer.gammaInput = true;

renderer.gammaOutput = true;



renderer.shadowMapEnabled = true;

renderer.shadowMapSoft = true;
• Blinn-phong
   – Diffuse + Specular + Normal + Environment maps

• THREE.ShaderLib.normal
   – > Per vertex point lights

• Custom shader with per-pixel point lights for the road
   – > Booster light
var boosterSprite = new THREE.Sprite(
{
  map: spriteTexture,
  blending: THREE.AdditiveBlending,
  useScreenCoordinates: false,
  color: 0xffffff
});

boosterSprite.mergeWith3D = false;
boosterMesh.add(boosterSprite);
var material = new THREE.ParticleBasicMaterial({
  color: 0xffffff,
  map: THREE.ImageUtils.loadTexture(“tex.png”),
  size: 4,
  blending: THREE.AdditiveBlending,
  depthTest: false,
  transparent: true,
  vertexColors: true,
  sizeAttenuation: true
});
var pool = [];
var geometry = new THREE.Geometry();
geometry.dynamic = true;

for(var i = 0; i < 1000; ++i)
{
   var p = new bkcore.Particle();
   pool.push(p);
   geometry.vertices.push(p.position);
   geometry.colors.push(p.color);
}
bkcore.Particle = function()
{
  this.position = new THREE.Vector3();
  this.velocity = new THREE.Vector3();
  this.force = new THREE.Vector3();
  this.color = new THREE.Color(0x000000);
  this.basecolor = new THREE.Color(0x000000);
  this.life = 0.0;
  this.available = true;
}
var system = new THREE.ParticleSystem(
   geometry,
   material
);
system.sort = false;

system.position.set(x, y, z);
system.rotation.set(a, b, c);

scene.add(system);
// Particle physics
var p = pool[i];
p.position.addSelf(p.velocity);
//…
geometry.verticesNeedUpdate = true;
geometry.colorsNeedUpdate = true;
• Particles (BKcore package)
   – Github.com/BKcore/Three-extensions
var clouds = new bkcore.Particles({
  opacity: 0.8,
  tint: 0xffffff, color: 0x666666, color2: 0xa4f1ff,
  texture: THREE.ImageUtils.loadTexture(“cloud.png”),
  blending: THREE.NormalBlending,
  size: 6, life: 60,      max: 500,
  spawn: new THREE.Vector3(3, 3, 0),
  spawnRadius: new THREE.Vector3(1, 1, 2),
  velocity: new THREE.Vector3(0, 0, 4),
  randomness: new THREE.Vector3(5, 5, 1)
});
scene.add(clouds);

// Game loop
clouds.emit(10);
clouds.update(dt);
• Built-in support for off-screen passes

• Already has some pre-made post effects
   – Bloom

   – FXAA

• Easy to use and Extend

• Custom shaders
var renderTargetParameters = {
   minFilter: THREE.LinearFilter,
   magFilter: THREE.LinearFilter,
   format: THREE.RGBFormat,
   stencilBuffer: false
};
var renderTarget = new THREE.WebGLRenderTarget(
   width, height,
   renderTargetParameters
);
var composer = new THREE.EffectComposer(
   renderer,
   renderTarget
);

composer.addPass( … );

composer.render();
• Generic passes
   –   RenderPass
   –   ShaderPass
   –   SavePass
   –   MaskPass

• Pre-made passes
   – BloomPass
   – FilmPass
   – Etc.
var renderModel = new THREE.RenderPass(
   scene,
   camera
);

renderModel.clear = false;

composer.addPass(renderModel);
var effectBloom = new THREE.BloomPass(
   0.8, // Strengh
   25, // Kernel size
   4, // Sigma
   256 // Resolution
);

composer.addPass(effectBloom);
var hexvignette: {
    uniforms: {
          tDiffuse: { type: "t", value: 0, texture: null },
          tHex: { type: "t", value: 1, texture: null},
          size: { type: "f", value: 512.0},
          color: { type: "c", value: new THREE.Color(0x458ab1) }
    },
    fragmentShader: [
          "uniform float size;",
          "uniform vec3 color;",
          "uniform sampler2D tDiffuse;",
          "uniform sampler2D tHex;",

          "varying vec2 vUv;",

          "void main() { ... }"

     ].join("n")
};
var effectHex = new THREE.ShaderPass(hexvignette);

effectHex.uniforms['size'].value = 512.0;
effectHex.uniforms['tHex'].texture = hexTexture;

composer.addPass(effectHex);

//…
effectHex.renderToScreen = true;
HTML5 game dev with three.js - HexGL
HTML5 game dev with three.js - HexGL
HTML5 game dev with three.js - HexGL

HTML5 game dev with three.js - HexGL

  • 1.
  • 2.
    • Thibaut Despoulain(@bkcore – bkcore.com) • 22 year-old student in Computer Engineering • University of Technology of Belfort-Montbéliard (France) • Web dev and 3D enthousiast • The guy behind the HexGL project
  • 4.
    • Fast-paced, futuristicracing game • Inspired by the F-Zero and Wipeout series • HTML5, JavaScript, WebGL (via Three.js) • Less than 2 months • Just me.
  • 9.
    • JavaScript API •OpenGL ES 2.0 • Chrome, FireFox, (Opera, Safari) • <Canvas> (HTML5)
  • 11.
    • Rendering engine •Maintained by Ricardo Cabello (MrDoob) and Altered Qualia • R50/stable • + : Active community, stable, updated frequently • - : Documentation
  • 15.
    • First «real » game • 2 months to learn and code • Little to no modeling and texturing skills • Physics? Controls? Gameplay?
  • 16.
    • Last timeI could have 2 months free • Visibility to get an internship • Huge learning opportunity • Explore Three.js for good
  • 18.
    • Third-party physicsengine (rejected) – Slow learning curve – Not really meant for racing games
  • 19.
    • Ray casting(rejected) – Heavy perfomance-wise – Needs Octree-like structure – > too much time to learn and implement
  • 20.
    • Home-made 2Dapproximation – Little to no learning curve – Easy to implement with 2D maps – Pretty fast – > But with some limitations
  • 23.
    • Home-made 2Dapproximation – No track overlap – Limited track twist and gradient – Accuracy depends on map resolution – > Enough for what I had in mind
  • 25.
    • No pixelgetter on JS Image object/tag • Canvas2D to the rescue
  • 26.
    Load data Draw it on a Get canvas Drawing Getting Loading texture with JS Canvas using pixels using Image object 2D context getImageData()
  • 27.
    • ImageData (BKcorepackage) – Github.com/Bkcore/bkcore-js var a = new bkcore.ImageData(path, callback); //… a.getPixel(x, y); a.getPixelBilinear(xf, yf); // -> {r, g, b, a};
  • 28.
    Game loop: Convert world position to pixel indexes Get current pixel intensity (red) If pixel is not white: Collision Test pixels relatively (front, left, right) :end
  • 29.
    Front Left Right Track Void
  • 30.
    Front Front Current Gradient Left Right Left Right Height map Tilt
  • 34.
    Model .OBJ Python Three.js Materials converter JSON model .MTL $ python convert_obj_three.py -i mesh.obj -o mesh.js
  • 35.
    var scene =new THREE.Scene(); var loader = new THREE.JSONLoader(); var createMesh = function(geometry) { var mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial()); mesh.position.set(0, 0, 0); mesh.scale.set(3, 3, 3); scene.add(mesh); }; loader.load("mesh.js", createMesh);
  • 37.
    var renderer =new THREE.WebGLRenderer({ antialias: false, clearColor: 0x000000 }); renderer.autoClear = false; renderer.sortObjects = false; renderer.setSize(width, height); document.body.appendChild(renderer.domElement);
  • 38.
    renderer.gammaInput = true; renderer.gammaOutput= true; renderer.shadowMapEnabled = true; renderer.shadowMapSoft = true;
  • 40.
    • Blinn-phong – Diffuse + Specular + Normal + Environment maps • THREE.ShaderLib.normal – > Per vertex point lights • Custom shader with per-pixel point lights for the road – > Booster light
  • 42.
    var boosterSprite =new THREE.Sprite( { map: spriteTexture, blending: THREE.AdditiveBlending, useScreenCoordinates: false, color: 0xffffff }); boosterSprite.mergeWith3D = false; boosterMesh.add(boosterSprite);
  • 44.
    var material =new THREE.ParticleBasicMaterial({ color: 0xffffff, map: THREE.ImageUtils.loadTexture(“tex.png”), size: 4, blending: THREE.AdditiveBlending, depthTest: false, transparent: true, vertexColors: true, sizeAttenuation: true });
  • 45.
    var pool =[]; var geometry = new THREE.Geometry(); geometry.dynamic = true; for(var i = 0; i < 1000; ++i) { var p = new bkcore.Particle(); pool.push(p); geometry.vertices.push(p.position); geometry.colors.push(p.color); }
  • 46.
    bkcore.Particle = function() { this.position = new THREE.Vector3(); this.velocity = new THREE.Vector3(); this.force = new THREE.Vector3(); this.color = new THREE.Color(0x000000); this.basecolor = new THREE.Color(0x000000); this.life = 0.0; this.available = true; }
  • 47.
    var system =new THREE.ParticleSystem( geometry, material ); system.sort = false; system.position.set(x, y, z); system.rotation.set(a, b, c); scene.add(system);
  • 48.
    // Particle physics varp = pool[i]; p.position.addSelf(p.velocity); //… geometry.verticesNeedUpdate = true; geometry.colorsNeedUpdate = true;
  • 49.
    • Particles (BKcorepackage) – Github.com/BKcore/Three-extensions
  • 50.
    var clouds =new bkcore.Particles({ opacity: 0.8, tint: 0xffffff, color: 0x666666, color2: 0xa4f1ff, texture: THREE.ImageUtils.loadTexture(“cloud.png”), blending: THREE.NormalBlending, size: 6, life: 60, max: 500, spawn: new THREE.Vector3(3, 3, 0), spawnRadius: new THREE.Vector3(1, 1, 2), velocity: new THREE.Vector3(0, 0, 4), randomness: new THREE.Vector3(5, 5, 1) });
  • 51.
  • 53.
    • Built-in supportfor off-screen passes • Already has some pre-made post effects – Bloom – FXAA • Easy to use and Extend • Custom shaders
  • 54.
    var renderTargetParameters ={ minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBuffer: false }; var renderTarget = new THREE.WebGLRenderTarget( width, height, renderTargetParameters );
  • 55.
    var composer =new THREE.EffectComposer( renderer, renderTarget ); composer.addPass( … ); composer.render();
  • 56.
    • Generic passes – RenderPass – ShaderPass – SavePass – MaskPass • Pre-made passes – BloomPass – FilmPass – Etc.
  • 57.
    var renderModel =new THREE.RenderPass( scene, camera ); renderModel.clear = false; composer.addPass(renderModel);
  • 58.
    var effectBloom =new THREE.BloomPass( 0.8, // Strengh 25, // Kernel size 4, // Sigma 256 // Resolution ); composer.addPass(effectBloom);
  • 60.
    var hexvignette: { uniforms: { tDiffuse: { type: "t", value: 0, texture: null }, tHex: { type: "t", value: 1, texture: null}, size: { type: "f", value: 512.0}, color: { type: "c", value: new THREE.Color(0x458ab1) } }, fragmentShader: [ "uniform float size;", "uniform vec3 color;", "uniform sampler2D tDiffuse;", "uniform sampler2D tHex;", "varying vec2 vUv;", "void main() { ... }" ].join("n") };
  • 61.
    var effectHex =new THREE.ShaderPass(hexvignette); effectHex.uniforms['size'].value = 512.0; effectHex.uniforms['tHex'].texture = hexTexture; composer.addPass(effectHex); //… effectHex.renderToScreen = true;