View Frustum

ViewFrustum #

Treegl allows you to visualize the volume that a camera is rendering through a frustum. This frustum shows the position of the screen and the camera, the render distance and the shape it has.

Params #

NamePropTypedescription
fbop5.RendererGL or p5.GraphicsThe renderer to be shown by the frustum.
bitsnumberBitmask to choose which planes to draw.
viewerfunctionCallback that allows to draw a visual representation of the fbo’s position.

Bitmask #

The bits parameter which planes to show. Even though this param is a number, it works as a Bitmask (Similar chmod’s numerical permissions in GNU/Linux). This bitmask allows different options to be chosen in a sigle parameter. p5.treegl adds the Tree object which allows easy access to all the necesary bits to mark different options. Each option is ordered as follows:

DescriptionValue in TreeBinary
Draws the far planeTree.NEAR0000001
Draws the near planeTree.FAR0000010
Draws the left boundaryTree.LEFT0000100
Draws the right boundaryTree.RIGHT0001000
Draws the bottom boundaryTree.BOTTOM0010000
Draws the top boundaryTree.TOP0100000
Draws the frustum’s bodyTree.BODY1000000

Orthographic Projection #

In orthographic projection, the camera that is rendering the scene does not identify depth. This means that no matter how far away an object is from the camera, it will retain its size. It’s often used for schematics, architectural drawings, 3D software when lining up vertices and mesuring distances as they also remain constant.

Click and drag to move the camera arround!

code #

sketch.js
'use strict';

let fbo1, fbo2;
let cam1, cam2;
let height = 600;
let boxes;
let persp = true;

var left = 300;
var right = 300;
var frustum_width = 200;
var bottom = 300;
var topa = 300;
var frustum_height = 200;
var far = 500;
var viewer = ['axes', 'arrow'];

var gui;

function setup() {
    
	createCanvas(400, 700);
    
	fbo1 = createGraphics(400, 350, WEBGL);
	fbo2 = createGraphics(400, 350, WEBGL);
	fbo1.ortho(-fbo1.width / 2, fbo1.width / 2, -fbo1.height / 2, fbo1.height / 2, 1, 500);
	// FBOs cams
	cam1 = new Dw.EasyCam(fbo1._renderer, { distance: 200 });
	cam1.setZoomScale(false);
	let state1 = cam1.getState();
	cam1.attachMouseListeners(this._renderer);
	cam1.state_reset = state1; // state to use on reset (double-click/tap)
	cam1.setViewport([0, 0, width / 2, height]);
	cam2 = new Dw.EasyCam(fbo2._renderer, { rotation: [0.94, 0.33, 0, 0] });
	cam2.setZoomScale(false);
	cam2.attachMouseListeners(this._renderer);
	let state2 = cam2.getState();
	cam2.state_reset = state2; // state to use on reset (double-click/tap)
	cam2.setViewport([width / 2, 0, width / 2, height]);
	document.oncontextmenu = function () {
		return false;
	};
	// scene
	colorMode(RGB, 1);
	boxes = [];
	for (let i = 0; i < 5; i++){
		for (let j = 0; j < 5; j++) {
			boxes.push({
				position: createVector(i * 40 - 70, 0, j * 40 - 70),
				size: 20,
				color: color(random(), random(), random()),
			});
		}
	}
	print(fbo1.bounds());
    gui = createGui('Double click to close').setPosition(30, 350);

	gui.addGlobals('frustum_width', 'frustum_height', 'far', 'viewer');
}

function draw() {
    fbo1.ortho(-frustum_width/2, frustum_width/2, -frustum_height/2, frustum_height/2, 1, far);
	fbo1.background(175, 125, 115);
	fbo1.reset();
	fbo1.axes({ size: 100, bits: Tree.X | Tree.YNEG });
	fbo1.grid();
	scene(fbo1);
	beginHUD();
	image(fbo1, 0, 0);
	endHUD();
	fbo2.background(130);
	fbo2.reset();
	fbo2.axes();
	fbo2.grid();
	scene(fbo2);
	fbo2.push();
	fbo2.strokeWeight(3);
	fbo2.stroke('magenta');
	fbo2.fill(color(1, 0, 1, 0.3));

	let selectedViewer;
	if(viewer === 'arrow'){
		selectedViewer = () => {
			fbo2.push();
			fbo2.stroke('#0803FF');
			fbo2.arrow({height: 50});
			fbo2.pop();
		};
	} else {
		selectedViewer = () => fbo2.axes({ size: 50, bits: Tree.X | Tree._X | Tree.Y | Tree._Y | Tree.Z | Tree._Z });
	}

	fbo2.viewFrustum({ fbo: fbo1, bits: Tree.NEAR | Tree.FAR, viewer: selectedViewer });
	fbo2.pop();
	beginHUD();
	image(fbo2, 0, 350);
	endHUD();
}

