By Dave Pagurek, Austin Lee Slominski, Adam Ferriss
This tutorial will introduce new concepts for creating 3D sketches with p5.js, including x, y, and z.
Let’s start by setting up the p5.js to use WebGL, by passing WEBGL
as a third parameter into createCanvas()
.
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
describe('A red box on a white background.');
}
function draw() {
background(255);
fill(255, 0, 0);
box();
}
3D coordinate space
2D sketches use the x- and y-axes to define horizontal and vertical positions. 3D sketches extend this model with a z-axis which defines depth. When drawing in 2D, the point (0,0) is located at the top left corner of the screen. In WebGL mode, the origin of the sketch (0,0,0) is located in the middle of the screen. By default, the x-axis goes left to right, the y-axis goes up to down, and the z-axis goes from further to closer, “out of” the screen.
You can call debugMode()
in your setup()
function to add a grid on the x- and z-axes and the red-green-blue x, y, and z arrows to your sketch, similar to the illustration above.
Transformations: Position and Size of 3D Shapes
p5.js has a few functions that we can use to position and orient objects within 3D space: translate()
, rotate()
, and scale()
. Collectively these are known as the transformation of an object. These methods are available for both 2D and 3D drawing.
translate()
: Moving Objects in Space
translate()
moves the origin to a given point. Anything drawn after we call translate()
will be positioned relative to that point. translate()
accepts arguments for x, y, and z values. Use the sliders in the sketch above to change the translation of the box and see how it moves along each axis. The code snippet below demonstrates simple translation on a box()
shape.
// draw a box 100 units to the right
translate(100,0,0);
box();
Try this!
A random walk involves moving in a random direction each step. Try doing a random walk in 3D using translate()
, drawing a cube after each step like a trail of breadcrumbs!
rotate()
: Orienting Objects in Space
rotate()
reorients whatever is drawn after it.
There are a few functions that can be used to rotate an object in 3D. Most of the time, it’s easiest to call functions like rotateX()
, rotateY()
, and rotateZ()
, which each allow for rotation around a specific axis. Each function accepts a single argument specifying the angle of rotation. Try moving the sliders in the example above to see how rotation is performed on each axis. The code below shows each of these methods in use.
// rotate X, Y, and Z axes by 45 degrees
rotateX(QUARTER_PI);
rotateY(QUARTER_PI);
rotateZ(QUARTER_PI);
box();
By default, p5.js expects angles to be in radians. Radians use numbers from 0 to TWO_PI
to specify an angle. To use degrees, either convert degrees to radians using radians(numberInDegrees)
or use angleMode(DEGREES)
:
// rotate each axis by 45 degrees
rotateX(radians(45));
box();
// or
angleMode(DEGREES);
rotateY(45);
box();
You can also use rotate(angle, axis)
, which allows you to specify which axis you’d like to rotate around using a vector as the second argument. This lets you rotate a shape about any axis you choose. In the example below, we create an axis using the mouse coordinates, and rotate about it:
scale()
: Size in Space
scale()
changes the size of whatever is drawn after it. Like the other functions described, it accepts arguments for x, y, and z values.
Try this!
Try making a cube jump up and down, but use scale()
to have it squash and stretch when it bounces to give it a cartoon feel!
Transforming Multiple Shapes
Transform functions are cumulative and affect everything drawn after them. For example, if you call translate(50, 0, 0)
twice, they add together, making it equivalent to calling translate(100, 0, 0)
once. However, sometimes you will want to draw different shapes with different transformations.
The push()
function saves the current transformations and style settings. Then, after performing new transformations, the pop()
function is used to restore us to the original transformations. The result is that whatever transformations or styling changes that are made between push()
and pop()
are isolated to that portion of the code. In the example below, we draw a number of boxes at their own distinct locations by placing their transforms between push()
and pop()
:
Transformation Matrices
While this is a more advanced topic, each of these transformations affects what is called the model matrix. The model matrix is combined with the view matrix and the projection matrix, both of which help simulate the view of a camera, and this combination results in our 3D scene! You can learn more about Model View Projection on MDN.
Try this!
Try making two spheres that can be moved around separately, with arrow keys controlling one, and WASD keys controlling the other.
The Order of Transformations Matters!
The way transformations affect each other can feel unpredictable at first because order matters. Each transformation always affects the next one. For example, if rotate()
is called, followed by translate()
, the direction of that translation will be affected by the rotation. The entire coordinate system is rotating and moving, not just the shape itself. Here are some recipes for different transformation orders that you can use.
The Default: Positioning Objects in a Scene
If you are drawing many objects, chances are that each one will have a unique position, orientation, and scale. For this, you should first translate()
to the center of where the object should be, then rotate()
to match its orientation, and finally scale()
to fit its size. This is a good order to use by default.
Try this!
Try making a solar system, where each planet has its own independent position as it moves around the sun, and each has its own rotation as they spin at different speeds!
Rotating Around a Pivot Point
Sometimes you want to rotate a shape about a point that isn’t its center. For this, you can translate()
to the pivot point, rotate()
or scale()
as needed, and then translate()
back to the center before drawing your shape. The sketch below uses this method to make a shape squash and stretch about its base rather than its center.
Try this!
Try making a character waving its arm! You will want to rotate the arm about the point where the arm connects to its body so that it stays connected.
Drawing with Symmetry
Sometimes you want to draw the same thing multiple times with mirror or radial symmetry. In these cases, you will end up drawing in a loop. For each iteration, rotate()
or scale()
depending on the type of symmetry you want. Finally, apply any other transformations needed to draw your shape.
In the example below, scale(-1, 1)
is used to add horizontal symmetry to draw the eyes and ears of a face:
In this example, rotateY()
is used to create radial symmetry for a stonehenge scene.
Try this!
Try giving a body with arms and legs to the head in the example!
Conclusion
By having control over 3D coordinates, you will be able to create more complex scenes in 3D. The tutorials that follow this one build on these skills by giving you more control over what you draw after you have transformed it.
Glossary
GPU
The GPU (Graphics Processing Unit) is a piece of hardware that is particularly well suited for performing many calculations in parallel, making it powerful for 3D graphics.
Model
A custom 3D geometry that can be saved and loaded from a file.
Matrix
A special array that can hold information about the transformation of a geometry.
Camera
The viewpoint of a 3D scene.
Transformation
The combined scale, rotation, and translation of a geometry. The verb transform is used when describing altering these properties.
Vertex
A point in 3D space with an x, y, and z position.
Face
A collection of three points that create a solid surface.
相关参考
translate
转换坐标系统。 默认情况下,在 2D 模式和 WebGL 模式中心,原点 (0, 0) 位于画布的左上角。 translate() 函数将原点移动到不同的位置。在调用 translate() 后绘制的所有内容将会被移动。有两种方式可以调用 translate() 来设置原点的位置参数。 第一种调用 translate() 的方式是使用数字设置平移的量。前两个参数 x 和 y 设置沿正 x 轴和正 y 轴的平移量。例如,调用 translate(20, 30) 将原点沿 x 轴移动 20 个像素,沿 y 轴移动 30 个像素。第三个参数 z 是可选的。它设置沿正 z 轴的平移量。例如,调用 translate(20, 30, 40) 将原点沿 x 轴移动 20 个像素,沿 y 轴移动 30 个像素,沿 z 轴移动 40 个像素。 第二种调用 translate() 的方式是使用 p5.Vector 对象来设置平移的量。例如,调用 translate(myVector) 使用 myVector 的 x、y 和 z 分量来设置沿 x、y 和 z 轴的平移量。这样做与调用 translate(myVector.x, myVector.y, myVector.z) 是相同的。 默认情况下,变换会累积。例如,调用 translate(10, 0) 两次与调用 translate(20, 0) 一次具有相同的效果。可以使用 push() 和 pop() 函数来隔离不同绘制组内的变换。 注意:变换会在绘制循环的开头被重置。在 draw() 函数内调用 translate(10, 0) 不会导致形状持续移动。 .
rotate
旋转坐标系。 默认情况下,正 x 轴指向右侧,正 y 轴指向下方。rotate() 函数通过围绕原点旋转坐标系来改变这种方向。在调用 rotate() 之后绘制的所有内容都将呈现为旋转状态。 第一个参数 angle 是旋转的角度。例如,调用 rotate(1) 将坐标系顺时针旋转 1 弧度,约为 57 度。rotate() 根据当前的 angleMode() 解释角度值。 第二个参数 axis 是可选的。它用于定位 WebGL 模式中的 3D 旋转。如果传递了一个 p5.Vector,如 rotate(QUARTER_PI, myVector),那么坐标系将围绕 myVector 旋转 QUARTER_PI 弧度。如果传递了一个向量分量数组,如 rotate(QUARTER_PI, [1, 0, 0]),那么坐标系将围绕具有分量 [1, 0, 0] 的向量旋转 QUARTER_PI 弧度。 默认情况下,转换会累积。例如,连续调用两次 rotate(1) 与调用一次 rotate(2) 效果相同。可以使用 push() 和 pop() 函数将转换隔离到不同的绘图组中。 注意:转换会在绘制循环的开始处重置。在 draw() 函数中调用 rotate(1) 不会导致形状旋转。 .
scale
缩放坐标系。 默认情况下,形状以其原始比例绘制。一个宽度为 50 像素的矩形在 100 像素宽的画布上看起来占据了一半的宽度。 scale() 函数可以缩小或拉伸坐标系,使形状以不同的大小呈现。有两种方式可以使用参数来调用 scale(),以设置缩放因子。 第一种调用 scale() 的方式使用数字来设置缩放量。第一个参数 s 设置每个轴的缩放量。例如,调用 scale(2) 将 x 轴、y 轴和 z 轴拉伸 2 倍。接下来的两个参数 y 和 z 是可选的。它们设置 y 轴和 z 轴的缩放量。例如,调用 scale(2, 0.5, 1) 将 x 轴拉伸 2 倍,y 轴缩小 0.5 倍,z 轴保持不变。 第二种调用 scale() 的方式使用一个 p5.Vector 对象来设置缩放因子。例如,调用 scale(myVector) 使用 myVector 的 x、y 和 z 分量来设置沿 x、y 和 z 轴的缩放量。这样做与调用 scale(myVector.x, myVector.y, myVector.z) 是一样的。 默认情况下,转换会累积。例如,连续两次调用 scale(1) 与调用一次 scale(2) 效果相同。可以使用 push() 和 pop() 函数将转换隔离到不同的绘图组中。 注意:转换会在绘制循环的开始处重置。在 draw() 函数中调用 scale(2) 不会导致形状持续增长。 .
push
Begins a drawing group that contains its own styles and transformations.
pop
Ends a drawing group that contains its own styles and transformations.