WebGL
Børre Stenseth
Basis >Android figuren

Android

Hva

Vi skal lage en enkel animert utgave av Android-ikonet, med vekt på basisgeometrien. I modulen Forbedret skal vi se nærmere på noen detaljer og vi skal se på materialegenskaper.

Det kan være lurt å kikke på OpenGL Shading Language (GLSL) Reference Pages [2] for å få en forklaring av de enkelte metodene i shading-eksemplene og OpenGL ES 2.0 Reference Pages [3] for å få en forklaring av de enkelte metodene i javascriptet.

Android geometri

android

Hvis vi betrakter denne figuren, ser vi at den kan settes sammen av noen få basisformer: sylinder, sirkelflate og halvkule. Vi baserer oss på dette når vi skal produsere figuren.

Vi lager oss en klasse for hver av de tre grunnformene. Når klassen initialiseres lager vi de bufferdata vi trenger. Når figuren tegnes, kopler vi til dataene og sender dem til vertex-fragmentet. Klassen for en sylinder ser slik ut:

_cylinder

For en halvkule (hat)

_hat

For en sirkelflate, en disk

_disk

Javascript

Javascriptet som handterer vår tegning er inkludert som egen fil. De viktigste delene av denne koden er kommentert nedenfor. Hele fila ser slik ut:

_androidscript.js

Det kan være lurt å kikke på OpenGL ES 2.0 Reference Pages [3] for å få en forklaring av de enkelte metodene.

Følgende globale variable er definert.

var canvas;
var gl;
var mvMatrix;
var perspectiveMatrix;
var shaderProgram;
// sizes
var bodyLength=3.0;
var bodyRadius=1.5;
var limbLength=1.7;
var limbRadius=0.4;
// drawing precision
var Precision=30;
//shapes
var aCylinder;
var aDisk;
var aHat;
var aSmallCylinder;
var aSmallDisk;
var aSmallHat;
// animation
var sceneRotation = 0.0;
var lastUpdateTime = 0;
var armswing=0.0;
var deltaArmswing=1.5;
var maxArmswing=50.0;
var talkSwing=0.0;
var talkCount=0;
var deltaTalkCount=1;
// camera
var viewAspect=1.0;

Funkjsonen start() kalles typisk enten ved body onload, eller ved et script i bunnen (etter canvas) i selve websiden. I dette eksempelet er det denne funksjone som drar hele jobben.

function start() {
    canvas = document.getElementById("glcanvas");
    initWebGL(canvas); 
    if (gl) {
        gl.clearColor(1.0, 1.0, 1.0, 1.0); 
        gl.clearDepth(1.0); 
        gl.enable(gl.DEPTH_TEST); 
        gl.depthFunc(gl.LEQUAL);       
        initBuffers();      
        initShaders();  
        gl.clearColor(1.0, 1.0, 1.0, 1.0);
        gl.enable(gl.DEPTH_TEST);    
        tick();
        
    // fill window, and draw
    onWindowResize();
    // pick up resizing
    window.addEventListener( 'resize', onWindowResize, false );
    }
    if (!gl) {
        // show captures instead
        showCaptureInstead();
    }
}

Funksjonen initWebGL() forsøker å sette opp WebGL i canvas-elementet. Merk at konstanten "experimental-webgl" skal bytte sut med "webgl" etterhvert som denne teknologien modnes hos nettleserne.

_initWebGL

I funksjonen initBuffers() setter vi opp de punktene som beskriver modellen vår og som etterhvert skal sendes til vertex-shaderen.

function initBuffers() {
    // set up all shapes
    aCylinder=new Cylinder(bodyRadius,bodyLength,Precision);
    aDisk=new Disk(bodyRadius,Precision);
    aHat=new Hat(bodyRadius,Precision);
    
    aSmallCylinder=new Cylinder(limbRadius,limbLength,Precision);
    aSmallDisk=new Disk(limbRadius,Precision);
    aSmallHat=new Hat(limbRadius,Precision);
}

Funksjonen drawScene() skiller seg fra det vi kjenner fra tradisjonell OpenGL på den måten at vi ikke lenger "tegner" hjørne for hjørne. I stedet sender vi en punktliste til shaderen, sammen med de to matrisene (modelview og perspective). Deretter starter vi tegningen med gl.drawArrays