function scene(graphics) {
	boxes.forEach(box => {
		graphics.push();
		graphics.fill(box.color);
		graphics.translate(box.position);
		graphics.box(box.size);
		graphics.pop();
	});
}

Perspective Projection #

This projection is the most similar to the human eye. In perspective projection, the camera gives a sense of depth. The further away an object is from the camera, the smaller it appears.

Click and drag to move the camera arround!

code #

sketch.js
'use strict';

let fbo1, fbo2;
let cam1, cam2;
let height = 600;
let boxes;
let persp = true;


var far = 500;
var vertical_fov = 100;
var viewer = ['axes', 'arrow'];

var gui;

function setup() {
    
	createCanvas(400, 700);
    
	fbo1 = createGraphics(400, 350, WEBGL);
	fbo2 = createGraphics(400, 350, WEBGL);
	// FBOs cams
	cam1 = new Dw.EasyCam(fbo1._renderer, { distance: 200 });
	cam1.setZoomScale(false);
	let state1 = cam1.getState();
	cam1.attachMouseListeners(this._renderer);
	cam1.state_reset = state1; // state to use on reset (double-click/tap)
	cam1.setViewport([0, 0, width / 2, height]);
	cam2 = new Dw.EasyCam(fbo2._renderer, { rotation: [0.94, 0.33, 0, 0] });
	cam2.setZoomScale(false);
	cam2.attachMouseListeners(this._renderer);
	let state2 = cam2.getState();
	cam2.state_reset = state2; // state to use on reset (double-click/tap)
	cam2.setViewport([width / 2, 0, width / 2, height]);
	document.oncontextmenu = function () {
		return false;
	};
	// scene
	colorMode(RGB, 1);
	boxes = [];
	for (let i = 0; i < 5; i++){
		for (let j = 0; j < 5; j++) {
			boxes.push({
				position: createVector(i * 40 - 70, 0, j * 40 - 70),
				size: 20,
				color: color(random(), random(), random()),
			});
		}
	}
	print(fbo1.bounds());
	
	gui = createGui('Double click to close').setPosition(30, 350);

	gui.addGlobals('vertical_fov', 'far', 'viewer');
}

function draw() {
    fbo1.perspective(vertical_fov*(PI/3)/100, 1, 10, far);
	fbo1.background(175, 125, 115);
	fbo1.reset();
	fbo1.axes({ size: 100, bits: Tree.X | Tree.YNEG });
	fbo1.grid();
	scene(fbo1);
	beginHUD();
	image(fbo1, 0, 0);
	endHUD();
	fbo2.background(130);
	fbo2.reset();
	fbo2.axes();
	fbo2.grid();
	scene(fbo2);
	fbo2.push();
	fbo2.strokeWeight(3);
	fbo2.stroke('magenta');
	fbo2.fill(color(1, 0, 1, 0.3));

	let selectedViewer;
	if(viewer === 'arrow'){
		selectedViewer = () => {
			fbo2.push();
			fbo2.stroke('#0803FF');
			fbo2.arrow({height: 50});
			fbo2.pop();
		};
	} else {
		selectedViewer = () => fbo2.axes({ size: 50, bits: Tree.X | Tree._X | Tree.Y | Tree._Y | Tree.Z | Tree._Z });
	}
	
	fbo2.viewFrustum({ fbo: fbo1, bits: Tree.NEAR | Tree.FAR, viewer: selectedViewer });
	fbo2.pop();
	beginHUD();
	image(fbo2, 0, 350);
	endHUD();
}

function scene(graphics) {
	boxes.forEach(box => {
		graphics.push();
		graphics.fill(box.color);
		graphics.translate(box.position);
		graphics.box(box.size);
		graphics.pop();
	});
}