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:
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.
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:
//globals
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;
//eofglobals
//start
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();
}
}
//eofstart
//initWebGL
function initWebGL() {
gl = null;
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
for(var i = 0; i < names.length; i++){
try {gl = canvas.getContext("experimental-webgl");}
catch(e) {}
if(gl){break;}
}
if (!gl) {
// show captures instead
showCaptureInstead();
}
}
//eofinitWebGL
//initBuffers
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
}
//eofinitBuffers
//drawScene
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;
}
//eofdrawScene
//initShaders
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
// Create the shader program
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// ok?
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Unable to initialize the shader program.");
}
gl.useProgram(shaderProgram);
// connect shader attribute to buffers.
// attributes, understood as attributes to verteces
vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal");
gl.enableVertexAttribArray(vertexNormalAttribute);
}
//eofinitShaders
//getShader
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
// ok?
if (!shaderScript) {
return null;
}
// Building the shader source string.
var theSource = "";
var currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == 3) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
// What type of shader, based on its MIME type.
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null; // Unknown shader type
}
// Send the source to the shader object
gl.shaderSource(shader, theSource);
// Compile the shader program
gl.compileShader(shader);
// See if it compiled successfully
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
//eofgetShader
// Matrix utility functions
//
function loadIdentity() {
mvMatrix = Matrix.I(4);
}
function multMatrix(m) {
mvMatrix = mvMatrix.x(m);
}
function mvTranslate(v) {
multMatrix(Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4());
}
//setMatrixUniforms
function setMatrixUniforms() {
var pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten()));
var mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten()));
var normalMatrix = mvMatrix.inverse();
normalMatrix = normalMatrix.transpose();
var nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix");
gl.uniformMatrix4fv(nUniform, false, new Float32Array(normalMatrix.flatten()));
}
//eofsetMatrixUniforms
var mvMatrixStack = [];
function mvPushMatrix(m) {
if (m) {
mvMatrixStack.push(m.dup());
mvMatrix = m.dup();
} else {
mvMatrixStack.push(mvMatrix.dup());
}
}
function mvPopMatrix() {
if (!mvMatrixStack.length) {
throw("Can't pop from an empty matrix stack.");
}
mvMatrix = mvMatrixStack.pop();
return mvMatrix;
}
function mvRotate(angle, v) {
var inRadians = angle * Math.PI / 180.0;
var m = Matrix.Rotation(inRadians, $V([v[0], v[1], v[2]])).ensure4x4();
multMatrix(m);
}
//resizing
function onWindowResize() {
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;
viewAspect=1.0*window.innerWidth / window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
// if not animated: drawScene();
}
//eofresizing
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.
function initWebGL() {
gl = null;
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
for(var i = 0; i < names.length; i++){
try {gl = canvas.getContext("experimental-webgl");}
catch(e) {}
if(gl){break;}
}
if (!gl) {
// show captures instead
showCaptureInstead();
}
}
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.
function setMatrixUniforms() {
var pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten()));
var mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten()));
var normalMatrix = mvMatrix.inverse();
normalMatrix = normalMatrix.transpose();
var nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix");
gl.uniformMatrix4fv(nUniform, false, new Float32Array(normalMatrix.flatten()));
}
De to funksjonene initShaders() og getShader() laster inn shaderne, kompilerer dem og etablerer shaderprogrammet.
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
// Create the shader program
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// ok?
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Unable to initialize the shader program.");
}
gl.useProgram(shaderProgram);
// connect shader attribute to buffers.
// attributes, understood as attributes to verteces
vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal");
gl.enableVertexAttribArray(vertexNormalAttribute);
}
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
// ok?
if (!shaderScript) {
return null;
}
// Building the shader source string.
var theSource = "";
var currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == 3) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
// What type of shader, based on its MIME type.
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null; // Unknown shader type
}
// Send the source to the shader object
gl.shaderSource(shader, theSource);
// Compile the shader program
gl.compileShader(shader);
// See if it compiled successfully
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
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