function drawScene() {
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);  
    perspectiveMatrix = 
      makePerspective(45, viewAspect, 0.1, 100.0);
    loadIdentity();
    // Move the drawing a bit from default eye-pos
    mvTranslate([-0.0, 0.0, -16.0]);
    mvRotate(sceneRotation, [1, 0, 1]);
    mvPushMatrix();
    // Draw the main body.
    //---------------
    setMatrixUniforms();  
    aCylinder.draw();
    // Draw the disk in cylinder ends
    aDisk.draw();  
    mvTranslate([0.0, 0.0, bodyLength]); 
    setMatrixUniforms();
    aDisk.draw(); 
    // draw head top 
    // talk
    mvRotate(talkSwing,[1.0, 0.0, 0.0]);
    mvTranslate([0.0, 0.0, 0.07+0.05*talkSwing]); 
    setMatrixUniforms();
    aDisk.draw();
    aHat.draw();
    // Draw arms
    //-------------
    mvPopMatrix();
    mvPushMatrix();
    mvTranslate([bodyRadius+limbRadius+0.1,0.0,bodyLength-limbRadius]);
    drawLimb(-armswing)
    mvPopMatrix();
    mvPushMatrix();
    mvTranslate([-(bodyRadius+limbRadius+0.1),0.0,bodyLength-limbRadius]);
    drawLimb(armswing);
    // Draw legs
    //-----------
    mvPopMatrix();
    mvPushMatrix();
    mvTranslate([(bodyRadius-limbRadius*2),0.0,0.2]);
    drawLimb(armswing);
    mvPopMatrix();
    mvPushMatrix();
    mvTranslate([-(bodyRadius-limbRadius*2),0.0,0.2]);
    drawLimb(-armswing);
    mvPopMatrix();
    // Update the rotation for the next draw
    var currentTime = (new Date).getTime();
    if (lastUpdateTime) {
        var delta = currentTime - lastUpdateTime;    
        sceneRotation += (30 * delta) / 1000.0;
    }  
    lastUpdateTime = currentTime;
    armswing=armswing+deltaArmswing;
    if ((armswing > maxArmswing)||(armswing < -maxArmswing))
    deltaArmswing=-deltaArmswing;
    talkCount=talkCount+deltaTalkCount;
    talkSwing=0.2*talkCount;
    if(talkCount > 40)
    deltaTalkCount=-1;
    else if(talkCount <1)
    deltaTalkCount=1;
}

Hjelpefunksjonen drawLimb er definert slik:

function drawLimb(swing)
{
    // arm or a leg
    pushMatrix();
    mvRotate(swing,[1.0,0.0,0.0]);
    setMatrixUniforms();
    aSmallHat.draw();
    popMatrix();
    pushMatrix();
    mvRotate(180.0+swing,[1.0,0.0,0.0]);  
    setMatrixUniforms();
    aSmallCylinder.draw();
    mvTranslate([0.0,0.0,limbLength]);
    setMatrixUniforms();
    aSmallHat.draw();
    popMatrix();
}

Funksjonen setMatrixUniforms() setter status på transformasjonsmatriser til shaderen.

_setMatrixUniforms

De to funksjonene initShaders() og getShader() laster inn shaderne, kompilerer dem og etablerer shaderprogrammet.

_initShaders

_getShader

Fragment shader

Fragment shaderen arbeider med en farge og en lysretning. Begge er satt fra vertex shader. Merk at de har kvalifiseringen varying.

varying mediump vec3 vLighting;
varying lowp vec4 vColor;           
void main(void) {
    gl_FragColor = vec4(vColor.rgb * vLighting, vColor.a);
}

Vertex shader

De tre variablene: uMVMatrix, uPMatrix og uNormalMatrix blir satt fra Javascriptkoden. aVertexPosition representerer det aktuelle punktet, slik det er ordnet i en buffer fra Javascriptet og aVertexNormal er normalen i punktet.

Merk at hele lyssettingen etableres i shaderen.

attribute mediump vec3 aVertexNormal; // or in
attribute mediump vec3 aVertexPosition;
uniform mediump mat4 uNormalMatrix;
uniform mediump mat4 uMVMatrix;
uniform mediump mat4 uPMatrix;
varying lowp vec4 vColor;
varying mediump vec3 vLighting;
void main(void) {
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
    // Lighting effect        
    mediump vec3 ambientLight = vec3(0.6, 0.6, 0.6);
    mediump vec3 directionalLightColor = vec3(0.5, 0.5, 0.75);
    mediump vec3 directionalVector = vec3(0.85, 0.8, 0.75);
    mediump vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0);
    mediump float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0);
    vLighting = ambientLight + (directionalLightColor * directional);
    // set color
    vColor=vec4(0.0,  1.0,  0.0,  1.0);
}

Dette er resultatet lagt i en iframe:

Du kan også inspisere resultatet og kildekoden på en enklere side:
simple.html https://borres.hiof.no/wep/webgl/basis/android/simple.html

Ikke veldig imponerende, men vi har fått basisgeometrien på plass. I den neste modulen skal vi forsøke å få litt orden på materialer, og vi skal få med øyne og "følehorn".

Referanser
  1. Sylvester sylvester.jcoglan.com/ 14-05-2011
  1. OpenGL Shading Language (GLSL) Reference Pages Opengl.org www.opengl.org/sdk/docs/manglsl/ 14-05-2011
  1. OpenGL ES Software Development Kit Khronos Group www.khronos.org/opengles/sdk/docs/man/ 14-05-2011
Basis >Android figuren