Public
Edited
Oct 2, 2023
Importers
Insert cell
Insert cell
Insert cell
defaultExample = {
const helper = gl2helper().draw();
return helper.canvas;
}
Insert cell
Insert cell
{
const helper = gl2helper({
vertexShader: `#version 300 es
layout(location=0) in vec4 position;
layout(location=1) in vec3 color;
out vec3 vColor;
void main() {
vColor = color;
gl_Position = position;
}`,
fragmentShader: `#version 300 es
precision highp float;
in vec3 vColor;
out vec4 fragColor;
void main() {
fragColor = vec4(vColor, 1.0);
}`,
attributes: {
positions: [
[-0.5, -0.5],
[0.5, -0.5],
[0.0, 0.5]
],
colors: [
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]
]
}
});
helper.clearColor(0, 0, 0, 1).clear().draw();
return helper.canvas;
}
Insert cell
Insert cell
//
// Example using Multiple render targets and FBO from the picoGL tutorial
// See also https://observablehq.com/@esperanc/picogl-tutorial-part-ii-b
//
fboAndMRTtest = {
const vertexShader = `#version 300 es
layout(location=0) in vec4 position;
out vec2 vUV;
void main() {
vUV = position.xy * 0.5 + 0.5;
gl_Position = position;
}`;
const fragShaderMRT = `#version 300 es
precision highp float;
layout(location=0) out vec4 fragColor1;
layout(location=1) out vec4 fragColor2;
void main() {
fragColor1 = vec4(1.0, 0.0, 0.0, 1.0);
fragColor2 = vec4(0.0, 0.0, 1.0, 1.0);
}`;
const fragShaderBlend = `#version 300 es
precision highp float;
in vec2 vUV;
uniform sampler2D texture1;
uniform sampler2D texture2;
out vec4 fragColor;
void main() {
vec4 color1 = texture(texture1, vUV);
vec4 color2 = texture(texture2, vUV);
fragColor = mix(color1, color2, vUV.x);
}`;
const testStep1 = gl2helper({
vertexShader,
fragmentShader: fragShaderMRT,
attributes: {
position: [
[-0.5, -0.5],
[0.5, -0.5],
[0.0, 0.5]
]
},
fbos: [2],
primitive: "triangles"
});
const testStep2 = gl2helper({
vertexShader,
fragmentShader: fragShaderBlend,
primitive: "triangle_fan",
app: testStep1.app,
canvas: testStep1.canvas,
attributes: {
position: [
[-1, -1],
[-1, 1],
[1, 1],
[1, -1]
]
}
});
testStep1.useFbo(0).clearColor(0.8, 0.8, 0.8, 1).clear().draw();
testStep2
.texture("texture1", testStep1.fboObjects[0].colorAttachments[0])
.texture("texture2", testStep1.fboObjects[0].colorAttachments[1])
.useFbo(null)
.draw();
return testStep1.canvas;
}
Insert cell
Insert cell
{
let [angleX, angleY] = [0, 0];
let { mat4, vec3 } = glmatrix;
let rotateMatrix = mat4.create();
let modelMatrix = mat4.create();
const canvas = cubeApp.canvas;

let refresh = () => {
cubeApp.uniform("uModel", modelMatrix);
cubeApp.clear();
cubeApp.draw();
};

let projMatrix = mat4.create();
mat4.perspective(
projMatrix,
Math.PI / 2,
canvas.width / canvas.height,
0.1,
100.0
);

let viewMatrix = mat4.create();
let eyePosition = [0, 0, 3, 1];
mat4.lookAt(viewMatrix, eyePosition, [0, 0, 0], [0, 1, 0]);

let viewProjMatrix = mat4.create();
mat4.multiply(viewProjMatrix, projMatrix, viewMatrix);

let lightPosition = [0.5, 0.5, 10, 1];
cubeApp
.uniform("viewProj", viewProjMatrix)
.uniform("eyePosition", eyePosition)
.uniform("lightPosition", lightPosition)
.depthTest(true);

yield canvas;

for (let frame = 0; frame >= 0; frame++) {
mat4.fromRotation(rotateMatrix, 0.01, [1, 0, 0]);
mat4.multiply(modelMatrix, rotateMatrix, modelMatrix);
mat4.fromRotation(rotateMatrix, 0.01, [0, 1, 0]);
mat4.multiply(modelMatrix, rotateMatrix, modelMatrix);
refresh();
yield canvas;
}
}
Insert cell
cubeApp = {
const vertexShader = `#version 300 es
layout(location=0) in vec3 position;
layout(location=1) in vec2 uv;
layout(location=2) in vec3 normal;
uniform mat4 viewProj;
uniform vec4 eyePosition;
uniform vec4 lightPosition;
uniform mat4 uModel;
out vec3 vPosition;
out vec2 vUV;
out vec3 vNormal;
void main() {
vec4 worldPosition = uModel * vec4(position, 1.);
vPosition = worldPosition.xyz;
vUV = uv;
vNormal = (uModel * vec4(normal,0.)).xyz;
gl_Position = viewProj * worldPosition;
}
`;
const fragmentShader = `#version 300 es
precision highp float;
uniform mat4 viewProj;
uniform vec4 eyePosition;
uniform vec4 lightPosition;
uniform sampler2D tex;
in vec3 vPosition;
in vec2 vUV;
in vec3 vNormal;
out vec4 fragColor;
void main() {
vec3 color = texture(tex, vUV).rgb;
vec3 normal = normalize(vNormal);
vec3 eyeVec = normalize(eyePosition.xyz - vPosition);
vec3 incidentVec = normalize(vPosition - lightPosition.xyz);
vec3 lightVec = -incidentVec;
float diffuse = max(dot(lightVec, normal), 0.0);
float highlight = pow(max(dot(eyeVec, reflect(incidentVec, normal)), 0.0), 100.0);
float ambient = 0.1;
fragColor = vec4(color * (diffuse + highlight + ambient), 1.0);
//fragColor = vec4(vNormal, 1.0);
}
`;
return gl2helper({
vertexShader,
fragmentShader,
attributes: objToAttributes(
await FileAttachment("texturedCube@14.obj").text()
),
textures: {
tex: await FileAttachment("webgl-logo.png").image()
},
primitive: "triangles"
});
}
Insert cell
Insert cell
//
// A webgl2 helper based on picogl.
//
gl2helper = function (options = {}) {
const {
vertexShader = `#version 300 es
layout(location=0) in vec4 position;
layout(location=1) in vec2 texCoord;
out vec2 uv;
void main() {
uv = texCoord;
gl_Position = position;
}`,
fragmentShader = `#version 300 es
precision highp float;
in vec2 uv;
out vec4 fragColor;
void main() {
fragColor = vec4(vec3(length(uv)/sqrt(2.)),1.);
}`,
textures = {},
attributes = {
position: [
[-1, -1],
[-1, 1],
[1, 1],
[1, -1]
],
texCoord: [
[0, 0],
[0, 1],
[1, 1],
[1, 0]
]
},
fbos = null,
primitive = "triangle_fan",
count = 4
} = options;
const canvas = options.canvas
? options.canvas
: options.app
? options.app.canvas
: DOM.canvas(800, 600);
const app =
options.app || picogl.createApp(canvas, { preserveDrawingBuffer: true });
const attributeBuffers = {};
const vertexArray = app.createVertexArray();
let attrCount = 0;
for (let attr of Object.keys(attributes)) {
const avalue = attributes[attr];
const elsize = avalue[0].length || 1;
const floatArray = new Float32Array(avalue.flat());
attributeBuffers[attr] = app.createVertexBuffer(
picogl.FLOAT,
elsize,
floatArray
);
vertexArray.vertexAttributeBuffer(attrCount++, attributeBuffers[attr]);
}
const program = app.createProgram(vertexShader, fragmentShader);
const drawCall = app.createDrawCall(program, vertexArray);
const textureObjects = {};
for (let texName of Object.keys(textures)) {
textureObjects[texName] = app.createTexture2D(textures[texName], {
flipY: true,
maxAnisotropy: picogl.WEBGL_INFO.MAX_TEXTURE_ANISOTROPY,
minFilter: app.gl.LINEAR,
maxFilter: app.gl.LINEAR
});
drawCall.texture(texName, textureObjects[texName]);
}
let fboObjects = [];
if (fbos) {
for (let iFbo = 0; iFbo < fbos.length; iFbo++) {
fboObjects[iFbo] = app.createFramebuffer();
for (let iTarget = 0; iTarget < fbos[iFbo]; iTarget++) {
let colorTarget = app.createTexture2D(app.width, app.height, {
minFilter: app.gl.LINEAR,
maxFilter: app.gl.LINEAR
});
fboObjects[iFbo].colorTarget(iTarget, colorTarget);
}
}
}
drawCall.primitive(
{
triangle_fan: app.gl.TRIANGLE_FAN,
points: app.gl.POINTS,
lines: app.gl.LINES,
line_strip: app.gl.LINE_STRIP,
triangles: app.gl.TRIANGLES,
triangle_strip: app.gl.TRIANGLE_STRIP
}[primitive]
);
const bundle = {
useFbo: (iFbo) => {
if (iFbo === 0 || iFbo > 0) app.drawFramebuffer(fboObjects[iFbo]);
else app.defaultDrawFramebuffer();
return bundle;
},
depthTest: (flag) => {
if (flag) app.enable(picogl.DEPTH_TEST);
else app.disable(picogl.DEPTH_TEST);
return bundle;
},
app,
drawCall,
program,
fboObjects,
textureObjects,
canvas
};
for (let func of ["texture", "uniform", "draw"])
bundle[func] = (...args) => {
drawCall[func](...args);
return bundle;
};
for (let func of ["clear", "clearColor"])
bundle[func] = (...args) => {
app[func](...args);
return bundle;
};
return bundle;
}
Insert cell
Insert cell
function objParse(obj) {
// Simplified obj file parser
let faces = [];
let vertices = [];
let vnormals = [];
let vtexcoords = [];
for (let line of obj.split(/\n/)) {
const words = line.trim().split(/\s+/);
const type = words[0].toUpperCase();
const data = words.slice(1);
switch (type) {
case "V":
vertices.push(data.map((w) => +w));
break;
case "VT":
vtexcoords.push(data.map((w) => +w));
break;
case "VN":
vnormals.push(data.map((w) => +w));
break;
case "F":
faces.push(data.map((w) => w.split("/").map((index) => +index)));
}
}
return { faces, vertices, vnormals, vtexcoords };
}
Insert cell
//
// From an obj file, returns a set of attributes named
// position, texcoord and normal with the corresponding values.
// texcoord and normal are optional and are omitted if not defined
// in the obj file. Assumes the obj file defines triangular faces
//
function objToAttributes(obj) {
const { faces, vertices, vnormals, vtexcoords } = objParse(obj);
const position = [];
const normal = [];
const texcoord = [];
for (let face of faces) {
for (let [i, j, k] of face) {
position.push(vertices[i - 1]);
if (j) texcoord.push(vtexcoords[j - 1]);
if (k) normal.push(vnormals[k - 1]);
}
}
const attr = { position };
if (texcoord.length) attr.texcoord = texcoord;
if (normal.length) attr.normal = normal;
return attr;
}
Insert cell
Insert cell
picogl = require("picogl@0.17.3/build/picogl.min.js")
Insert cell
glmatrix = import("https://unpkg.com/gl-matrix@3.4.3/esm/index.js?module")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more