Dette er ikke en pipe
Magritte malte (første gang) i 1926 et bilde som
har tiltrukket seg en del oppmerksomhet. Som du ser har han malt på bildet
en tekst "ceci n'est pas une pipe", eller på norsk "dette er ikke en pipe".
Når Magritte ble spurt om hvordan han kunne mene dette svarte han:
"Forsøk å stappe tobakk i den".
Det han forsøker å gjøre er altså å fokusere på forskjellen på en gjenstand
og framstillingen av den. Det ligger langt utenfor min kompetanse å forsøke og
forklare på en kort og konsistent måte surrealismens vesen og surrealistenes
argumentasjon. Slik jeg oppfatter det, anser de en naturalistisk framstilling som
lite interessant, og de forsøker å legge det "usynlige" inn i bildene.
Magrittes naturalistiske bilde, med den gitte teksten, er slik forstått å
understreke avstanden mellom bilde og "natur". De fleste av Magrittes bilder er ikke
av denne naturtro typen.
Tvert imot bruker han og andre surrealister argumentet til å endre den naturen han framstiller.
Søk på google med "surealisme".
Dette er ikke et bilde av en pipe
Hvis jeg nå, fra mitt teknologiske ståsted, vil ta dette et skritt videre
kan jeg kanskje påstå at
"dette er ikke et bilde av en pipe" (Ceci n'est pas une image d'une pipe).
Med dette forstår jeg da at det finnes ikke noe fysisk bilde av noe som ser slik ut
som det du ser på skjermen. Bildet skapes og gjenskapes hver gang vi ønsker å se det,
i en eller annen synsvinkel. På mange måter er dette en konkretisering av
den problemstillingen: Hva er egentlig et elektronisk dokument.
Så kan man jo spørre seg hva surrealistene midt i forrige århundre ville
ha laget dersom de kunne programmere
interaktive framstillinger. I vår tid er det ikke akkurat mangel på manipulerte
bilder med et visst "surrealistisk" preg.
Man kunne jo tenke seg at kommunikasjonsflaten mellom
kunstner og publikum ville tjene på en dialog. Eller kanskje
surrealismen ikke ville oppstått i den formen den fikk, dersom
teknologien og mediebildet hadde vært som idag ?
Det er en algoritme
Algoritmen som er brukt er basert på Bezier funksjoner og Frenet-frames,
se referanser under.
Vi kunne ha brukt mange andre teknikker ( og vi burde kanskje brukt en annen dersom det
hadde vært et mål å få vår pipe helt lik Magrittes pipe).
Bezier
Pipa er beskrevet ved 3 Bezier-funksjoner. Framstillingen er basert på at
pipas lengderetning ligger i YZ-planet. Bøyingen på pipa er en kubisk Bezierfunksjon, Bz.
Pipa er bygd opp av et antall ellipser tegnet rundt denne bezierfunksjonen. Radiene for hhv.
X og Y i ellipsen er beskrevet ved hver sin kubiske Bezierfunksjon, Bx og By.
Frenet Frames
Hver ellipse omkring Bz tegnes på samme måte, som en ellipse i XY-planet om kring Z-aksen.
Vi må bare sørge for å skifte koordinatsystem og radier. Frenet-frames sier oss hvordan vi kan
bytte koordinatsystem. Vi gjør dette ved å finne origo og de tre aktuelle aksene. I dette
tilfellet gjør vi det slik:
- Vi bestemmer origo i det nye systemet, C=Bz(t).
- Vi bestemmer Z-aksen i det nye koordinatsystemet, T=Bz'(t). Altså den deriverte til Bz.
- Vi bestemmer x-aksen i det nye koordinatsystemet som X=(1,0,0)
- Vi bestemmer y-aksen i det nye koordinatsystemet som Y=T x X (kryssproduktet)
En skisse av algoritmen blir slik
// generate the body along Bezier, as t ticks
// using sylvester.js
for (var tix=0;tix < TCOUNT; tix++)
{
// determine the coordinate system for next ellipse
var C=c.B(t); // actual origo as value of main Bezier,c
var T=c.dB(t); // tangent (derivative) to main Bezier,c, used as Z
T=T.toUnitVector();// normalize
// an other vector, use x which is always normal to derivative
var X=Vector.create([1.0,0.0,0.0]);
X=X.toUnitVector();// normalize
// a third vector, used as Y
var Y=B.cross(T);
// matrix that brings us to next coordinate system
var M=Matrix.create([
[X.e(1),Y.e(1),T.e(1),C.e(1)] ,
[X.e(2),Y.e(2),T.e(2),C.e(2)] ,
[X.e(3),Y.e(3),T.e(3),C.e(3)] ,
[0,0,0,1] ]
);
// radie
var rx=x.B(t).e(1)
var ry=y.B(t).e(2);
makeEllipse(M,rx,ry,.......);
t+=dt;
}
Javascript
Det er tre javascript involvert, foruten Sylvester og GLUtils:
pipe.js som drar dynamikke, curve.js som lager selve pipa og forbereder punkter, normaler og
materialer og endelig bezier.js som handterer en kubisk Bezierfunksjon.
//globals
var canvas;
var gl;
var mvMatrix;
var perspectiveMatrix;
var shaderProgram;
// textured square size
var SIZE=14;
//shapes
var aCurve;
var aTex;
//dynamic
var xRot=0.0;
var yRot=0.0;
var deltayRot=0.2;
var drag=0;
var xRot=0;
var yRot=0;
// camera
var viewAspect=1.0;
//eofglobals
//start
function start() {
canvas = document.getElementById("glcanvas");
initWebGL(canvas);
if (gl) {
gl.clearColor(0.922, 0.762, 0.484, 1.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
initBuffers();
initShaders();
initMousing();
tick();
// fill window, and draw
onWindowResize();
// pick up resizing
window.addEventListener( 'resize', onWindowResize, false );
}
if (!gl) {
// show captures instead
showCaptureInstead();
}
}
//eofstart
function tick() {
// using webgl-utils.js
requestAnimFrame(tick,canvas);
yRot+=deltayRot;
drawScene();
}
//initWebGL
function initWebGL() {
gl = null;
// browser and veesions reckognize webgl differently ?
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() {
// set up the curve described by 3 cubic Bezierfunctions
var zBez=new Bezier([1,4.0,0],[1,-4.5,2.5],[1,8.5,3.5],[1,6.0,8.0]);
var xBez=new Bezier([1.5,0,0],[2.1,0,0],[0.4,0,0],[0.3,0,0]);
var yBez=new Bezier([0,1.5,0],[0,2.1,0],[0,0.4,0],[0,0.1,0]);
aCurve=new Curve(zBez,xBez,yBez);
aTex=new Tex(SIZE);
aTex.initTexture('back.png');
}
//eofinitBuffers
//drawScene
function drawScene() {
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
perspectiveMatrix = makeOrtho(-SIZE/2, SIZE/2 ,
-SIZE/2, SIZE/2,
0.1, 100.0);
//perspectiveMatrix = makePerspective(45, canvas.width / canvas.height, 0.1, 100.0);
loadIdentity();
setLight(10,10,10);
// Draw background
// enable texture attributes
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
mvPushMatrix();
gl.uniform1f(gl.getUniformLocation(shaderProgram, "uIsTextured"), 1);
mvTranslate([0.0, 0.0, -30.0]);
setMatrixUniforms();
aTex.draw();
mvPopMatrix();
// Draw the pipe
mvTranslate([-2.5, -2.5, -16.0]);
// overall rotation
mvRotate(90.0, [0, 1, 0]);
// rotate according to user action
mvRotate(xRot, [1, 0, 0]);
mvRotate(yRot, [0, 1, 0]);
// dynamic
mvTranslate([0.0, 0.0, 2.0]);
mvRotate(yRot, [0, 1, 0]);
mvTranslate([0.0, 0.0, -2.0]);
mvTranslate([0.0, 2.5, 0.0]);
mvRotate(xRot, [0, 0, 1]);
mvTranslate([0.0, -2.5, 0.0]);
// disable texture attributes
gl.disableVertexAttribArray(shaderProgram.textureCoordAttribute);
gl.uniform1f(gl.getUniformLocation(shaderProgram, "uIsTextured"), 0);
setMatrixUniforms();
mvPushMatrix();
aCurve.draw();
mvPopMatrix();
}
//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);
// let the shaderprogram remember the addresses
// so we can use when we fill the attribute buffers
shaderProgram.vertexPositionAttribute =
gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.vertexNormalAttribute =
gl.getAttribLocation(shaderProgram, "aVertexNormal");
gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute);
shaderProgram.vertexMaterialIndexAttribute =
gl.getAttribLocation(shaderProgram, "aMaterialIndex");
gl.enableVertexAttribArray(shaderProgram.vertexMaterialIndexAttribute);
shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
// mark light
shaderProgram.ambientLightingColorUniform =
gl.getUniformLocation(shaderProgram, "uAmbientLightingColor");
shaderProgram.pointLightingLocationUniform =
gl.getUniformLocation(shaderProgram, "uPointLightingLocation");
shaderProgram.pointLightingDiffuseColorUniform =
gl.getUniformLocation(shaderProgram, "uPointLightingDiffuseColor");
shaderProgram.pointLightingSpecularColorUniform =
gl.getUniformLocation(shaderProgram, "uPointLightingSpecularColor");}
//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());
}
//setlightandcolor
function setLight(x,y,z)
{
gl.uniform3f(shaderProgram.pointLightingLocationUniform,x,y,z);
gl.uniform3f(shaderProgram.ambientLightingColorUniform, 1.0, 1.0, 1.0);
gl.uniform3f(shaderProgram.pointLightingDiffuseColorUniform, 1.0, 1.0, 1.0);
gl.uniform3f(shaderProgram.pointLightingSpecularColorUniform, 1.0, 1.0, 1.0);
gl.uniform1i(shaderProgram.showSpecularHighlightsUniform, true);
}
//eofsetlightandcolor
//setMatrixUniforms
function setMatrixUniforms() {
// set al the transformation matrices (ModelView, perspective and normal
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);
}
//requestFrame
//requestAnimationFrame in a cross browser way, from Googles webgl-lib
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function FrameRequestCallback */ callback,
/* DOMElement Element */ element) {
window.setTimeout(callback, 1000/60);
};
})();
//eofrequestFrame
//mousing
function initMousing(){
canvas.onmousedown = function ( ev ){
drag = 1;
xOffs = ev.clientX;
yOffs = ev.clientY;
}
canvas.onmouseup = function ( ev ){
drag = 0;
xOffs = ev.clientX;
yOffs = ev.clientY;
}
canvas.onmousemove = function ( ev ){
if ( drag == 0 ) return;
yRot += - xOffs + ev.clientX;
xRot += - yOffs + ev.clientY;
xOffs = ev.clientX;;
yOffs = ev.clientY;
drawScene();
}
}
//eofmousing
//resizing
function onWindowResize() {
canvas.width=canvas.height=Math.min(window.innerWidth,window.innerHeight)
gl.viewport(0, 0, canvas.width, canvas.height);
// if not animated: drawScene();
}
//eofresizing
// Assuming:
// main curvature in ZY-plane
// Curvature described as C : B(t)
// Contours described as X : X(t) and Y: Y(t)
// These ar relative to point on curve and describes an ellipse
// with center on C and radie X(t) and Y(t).
// Local z-axiz assumed as C'(t)
// Main strategi is to use Fermet frames.
// produce local transformation matrix with:
// T=C'(t), B=[1,0,0], N=T X B
// we will then assume local z along T,
// and use (X(t) and Y(t) in ellpise-drawing
// parameters are Bezier controlpoints
// C, X, Y as described above
Curve=function(c,x,y)
{
// prepare points and normals
var t=0.0;
var TCOUNT=40;
var dt=1.0/TCOUNT;
var VCOUNT=50;
var BRASS_START=26;
var dv=2*Math.PI/VCOUNT;
var vertices=[];
var normals=[];
var indices=[];
var matix=[];
var BLACK=1.0;
var BROWN=2.0;
var BRASS=3.0;
// produce an ellipse
// M Frenet Matrix
// rx,ry: radie
// mat_ix: the material index for this ellipse
// z-normal: true: normal along z, false: normal to ellipse
this.makeEllipse=function(M,rx,ry,mat_ix,z_normal)
{
var v=0.0;
for(var vix=0;vix < VCOUNT+1; vix++)
{
// vertice
var xp=rx*Math.cos(v);
var yp=ry*Math.sin(v);
var vec=Vector.create([xp,yp,0.0,1]);
var tvec=M.multiply(vec);
vertices=vertices.concat([tvec.e(1),tvec.e(2),tvec.e(3)]);
matix=matix.concat([mat_ix]);
// normal
var nvec;
var tnvec;
if(z_normal)
nvec=Vector.create([0,0,-1.0,1]);
else
{
var xn=(rx+15)*Math.cos(v)-xp;
var yn=(ry+15)*Math.sin(v)-yp;
nvec=Vector.create([xn,yn,0.0,1]);
}
var tnvec=M.multiply(nvec);
normals=normals.concat([tnvec.e(1),tnvec.e(2),tnvec.e(3)]);
v+=dv;
}
}// eof makeEllipse
// generate the body along Bezier, as t ticks
for (var tix=0;tix < TCOUNT; tix++)
{
var lastM;
// determine the coordinate system for next ellipse
// actual pos
var C=c.B(t);
// tangent
var T=c.dB(t);
T=T.toUnitVector();// normalize
// an other vector, use x which is always normal to derivative
var X=Vector.create([1.0,0.0,0.0]);
X=X.toUnitVector();// normalize
// a third vector
var Y=T.cross(X);
Y=Y.toUnitVector();// normalize
// matrix
var M=Matrix.create([ [X.e(1),Y.e(1),T.e(1),C.e(1)] ,
[X.e(2),Y.e(2),T.e(2),C.e(2)] ,
[X.e(3),Y.e(3),T.e(3),C.e(3)] ,
[0,0,0,1] ]
);
var rx=x.B(t).e(1)
var ry=y.B(t).e(2);
if(tix==0)
{
// black fill as a disk
this.makeEllipse(M,rx*0.01,ry*0.01,BLACK,true);
this.makeEllipse(M,rx*0.8,ry*0.8,BLACK,true);
// ring on top
this.makeEllipse(M,rx*0.8,ry*0.8,BROWN,true);
this.makeEllipse(M,rx,ry,BROWN,true);
}
if(tix < TCOUNT-1)
{
lastM=M.dup();
}
if(tix == BRASS_START)
this.makeEllipse(M,rx,ry,BRASS,false);
else if(tix > BRASS_START)
this.makeEllipse(M,rx,ry,BLACK,false);
else
this.makeEllipse(M,rx,ry,BROWN,false);
if(tix==TCOUNT-1)
{
//mouthpiece end
this.makeEllipse(M,rx*1.8,ry*1.8,BLACK,true);
this.makeEllipse(lastM,rx*1.2,ry*1.2,BLACK,false);
}
t+=dt;
}
// set up indices
for(var ixt=0; ixt < TCOUNT+5;ixt++) //!!!!
{
for(var ixv=0;ixv < VCOUNT; ixv++)
{
indices=indices.concat(ixt*(VCOUNT+1)+ ixv);
indices=indices.concat((ixt+1)*(VCOUNT+1)+ ixv);
indices=indices.concat(ixt*(VCOUNT+1)+ (ixv+1));
indices=indices.concat((ixt+1)*(VCOUNT+1)+ (ixv+1));
}
}
// prepare buffers once and for all
this.verticesBuffer= gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
this.verticesNormalBuffer= gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesNormalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
this.verticesMaterialBuffer=gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesMaterialBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(matix), gl.STATIC_DRAW);
this.verticesIndexBuffer= gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
this.verticesIndexBuffer.COUNT=indices.length;
// draw based on set buffers
// for each vertex: position, normal, materialix
// indices
this.draw=function()
{
gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,3,gl.FLOAT,false,0,0);
// Set the normal attribute for the vertices.
gl.bindBuffer(gl.ARRAY_BUFFER,this.verticesNormalBuffer);
gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute,3,gl.FLOAT,false,0,0);
// set material index attribute
gl.bindBuffer(gl.ARRAY_BUFFER,this.verticesMaterialBuffer);
gl.vertexAttribPointer(shaderProgram.vertexMaterialIndexAttribute,1,gl.FLOAT,false,0,0);
// Draw the cylinder.
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer);
gl.drawElements(gl.TRIANGLE_STRIP,this.verticesIndexBuffer.COUNT,gl.UNSIGNED_SHORT,0);
}
}
// Bezierfunctions
// based on sylvester
// 4 ctrl points
function Bezier(P0,P1,P2,P3)
{
this.p0=P0;
this.p1=P1;
this.p2=P2;
this.p3=P3;
this.B=function(t){
return Vector.create(
[this.p0[0]*(-t*t*t + 3*t*t - 3*t + 1)+
this.p1[0]*(3*t*t*t - 6*t*t + 3*t)+
this.p2[0]*(-3*t*t*t + 3*t*t)+
this.p3[0]*(t*t*t),
this.p0[1]*(-t*t*t + 3*t*t - 3*t + 1)+
this.p1[1]*(3*t*t*t - 6*t*t + 3*t)+
this.p2[1]*(-3*t*t*t + 3*t*t)+
this.p3[1]*(t*t*t),
this.p0[2]*(-t*t*t + 3*t*t - 3*t + 1)+
this.p1[2]*(3*t*t*t - 6*t*t + 3*t)+
this.p2[2]*(-3*t*t*t + 3*t*t)+
this.p3[2]*(t*t*t)]);
}
this.dB=function(t){
return Vector.create(
[this.p0[0]*(-3*t*t + 6*t - 3)+
this.p1[0]*(9*t*t - 12*t + 3)+
this.p2[0]*(-9*t*t + 6*t)+
this.p3[0]*(3*t*t),
this.p0[1]*(-3*t*t + 6*t - 3)+
this.p1[1]*(9*t*t - 12*t + 3)+
this.p2[1]*(-9*t*t + 6*t)+
this.p3[1]*(3*t*t),
this.p0[2]*(-3*t*t + 6*t - 3)+
this.p1[2]*(9*t*t - 12*t + 3)+
this.p2[2]*(-9*t*t + 6*t)+
this.p3[2]*(3*t*t)]);
}
this.ddB=function(t){
return Vector.create(
[this.p0[0]*(-6*t + 6)+
this.p1[0]*(18*t - 12)+
this.p2[0]*(-18*t + 6)+
this.p3[0]*(6*t),
this.p0[1]*(-6*t + 6)+
this.p1[1]*(18*t - 12)+
this.p2[1]*(-18*t + 6)+
this.p3[1]*(6*t),
this.p0[2]*(-6*t + 6)+
this.p1[2]*(18*t - 12)+
this.p2[2]*(-18*t + 6)+
this.p3[2]*(6*t)]);
}
}
Bakgrunnen, lerretet, er laget med en tekstur. Preparering av nødvendige buffere og
selve uttegningen er plassert i en klasse:
// handle a simple square with a texture
Tex=function(SIZE)
{
this.initTexture=function(imageName)
{
//this.squareTexture = gl.createTexture();
var squareImage = new Image();
this.squareTexture= gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D,this.squareTexture );
squareImage.onload = function()
{
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, squareImage);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
}
squareImage.src = imageName;
}
this.squareVerticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesBuffer);
var vertices = [
-SIZE/2, -SIZE/2, SIZE/2,
SIZE/2, -SIZE/2, SIZE/2,
SIZE/2, SIZE/2, SIZE/2,
-SIZE/2, SIZE/2, SIZE/2
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Set up the normals for the vertices
this.squareVerticesNormalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesNormalBuffer);
var vertexNormals = [
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexNormals),gl.STATIC_DRAW);
// Map the texture onto the square.
this.squareVerticesTextureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesTextureCoordBuffer);
var textureCoordinates = [
0.0, 1.0,
1.0, 1.0,
1.0, 0.0,
0.0, 0.0];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates),gl.STATIC_DRAW);
// Build the element array buffer; this specifies the indices
// prepared for 2 triangles
this.squareVerticesIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.squareVerticesIndexBuffer);
var squareVertexIndices = [0, 1, 2, 0, 2, 3];
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,new Uint16Array(squareVertexIndices), gl.STATIC_DRAW);
// for each vertex: position, normal, texturecoord, indices
this.draw=function()
{
// Draw the BACKGROUND
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
// Set the texture coordinates attribute for the vertices.
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesTextureCoordBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
// Bind the normals buffer to the shader attribute.
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesNormalBuffer);
gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0);
// Specify the texture to map onto the faces.
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.squareTexture);
gl.uniform1i(gl.getUniformLocation(shaderProgram, "uSampler"), 0);
// Draw the square.
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.squareVerticesIndexBuffer);
setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
}
}
Selve uttegningen blir slik:
function drawScene() {
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
perspectiveMatrix = makeOrtho(-SIZE/2, SIZE/2 ,
-SIZE/2, SIZE/2,
0.1, 100.0);
//perspectiveMatrix = makePerspective(45, canvas.width / canvas.height, 0.1, 100.0);
loadIdentity();
setLight(10,10,10);
// Draw background
// enable texture attributes
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
mvPushMatrix();
gl.uniform1f(gl.getUniformLocation(shaderProgram, "uIsTextured"), 1);
mvTranslate([0.0, 0.0, -30.0]);
setMatrixUniforms();
aTex.draw();
mvPopMatrix();
// Draw the pipe
mvTranslate([-2.5, -2.5, -16.0]);
// overall rotation
mvRotate(90.0, [0, 1, 0]);
// rotate according to user action
mvRotate(xRot, [1, 0, 0]);
mvRotate(yRot, [0, 1, 0]);
// dynamic
mvTranslate([0.0, 0.0, 2.0]);
mvRotate(yRot, [0, 1, 0]);
mvTranslate([0.0, 0.0, -2.0]);
mvTranslate([0.0, 2.5, 0.0]);
mvRotate(xRot, [0, 0, 1]);
mvTranslate([0.0, -2.5, 0.0]);
// disable texture attributes
gl.disableVertexAttribArray(shaderProgram.textureCoordAttribute);
gl.uniform1f(gl.getUniformLocation(shaderProgram, "uIsTextured"), 0);
setMatrixUniforms();
mvPushMatrix();
aCurve.draw();
mvPopMatrix();
}
Shadere
#ifdef GL_ES
precision mediump float;
#endif
varying vec3 vTransformedNormal;
varying vec4 vPosition;
varying float materialIndex;
// texture related
varying mediump vec2 vTextureCoord;
varying float vIsTextured;
// texture related
uniform sampler2D uSampler;
uniform vec3 uAmbientLightingColor;
uniform vec3 uPointLightingDiffuseColor;
uniform vec3 uPointLightingSpecularColor;
uniform vec3 uPointLightingLocation;
void main(void) {
if(vIsTextured > 0.5)
{
// the background
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
}
else
{
// we deal with the pipe
// assume brown
vec3 materialAmbientColor = vec3(0.11, 0.1, 0.0);
vec3 materialDiffuseColor = vec3(0.22, 0.11, 0.11);
vec3 materialSpecularColor = vec3(0.1, 0.1, 0.1);
vec3 materialEmissiveColor = vec3(0.0, 0.0, 0.0);
float materialShininess=90.0;
if(materialIndex < 1.999)//influenced by BLACK ->black
{
materialAmbientColor = vec3(0.0, 0.0, 0.0);
materialDiffuseColor = vec3(0.01, 0.01, 0.01);
materialSpecularColor = vec3(0.50, 0.50, 0.50);
materialEmissiveColor = vec3(0.0, 0.0, 0.0);
materialShininess=32.0;
}
if(materialIndex > 2.0001)// influenced by BRASS ->brass
{
materialAmbientColor = vec3(0.329412, 0.223529, 0.027451);
materialDiffuseColor = vec3(0.780392, 0.568627, 0.113725);
materialSpecularColor = vec3(0.992157, 0.941176, 0.807843);
materialEmissiveColor = vec3(0.0, 0.0, 0.0);
materialShininess=27.0;
}
vec3 ambientLightWeighting = uAmbientLightingColor;
vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
vec3 normal = normalize(vTransformedNormal);
vec3 specularLightWeighting = vec3(0.0, 0.0, 0.0);
vec3 eyeDirection = normalize(-vPosition.xyz);
vec3 reflectionDirection = reflect(-lightDirection, normal);
float specularLightBrightness =
pow(max(dot(reflectionDirection, eyeDirection), 0.0), materialShininess);
specularLightWeighting = uPointLightingSpecularColor * specularLightBrightness;
float diffuseLightBrightness = max(dot(normal, lightDirection), 0.0);
vec3 diffuseLightWeighting = uPointLightingDiffuseColor * diffuseLightBrightness;
float alpha = 1.0;
gl_FragColor = vec4(
materialAmbientColor * ambientLightWeighting
+ materialDiffuseColor * diffuseLightWeighting
+ materialSpecularColor * specularLightWeighting
+ materialEmissiveColor,
alpha
);
}
}
attribute mediump vec3 aVertexNormal; // or in
attribute mediump vec3 aVertexPosition;
attribute mediump float aMaterialIndex;
attribute mediump vec2 aTextureCoord;
uniform mediump mat4 uNormalMatrix;
uniform mediump mat4 uMVMatrix;
uniform mediump mat4 uPMatrix;
uniform float uIsTextured;
varying vec3 vTransformedNormal;
varying vec4 vPosition;
varying float vIsTextured;
varying mediump vec2 vTextureCoord;
varying lowp vec4 vColor;
varying mediump vec3 vLighting;
varying float materialIndex;
void main(void) {
vPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
gl_Position = uPMatrix * vPosition;
vTransformedNormal=(uNormalMatrix * vec4(aVertexNormal, 1.0)).xyz;
materialIndex=aMaterialIndex;
vIsTextured=uIsTextured;
vTextureCoord = aTextureCoord;
}
Du kan også inspisere resultatet og kildekoden på en enklere side:
simple.html
https://borres.hiof.no/wep/webgl/basis/pipe/simple.html
Og hva så?
I forklaringen ovenfor har vi lagt vekt på at bilde av pipa ikke finnes, det skapes.
Er nå det et holdbart resonnement? Kan vi ikke si at algoritmen er en bildebeskrivelse i seg selv?
Hva er den prinsippielle forskjellen på den algoritmen vi har skrevet og de algoritmene som er involvert for
å skanne, flytte og framstille en tegning eller et maleri på skjermen?
Vi kan jo for illustrasjonens(!) skyld se på en alternativ måte å lage og framstille pipa på.
Målet er å finne en litt tydeligere bildebeskrivelse. Vi ønsker å lage en
beskrivelse av bildet som inneholder en eksplisitt liste av alle punktene som benyttes for å
lage pipa.
Hvis vi tar fatt i programmet som er laget ovenfor og skriver ut alle de punktverdiene, normalverdiene
og indeksene som genereres kan vi få noe slikt (forkortet for lesbarhetenes skyld):
"vertexPositions" : [1.015,4,0,1.015,4.001,0.002,1.015,..]
"vertexNormals" : [1,4.959,-0.282,1,4.959,-0.282,1,4.959,..]
"vertexMatIx" : [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,..]
"indices" : [0,51,1,52,1,52,2,53,2,53,3,54,3,54,4,55,4,..]
Nå er det ikke så lett å skrive filer fra Javascript, men vi kan lett plassere dataene i
et element på websiden og kopiere dem derfra.
Den teksten vi får ut kan vi formatere som JSON
[2]
. Ut fra dette formatet kan vi evaluere teksten og få etablert
Javascript-objekter som vi kan bruke til å etablere alle nødvendige
databuffere. Teksten kan vi hente fra fil ved hjelp av en httprequest (AJAX),
eller vi kan pakke den inn i en Javascript-string som vi inkluderer i websiden.
Dersom vi gjør det siste blir stringen for eksempel slik ( igjen forkortet for lesbarhetenes skyld):
// the pipe described as a jacascript string
// formatted for JSON-evaluation
var pipeAsJason='{'
+'"vertexPositions" : [1.015,4,0,1.015,4.001,0.002,1.015,4.001,0.004,1.014,....],'
+'"vertexNormals" : [1,4.959,-0.282,1,4.959,-0.282,1,4.959,-0.282,1,....],'
+'"vertexMatIx" : [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,....],'
+'"indices" : [0,51,1,52,1,52,2,53,2,53,3,54,3,54,4,55,4,55,5,56,....]'
+'}';
Gitt denne prepareringen av data vil curven kunne beskrives vesentlig enklere:
// Loading jason format for
// verices, normals, materialinides, indices
// and
// Draw
Curve=function()
{
this.verticesBuffer= gl.createBuffer();
this.verticesNormalBuffer= gl.createBuffer();
this.verticesMaterialBuffer=gl.createBuffer();
this.verticesIndexBuffer= gl.createBuffer();
var pipeData=JSON.parse(pipeAsJason);
gl.bindBuffer(gl.ARRAY_BUFFER,this.verticesBuffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(pipeData.vertexPositions),gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER,this.verticesNormalBuffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(pipeData.vertexNormals),gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER,this.verticesMaterialBuffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(pipeData.vertexMatIx),gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,this.verticesIndexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,new Uint16Array(pipeData.indices),gl.STATIC_DRAW);
this.verticesIndexBuffer.COUNT=pipeData.indices.length;
// draw based on set buffers
// for each vertex: position, normal, materialix
// indices
this.draw=function()
{
gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,3,gl.FLOAT,false,0,0);
// Set the normal attribute for the vertices.
gl.bindBuffer(gl.ARRAY_BUFFER,this.verticesNormalBuffer);
gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute,3,gl.FLOAT,false,0,0);
// set material index attribute
gl.bindBuffer(gl.ARRAY_BUFFER,this.verticesMaterialBuffer);
gl.vertexAttribPointer(shaderProgram.vertexMaterialIndexAttribute,1,gl.FLOAT,false,0,0);
// Draw it.
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,this.verticesIndexBuffer);
gl.drawElements(gl.TRIANGLE_STRIP,this.verticesIndexBuffer.COUNT,gl.UNSIGNED_SHORT,0);
}
}
Resultatet skiller seg ikke fra det vi har sett tidligere. Vi har heller ikke kommet særlig nærmere
en klarhet i forskjellen mellom en algoritmisk beskrivelse eller en passiv gjengivelse av et "fysisk" bilde.
Det vi har oppnådd med den siste varianten er et enkelt forsøk på å pakke geometri i
et ganske fleksibelt format som er velegnet for våre programmeringsomgivelser, JSON.
En kan kanskje påstå at JSON-formatet med sine ferdigberegnede punkter ligger nærmere opp til en pixel-beskrivelse av bildet enn en algoritme som generer punkter
"on the fly". På den annen side har ikke punktene noen mening dersom
de ikke tolkes av vårt program. Det har jo ikke pixelverdiene heller.
Du kan også se resultatet og kildekoden på en enklere side:
index_json.html
https://borres.hiof.no/wep/webgl/basis/pipe/jsonpipe/index_json.html
eller med JQuery UI slidere:
index_json_slide.html
https://borres.hiof.no/wep/webgl/basis/pipe/jsonpipe/index_json_slide.html
I modulen
Pipe
finner du et eksempel på at vi lager en Wavefront .obj
[3]
fil av dataene.