WebGL
Børre Stenseth
Basis >Torus

En smultring

Hva

Vi skal lage en gul smultring som roterer.

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

Torus geometri

Vi lar w beskrive rotasjonen om selve smultringens hovedakse, "hullet", og lar v beskrive rotasjonen rundt selve smultringkroppen. Vi tegner smultringen i to projeksjoner:

torus

Ved å studere de to projeksjonene kan vi overbevise oss om at punktet p's koodinater er:

  z=r.sin(v)
  y=(R+r·cos(v))sin(w)
  x=(R+r·cos(v))cos(w)
  

Vi kan altså beskrive et vilket som helst punkt på smultringensoverflate ved de tre parametriske ligningene ovenfor. Da er vi også i stand til å avgrense flater på smultringoverflaten med nødvendig presisjon.

Flatene i smultringen kan genereres som polygoner der vi lar de to rotasjonsvinkelene gjennomløpe en full sirkel i en dobbeltløkke.

Vi ser videre at vi kan beregne normalen i et hvert punkt på torusen ved å introdusere en "omgivende" torus med radius rr. Normalen blir vektoren fra den indre til den ytre, gitt samme w og v.

dobeltorus

Javascript

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

_torusscript.js

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

Følgende globale variable er definert.

var canvas;
var gl;
var torusVerticesBuffer;
var torusVerticesIndexBuffer;
var torusVerticesColorBuffer;
var torusVerticesNormalBuffer;
var torusRotation = 0.0;
var lastTorusUpdateTime = 0;
var mvMatrix;
var perspectiveMatrix;
var shaderProgram;
var vertexPositionAttribute;
var vertexNormalAttribute;
var vertexColorAttribute;
// torus drawing precision
var N=40;
var n=40;

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); 
      
    initShaders();     
    initBuffers();      
    setInterval(drawScene, 15);
        
    // 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() {
  // produce thre arrays:
  var vertices=[]; //each points coordinates
  var normals=[];  //each points normal
  var indices=[];  //each drawobjects index to points (Gl_TIANGLE_STRIPS)
  
  // dimensions
  var R=5.0;
  var r=1.0;
  var rr=1.5*r; 
  // as we go
  var dw=2*Math.PI/(1.0*N);
  var dv=2*Math.PI/(1.0*n);
  var v=0.0;
  var w=0.0;
  var index=0;
  
  // outer loop
  while(w<2*Math.PI+dw)
  {
        v=0.0;
        // inner loop
        while(v<2*Math.PI+dv)
        {
            normals=
            normals.concat([(R+rr*Math.cos(v))*Math.cos(w)-(R+r*Math.cos(v))*Math.cos(w),
                            (R+rr*Math.cos(v))*Math.sin(w)-(R+r*Math.cos(v))*Math.sin(w),
                            (rr*Math.sin(v)-r*Math.sin(v))]);
            vertices=
            vertices.concat([(R+r*Math.cos(v))*Math.cos(w),
                             (R+r*Math.cos(v))*Math.sin(w),
                             r*Math.sin(v)]);
            normals=
            normals.concat([(R+rr*Math.cos(v+dv))*Math.cos(w+dw)-(R+r*Math.cos(v+dv))*Math.cos(w+dw),
                            (R+rr*Math.cos(v+dv))*Math.sin(w+dw)-(R+r*Math.cos(v+dv))*Math.sin(w+dw),
                             rr*Math.sin(v+dv)-r*Math.sin(v+dv)]);
            vertices=
            vertices.concat([(R+r*Math.cos(v+dv))*Math.cos(w+dw),
                             (R+r*Math.cos(v+dv))*Math.sin(w+dw),
                             r*Math.sin(v+dv)]);
            
            indices=indices.concat(index++);
            indices=indices.concat(index++);            
            v+=dv;
        }        
        w+=dw;
  }
  
  // Buffer for the torus's vertices.  
  torusVerticesBuffer = gl.createBuffer(); 
  gl.bindBuffer(gl.ARRAY_BUFFER, torusVerticesBuffer); 
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  
 // Normals for each vertex
 torusVerticesNormalBuffer = gl.createBuffer();
 gl.bindBuffer(gl.ARRAY_BUFFER, torusVerticesNormalBuffer);
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
      
  // Specify the indices into the vertex array for each face's vertices. 
  torusVerticesIndexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, torusVerticesIndexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,new Uint16Array(indices), gl.STATIC_DRAW);
  
  //colors is not set, uniform color as in fragment shader 
}

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.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]);
  
  // Save the current matrix, then rotate before we draw. 
  mvPushMatrix();
  mvRotate(torusRotation, [1, 0, 1]);
   
  // Draw the torus by binding the array buffer to the torus's vertices
  // array, setting attributes, and pushing it to GL. 
  gl.bindBuffer(gl.ARRAY_BUFFER, torusVerticesBuffer);
  gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
   
  // Set the normal attribute for the vertices. 
  gl.bindBuffer(gl.ARRAY_BUFFER, torusVerticesNormalBuffer);
  gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0);
  
  // tell the shader about transformation status
  setMatrixUniforms();
 
  // Draw the torus.
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, torusVerticesIndexBuffer); 
  gl.drawElements(gl.TRIANGLE_STRIP, (N+1)*(n+1)*2, gl.UNSIGNED_SHORT, 0);
 
  // Restore the original matrix  
  mvPopMatrix();
  
  // Update the rotation for the next draw, if it's time to do so.
  var currentTime = (new Date).getTime();
  if (lastTorusUpdateTime) {
    var delta = currentTime - lastTorusUpdateTime;    
    torusRotation += (30 * delta) / 1000.0;
  }  
  lastTorusUpdateTime = currentTime;
}

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);
    
    // Apply 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(1.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/torus/simple.html

[3]
Referanser
  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
  1. Sylvester sylvester.jcoglan.com/ 14-05-2011
Basis >Torus