GLSL with Processing: Implementing 3D transformations

Soumya
4 min readAug 14, 2022

Note that this post assumed prior knowledge about expressing transformations as matrices.

First, let’s add controls so that the user can select the amount of each transformation to be applied. There will be three sliders for rotation about the x, y, and z axes, three sliders for translation in the x, y, and z directions, and one slide for scale. This code is defined in Controls.pde.

For more info about how to add controls with ControlP5 in Processing, see this post.

Controls.pde

import controlP5.*;public class Controls extends PApplet {
ControlP5 cp5;

float rotationX = 0;
float rotationY = 0;
float rotationZ = 0;

float translationX = 0;
float translationY = 0;
float translationZ = 0;

float scale = 1;
void settings() {
size(320, 340);
}

void setup() {
cp5 = new ControlP5(this);

Group g1 = cp5.addGroup("g1")
.setPosition(25,25)
.setBackgroundHeight(280)
.setWidth(250)
.setBackgroundColor(color(255,50))
.setLabel("Transformations")
;

cp5.addSlider("scale")
.setPosition(25, 25)
.setRange(0.5, 2)
.setGroup(g1)
.setValue(1)
.setLabel("Scale")
;

cp5.addSlider("translationX")
.setPosition(25, 75)
.setRange(-50,50)
.setGroup(g1)
.setValue(0)
.setLabel("X Translation")
;
cp5.addSlider("translationY")
.setPosition(25, 100)
.setRange(-50,50)
.setGroup(g1)
.setValue(0)
.setLabel("Y Translation")
;
cp5.addSlider("translationZ")
.setPosition(25, 125)
.setRange(-50,50)
.setGroup(g1)
.setValue(0)
.setLabel("Z Translation")
;

cp5.addSlider("rotationX")
.setPosition(25, 175)
.setRange(0,2*PI)
.setGroup(g1)
.setValue(0)
.setLabel("X Rotation")
;
cp5.addSlider("rotationY")
.setPosition(25, 200)
.setRange(0,2*PI)
.setGroup(g1)
.setLabel("Y Rotation")
;
cp5.addSlider("rotationZ")
.setPosition(25, 225)
.setRange(0,2*PI)
.setGroup(g1)
.setValue(0)
.setLabel("Z Rotation")
;
}
void draw() {
background(100);
}
}

In the main Processing file, let’s connect the Controls class and create a shape. Additionally, the values from the sliders need to be passed to the vertex shader.

Transformations.pde

Controls controls;PShader myShader;PShape primitiveObject;void setup(){
size(640,480,P3D);

primitiveObject = createShape(BOX, 100, 100, 100);
primitiveObject.setFill(color(200, 50, 50));
primitiveObject.setStroke(false);
String[] args = {"Controls window"};
controls = new Controls();
PApplet.runSketch(args, controls);

myShader = loadShader("frag.glsl", "vert.glsl");
}
void draw(){
background(100);

myShader.set("scaleAmount", controls.scale);
myShader.set("rotateXAmount",controls.rotationX);
myShader.set("rotateYAmount", controls.rotationY);
myShader.set("rotateZAmount", controls.rotationZ);
myShader.set("translateXAmount", controls.translationX);
myShader.set("translateYAmount", controls.translationY);
myShader.set("translateZAmount", controls.translationZ);
shader(myShader);

shape(primitiveObject, width/2, height/2);
}

Note that the shaders should be in a data directory.

Project structure

The fragment shader simply assigns the vertex color to the fragment color.

frag.glsl

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif
varying vec4 vertColor;void main() {
gl_FragColor = vertColor;
}

In order to implement the transformations in the vertex shader transformation matrices must be used. The following are 3D transformation matrices in homogenous coordinates. These can be multiplied together when multiple transformations are to be applied to a shape.

Where should the transformation matrix be put in the shader? gl_Position is set by transforming the vertex position. The modelview matrix transforms the position from object coordinates to eye coordinates, and the projection matrix converts it to screen coordinates.

gl_Position = projection * modelview * position;

Since the shape is to be transformed based on its local coordinate system, it would make sense to put the transformation matrix before the modelview matrix.

gl_Position = projection * modelview * transformationMatrix * position

Pay particular attention to the construction of matrices in GLSL. Following other programming languages, one might define the translation matrix as follows.

return mat4 (
1,0,0,x,
0,1,0,y,
0,0,1,z,
0,0,0,1
);

This, however, produced the transpose of what may be expected, because the first row in the constructor is actually the first column of the matrix. As can be seen in this webpage, the proper way to define the translation matrix would be

return mat4 (
1,0,0,0, //column 1
0,1,0,0, //column 2
0,0,1,0, //column 3
x,y,z,1 //column 4
);

Now, everything can be put together in the vertex shader.

vert.glsl

uniform float scaleAmount;
uniform float rotateXAmount;
uniform float rotateYAmount;
uniform float rotateZAmount;
uniform float translateXAmount;
uniform float translateYAmount;
uniform float translateZAmount;
uniform mat4 modelview;
uniform mat4 projection;
uniform mat4 transform;
uniform mat3 normalMatrix;
attribute vec4 position;
attribute vec4 color;
attribute vec3 normal;
varying vec4 vertColor;mat4 rotateX(float angle) {
mat4 rotationMatrix = mat4(
1, 0, 0, 0,
0, cos(angle), -sin(angle), 0,
0, sin(angle), cos(angle), 0,
0, 0, 0, 1
);
return rotationMatrix;
}
mat4 rotateY(float angle) {
mat4 rotationMatrix = mat4(
cos(angle), 0, -sin(angle), 0,
0, 1, 0, 0,
sin(angle), 0, cos(angle), 0,
0, 0, 0, 1
);
return rotationMatrix;
}
mat4 rotateZ(float angle) {
mat4 rotationMatrix = mat4(
cos(angle), -sin(angle), 0, 0,
sin(angle), cos(angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
return rotationMatrix;
}
mat4 scale(float amt) {
return mat4(
amt,0,0,0,
0,amt,0,0,
0,0,amt,0,
0,0,0,1
);
}
mat4 translate(float x, float y, float z) {
return mat4 (
1,0,0,0,
0,1,0,0,
0,0,1,0,
x,y,z,1
);
}
void main() {
mat4 scaleMatrix = scale(scaleAmount);
mat4 translateMatrix = translate(translateXAmount, translateYAmount, translateZAmount);
mat4 rotateMatrix = rotateX(rotateXAmount) * rotateY(rotateYAmount) * rotateZ(rotateZAmount);

//note that the order the transformations are applied will impact the result
mat4 transformationMatrix = translateMatrix * rotateMatrix * scaleMatrix;
gl_Position = projection * modelview * transformationMatrix * position;

vertColor = color;
}

The final result:

What the sketch looks like at the start
The shape can be scaled, translated, and rotated using the sliders

Feel free to reach out to me for a chat or if you have any questions/comments/suggestions :)

--

--

Soumya

Budding software developer passionate about all things mobile