// Projekt mit Framebuffer.
// Zuerst wird eine Szene gerendert. Das Ergebnis landet in einer Textur.
// In einem zweiten Render-Durchlauf wird dann die Textur auf ein Quadrat aufgetragen.
"use strict";
//
// initWebGL
//
// Initialize WebGL, returning the GL context or null if
// WebGL isn't available or could not be initialized.
//
function initWebGL(canvas) {
let gl = null;
try {
gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
}
catch(e) {
}
if (!gl)
throw "Unable to initialize WebGL. Your browser may not support it.";
return gl;
}
//
// getShader
//
// Loads a shader program by scouring the current document,
// looking for a script with the specified ID.
//
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
// Didn't find an element with the specified ID; abort.
if (!shaderScript) {
return null;
}
// Walk through the source element's children, building the
// shader source string.
var theSource = "";
var currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == 3) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
// Now figure out 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 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;
}
class Framebuffer {
/**
* @param WebGLRenderingContext _gl
* @throws string
*/
constructor(_gl) {
if(!(_gl instanceof WebGLRenderingContext))
throw "gl must be of type WebGLRenderingContext";
// frame-buffer:
var framebuffer = _gl.createFramebuffer();
// auf selbst definierten frame-buffer switchen:
_gl.bindFramebuffer(_gl.FRAMEBUFFER, framebuffer); // bind
// 2 dynamische Texturen für ping-pong-buffer:
var texture = _gl.createTexture();
_gl.bindTexture(_gl.TEXTURE_2D, texture);
_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.LINEAR);
_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.LINEAR);
_gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, 512, 512, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, null); // null bezieht sich auf die Daten
// framebuffer-attachement: Für die Farben Texturen dem frame-buffer zuordnen:
_gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, texture, 0);
// depth-buffer:
var depthbuffer = _gl.createRenderbuffer();
_gl.bindRenderbuffer(_gl.RENDERBUFFER, depthbuffer);
_gl.renderbufferStorage(_gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, 512, 512);
// framebuffer-attachement: Für die z-Werte depth-buffer dem frame-buffer zuordnen:
_gl.framebufferRenderbuffer(_gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, depthbuffer);
/**
* @return WebGLFramebuffer
*/
this.getFramebuffer = function() {
return framebuffer;
};
/**
* @return WebGLTexture
*/
this.getTexture = function() {
return texture;
};
/**
* @return WebGLRenderbuffer
*/
this.getDepthbuffer = function() {
return depthbuffer;
};
}
/**
* @return WebGLFramebuffer
*/
get framebuffer() {
return this.getFramebuffer();
}
/**
* @return WebGLTexture
*/
get texture() {
return this.getTexture();
}
/**
* @return WebGLRenderbuffer
*/
get depthbuffer() {
return this.getDepthbuffer();
}
}
/**
* implementiert das Iterator-Pattern
*/
class PingPongFramebuffer {
/**
* @param WebGLRenderingContext _gl
* @param int _count >= 0, (maximale) Anzahl an Durchläufen.
* @throws string
*/
constructor(_gl, _count) {
if(!(_gl instanceof WebGLRenderingContext))
throw "gl must be of type WebGLRenderingContext";
var gl = _gl;
if(isNaN(_count) || _count < 0)
throw "count must be of type integer >= 0";
var count = _count;
var framebuffers = [
new Framebuffer(gl),
new Framebuffer(gl)
];
var shaderOutputFramebuffer = 0;
var i = 0; // Zähler
/**
* @return boolean
*/
this.hasNext = function() {
if(i >= count) {
return false;
}
// Textur als shader-input:
if(i == 0) {
// Beim ersten Durchlauf muss die Textur während des renderns deaktiviert sein.
// Macht man das nicht, liefert Chrome den folgenden Fehler:
// [GroupMarkerNotSet(crbug.com/242999)!:5C915608]
// GL ERROR :GL_INVALID_OPERATION : glDrawElements: Source and destination textures of the draw are the same.
// Beim Firefox kommt dieser Fehler nicht.
gl.bindTexture(gl.TEXTURE_2D, null);
// Ist shaderInputTexture null bedeutet das, dass in den Shader keine Texturdaten reinkommen.
}
else {
// Textur für shader-input vorbereiten:
let shaderInputTexture = (shaderOutputFramebuffer + 1) % 2;
gl.bindTexture(gl.TEXTURE_2D, framebuffers[shaderInputTexture].texture);
gl.generateMipmap(gl.TEXTURE_2D);
}
// framebuffer als shader-output:
if(i < count - 1) {
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[shaderOutputFramebuffer].framebuffer);
}
else {
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// Wird als Parameter null übergeben, wird der Default-framebuffer als Shader-Ausgabe verwendet.
// D. h. es erfolgt eine Ausgabe im canvas.
}
return true;
};
this.next = function() {
shaderOutputFramebuffer = (shaderOutputFramebuffer + 1) % 2;
++i;
};
}
}
function start() {
let canvas = document.getElementById("glcanvas");
let gl = initWebGL(canvas); // Initialize the GL context
if (gl) {
gl.clearDepth(1); // Clear everything
gl.enable(gl.DEPTH_TEST); // Enable depth testing
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
// shader:
let fragmentShader = getShader(gl, "shader-fs");
let vertexShader = getShader(gl, "shader-vs");
let shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
throw "Unable to initialize the shader program.";
gl.useProgram(shaderProgram);
// Positionen Vertices:
let vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
let vertices = [
-1, -1, 0, // links unten
1, -1, 0,
1, 1, 0,
-1, 1, 0
];
let verticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
// Indizes:
let vertexIndices = [
0, 1, 2, 0, 2, 3
];
let verticesIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, verticesIndexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertexIndices), gl.STATIC_DRAW);
// Farben für beide Renderdurchläufe:
let verticesColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, verticesColorBuffer);
let vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
gl.enableVertexAttribArray(vertexColorAttribute);
// Uniforms für beide Renderdurchläufe:
let useTexturesUniform = gl.getUniformLocation(shaderProgram, "uUseTextures");
let samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
let pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
// model-view-Matrix für beide Renderdurchläufe gleich gewählt:
let mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
let mvMatrix = Matrix.I(4).x(Matrix.Translation($V([0, 0, -6])).ensure4x4());
gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten()));
let vertexColors = [];
if(true) { // ob mit oder ohne Textur rendern
// Textur-Koordinaten:
let textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
gl.enableVertexAttribArray(textureCoordAttribute);
let textureCoordinates = [
0, 0, // links unten ist u = v = 0
1, 0,
1, 1,
0, 1
];
let verticesTextureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, verticesTextureCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW);
gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
// Hintergrundfarben für die Durchläufe:
let clearColors = [
[1, 0, 0], // rot
[0, 1, 0], // grün
[0, 0, 1] // blau
];
let count = 3; // Anzahl der Durchläufe
let ppFramebuffer = new PingPongFramebuffer(gl, count); // framebuffer/Texturen
let i = 0; // aktueller Durchlauf
while(ppFramebuffer.hasNext()) {
// Ob im shader mit eingehenden Texturdaten gearbeitet werden soll:
gl.uniform1i(useTexturesUniform, i > 0);
// Farben:
if(i == 0) {
// Im ersten Durchlauf wird das Quadrat eingefärbt.
vertexColors = [
1, 1, 1, // weiß
1, 0, 0, // rot
0, 1, 0, // grün
0, 0, 1 // blau
];
}
else {
// In alles weiteren Durchläufen soll der Textur keine weitere Farbe hinzugefügt werden.
vertexColors = [
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0
];
};
gl.bindBuffer(gl.ARRAY_BUFFER, verticesColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexColors), gl.STATIC_DRAW);
gl.vertexAttribPointer(vertexColorAttribute, 3, gl.FLOAT, false, 0, 0);
// projection-Matrix:
let perspectiveMatrix;
if(i < count - 1) {
let viewportSize = 512;
perspectiveMatrix = makePerspective(45, viewportSize / viewportSize, 0.1, 100);
gl.viewport(0, 0, viewportSize, viewportSize);
}
else {
// im letzten Durchlauf wird im canvas ausgegeben:
perspectiveMatrix = makePerspective(45, canvas.width / canvas.height, 0.1, 100);
gl.viewport(0, 0, canvas.width, canvas.height);
}
// rendern:
console.log(i);
gl.clearColor(clearColors[i][0], clearColors[i][1], clearColors[i][2], 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten()));
gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_SHORT, 0);
// framebuffer-swap:
ppFramebuffer.next();
// nächster Durchlauf:
++i;
}
}
else {
// Farben:
vertexColors = [
1, 1, 1, // weiß
1, 1, 1,
1, 1, 1,
1, 1, 1
];
gl.bindBuffer(gl.ARRAY_BUFFER, verticesColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexColors), gl.STATIC_DRAW);
gl.vertexAttribPointer(vertexColorAttribute, 3, gl.FLOAT, false, 0, 0);
// ohne Textur:
gl.uniform1i(useTexturesUniform, false);
// projection-Matrix:
let perspectiveMatrix = makePerspective(45, canvas.width / canvas.height, 0.1, 100);
gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten()));
// rendern:
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_SHORT, 0);
}
}
}