En smultring
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 torus](torus.jpg)
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 dobeltorus](dobeltorus.gif)
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;
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); } }
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.
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, 640.0/480.0, 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.
De to funksjonene initShaders() og getShader() laster inn shaderne, kompilerer dem og etablerer shaderprogrammet.
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:
Du kan også inspisere resultatet og kildekoden på en enklere side:
index.html
http://www.it.hiof.no/~borres/dw/wgl/torus/index.html