Tradisjonell OpenGL
Nedenfor finner du essensen i et Java-program som bruker OpenGL til å lage
figuren som er vist øverst på siden.
Dette er klippet fra en løsning som lager tegningen i et eget vindu og det finnes altså ikke
noen kopling til noe canvas-element.
public void init(GLAutoDrawable drawable) {
GL gl = drawable.getGL();
System.err.println("INIT GL IS: " + gl.getClass().getName());
gl.setSwapInterval(1);
gl.glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
gl.glShadeModel(GL.GL_SMOOTH);
}
public void reshape(GLAutoDrawable drawable,int x, int y, int width, int height)
{
GL gl = drawable.getGL();
GLU glu = new GLU();
if (height <= 0) {height = 1;}
final float h = (float) width / (float) height;
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glLoadIdentity();
glu.gluPerspective(45.0f, h, 1.0, 20.0);
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glLoadIdentity();
}
public void display(GLAutoDrawable drawable) {
GL gl = drawable.getGL();
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef(-0.0f, 0.0f, -6.0f);
gl.glBegin(GL.GL_QUADS);
gl.glColor3f(1.0f, 0.0f, 0.0f);
gl.glVertex3f(-1.0f, 1.0f, 0.0f);
gl.glVertex3f(1.0f, 1.0f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, 0.0f);
gl.glVertex3f(-1.0f, -1.0f, 0.0f);
gl.glEnd();
gl.glFlush();
}
OpenGL ES
Her må vi altså fordele logikken på flere kodesegmenter:
- Shading language kode for hvert fragment (~ pixel)
- Shading language kode for hvert hjørne, vertex
- JavaScript kode for selve organiseringen av tegningen
og vi må ha et canvas-element.
Shading language
Både fragment-shader og vertex-shader er plassert som kildekode på selve
websiden, i hver sin script-tag med mimetyper henholdsvis:
- type="x-shader/x-fragment"
- type="x-shader/x-vertex"
Fragment shader
Fragment shaderen er svært enkel: Den produserer et rødt, ikke gjennomsiktig, fragment uansett.
gl_FragColor er en predfinert variabel.
void main(void) {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
Vertex shader
De to variablene: uMVMatrix og uPMatrix blir satt fra
Javascriptkoden. aVertexPosition representerer det aktuelle punktet, slik det er ordnet
i en buffer fra Javascriptet. Det denne shaderen gjør er å ta det aktuelle punktet og
multiplisere den med de to matrisene som kontrollerer transformasjoner i selve modellen (uMVMatrix)
og perspektivet (uPMatrix). Resultatet av denne multiplikasjonen er punktet angitt i rommet, sett fra betrakteren.
I tradisjonell OpenGL-programmering heter disse to matrisene henholdsvis
GL_MODELVIEW og GL_PROJECTION. gl_Position er en predfinert variabel.
attribute vec3 aVertexPosition; // attribute deprecated use: in (when implemented)
// ModeView matrix
uniform mat4 uMVMatrix;
// Perspective matrix
uniform mat4 uPMatrix;
void main(void) {
// transformed position
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}
Javascript
Websiden bruker noen Javascriptbiblioteker for å håndtere matriser og vektorer,
Sylvester
[5]
og et enkelt tillegg til Sylvester som brukes av Mozillas WebGL sider
[2]
.
Ellers er
alle nødvendige biblioteker implementert som en integrert del av nettleseren,
altså ingen plug-ins.
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; // the canvas we use
var gl; // the gl-context we find in canvas
var squareVerticesBuffer; // the vertices of our square
var mvMatrix; // the modelview matrix
var perspectiveMatrix; // the perspective matrix
var shaderProgram; // the shaderprogram we build and compile
var vertexPositionAttribute;
var viewAspect=1.0;
//eofglobals
//start
function start() {
canvas = document.getElementById("glcanvas");
initWebGL(canvas);
if (gl) {
gl.clearColor(0.8, 0.8, 0.8, 1.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
initShaders();
initBuffers();
onWindowResize();
drawScene();
// pick up resize
window.addEventListener( 'resize', onWindowResize, false );
}
}
//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() {
// Create a buffer for the square's vertices.
squareVerticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
// Create an array of vertices for the square.(z-ccord=0.0)
var vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
}
//eofinitBuffers
//drawScene
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
perspectiveMatrix = makePerspective(45, viewAspect, 0.1, 100.0);
loadIdentity();
mvTranslate([-0.0, 0.0, -6.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
//"uPMatrix" and "uMVMatrix" identifies storage in vertex-shader
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()));
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
//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);
// link to shaderProgram, "aVertexPosition" is a storageidentifier in vertex shader
vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
}
//eofinitShaders
//getShader
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
// pick up source.
var theSource = "";
var currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == 3) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
// what type of shader script we have, 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
}
// Send the source to the shader object
gl.shaderSource(shader, theSource);
// Compile the shader program
gl.compileShader(shader);
// 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());
}
//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
[6]
for å få en forklaring av
de enkelte metodene.
Følgende globale variable er definert.
var canvas; // the canvas we use
var gl; // the gl-context we find in canvas
var squareVerticesBuffer; // the vertices of our square
var mvMatrix; // the modelview matrix
var perspectiveMatrix; // the perspective matrix
var shaderProgram; // the shaderprogram we build and compile
var vertexPositionAttribute;
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(0.8, 0.8, 0.8, 1.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
initShaders();
initBuffers();
onWindowResize();
drawScene();
// pick up resize
window.addEventListener( 'resize', onWindowResize, false );
}
}
Funkjsonen initWebGL() forsøker å sette opp WebGL i canvas-elementet. Merk at rekken av konstanter
som forsøkes anvendt i metoden getContext() skal standardiseres til "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() {
// Create a buffer for the square's vertices.
squareVerticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
// Create an array of vertices for the square.(z-ccord=0.0)
var vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
}
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();
mvTranslate([-0.0, 0.0, -6.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
//"uPMatrix" and "uMVMatrix" identifies storage in vertex-shader
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()));
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
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);
// link to shaderProgram, "aVertexPosition" is a storageidentifier in vertex shader
vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
}
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
// pick up source.
var theSource = "";
var currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == 3) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
// what type of shader script we have, 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
}
// Send the source to the shader object
gl.shaderSource(shader, theSource);
// Compile the shader program
gl.compileShader(shader);
// Compiled successfully ?
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
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/firkant/simple.html