In this unit, we will try to use simple 2D drawing methods to create basic 3D graphics, which can also help you better understand, how the 3D graphics system creates perspective.
1.Beginning
Let’s start with a video:
This is the first 3D computer graphics applied to movies, created by artist Larry Cuba, he and John Whitney are both very important people in the field of experimental film and computer graphics.
2.2D Perspective Projection
In order to draw a 3D scene, we need to create depth. So we need to understand the idea of perspective and projection.
We can create a simple perspective projection through code:
First, we create a bunch of random coordinates. They are many 3D vectors. We also call them vertices. When we create them in 3D, we assign x, y and z coordinates to each vertex, and the z coordinate is the vertex. Provides depth.
We start with a bunch of 3D points, x,y,z; then we use z to work out new positions for x and y. So we are going to scale the x and y coordinates of each vertex based on a value we derive from the z coordinate of each vertex.
We get this new value by adding the Field of View to the z coordinate, and dividing the FOV by the result.
3. Field of View
We could just use the z coordinate to directly scale the x and y values, as well as the size of the point we will draw. However, this really doesn’t work very well, and don’t simulate real world experience as well as we might like.
In order to make this a bit better, we generate an intermediate value called a Field of View. Field of view is basically a term that means ‘what the camera can see’, but it also describes the extent to which we understand size and distance in a visual scene.
One simple way to emulate field of view mathematically to scale a vertex is to generate a value, add the z coordinate of the vertex, and divide by the starting value.
This produces a factor that represents the ratio of the field of view to the vertex z position. There are other ways of doing this which are slightly better, but we will come on to those later.
scale = for / (fov + z3d);
x = x * scale;
y = y * scale;
z = z;
1 | var canvas = document.querySelector("canvas"); |
4.Unit Vector
The unit vector gives you a normalised vector with a length of 1. It doesn’t tell you how far away things are; instead, it tells you what direction you need to go in to get to them. It provides this information as a cartesian coordinate.
How can we calculate the unit vector?
Get the difference v between points a and b; this is just an element wise subtraction.
Then, divide each element in v by the magnitude of v.(Remember, v is the difference in three dimensions)
The magnitude of v is the same as the distance between a and b.
5.Vertices Faces and Primitives
Vertices are collections of 3D position vectors; a 3D object usually has a number of vertices. We use these vertices to describe the faces of a 3D object, often do this by using the vertices to specify triangles; we can also use ‘quads’, which we can think of as planes.
Planes are very basic 2D shapes in 3D space; planes have four vertices, they are usually square, but don’t have to be. Planes have 1 face, or 2faces if you want to see both side. When you do that ,it is no longer 2D. It becomes an infinitely thin 3D object. In openGL, we need to specify that a collection of vertices is a face. We also need to tell the renderer how to render that face.
3D shapes are sometimes called Primitives. Some easy primitives to generate are the Platonic solids, platonic solids are interesting because each face of a platonic solid is the same size and shape.
6.Scaling, Rotating and Translating with Matrices
We can use matrix transformations to do this all for us.
This is what openGL does behind the scenes. It has a bunch of matrix operations that you can set up with a single command; this handles the whole pipeline.
Order of Transformations
- TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector;
In openGL, depth is usually represented along the z axis; in order to render 3D objects properly in openGL, you need to use something called a “Depth Buffer”, or z buffer. The z buffer is disabled by default, which means when you start coding in openGL, you can not draw depth properly.
The z buffer knows what the current z value is for each pixel on the screen; if the z value in a 3D object is greater than the z value in the buffer for that pixel, the z buffer draws the pixel. To enable the depth buffer in openFrameworks, you would do this:
ofEnableDepthTest();
7.Works
Below I will show some drawing examples:
I also share some other formulas, which can replace my code to explore more interesting things!
蝶形曲線 butterfly curve
x= Math.exp(Math.cos(j))-2Math.cos(4j)-Math.pow(Math.sin(j/12),5)*Math.sin(j)
y= Math.exp(Math.cos(j))-2Math.cos(4j)-Math.pow(Math.sin(j/12),5)*Math.cos(j)
*Final:**var point = [(Math.exp(Math.cos(spacingj))-2Math.cos(spacing4j)-Math.pow(Math.sin(spacingj/12),5))Math.sin(spacingj) * s,(Math.exp(Math.cos(spacingj))-2Math.cos(spacing4j)-Math.pow(Math.sin(spacingj/12),5))Math.cos(spacingj) s,z];
**Wiki:**https://en.wikipedia.org/wiki/Butterfly_curve_(transcendental)
紡錘線 dumbbell Curve
x=a*j
y=a*Math.pow(j,2)*Math.sqrt(1-Math.pow(j,2))
三尖瓣線 tricuspoid
x= 2aMath.cos(j)+aMath.cos(2aj);
y= 2aMath.sin(j)-aMath.sin(2aj);
Final: var point = [(2Math.cos(spacingj)+Math.cos(2spacingj))* s,(2Math.sin(spacingj)-Math.sin(2spacingj))* s,z];
**Wiki:**https://en.wikipedia.org/wiki/Deltoid_curve
擺線 epicycloid
x= 2Math.cos(j) - Math.cos(2j);
y= 2Math.sin(j) - Math.sin(2j);
*Final:**var point = [(2Math.cos(j) - Math.cos(2j)) s,(2Math.sin(j) - Math.sin(2j))* s,z];
// when a=2b, nephroid
x= 3Math.cos(j) - Math.cos(3j);
y= 3Math.sin(j) - Math.sin(3j);
*Final:**var point = [(3Math.cos(j) - Math.cos(3j)) s,(3Math.sin(j) - Math.sin(3j))* s,z];
歸納與總結:
var side= 5; // Q為任意的大於3的數都可以生成角度。
var point = [(sideMath.cos(j) + Math.cos(sidej))* s,(sideMath.sin(j) - Math.sin(sidej))* s,z];
Web Sketchpad
x= Math.cos(14j) +Math.cos(14j)/2+Math.sin(3*j)/3
y= Math.sin(14j) +Math.sin(14j)/2+Math.cos(3*j)/3
*Final:**var point = [(Math.cos(20j) +Math.cos(20j)/2+Math.sin(1j)/3)* s,(Math.sin(20j) +Math.sin(20j)/2+Math.cos(1j)/3) s,z];
(我也不知道為啥沒有生成游泳圈的樣子)= =
x=((1+Math.pow(Math.sin(j),2))*Math.cos(j))
y=((Math.pow(Math.sin(j),2)-1)*Math.sin(j))
// 添加項數:Math.round(Math.random()) 隨機抽取掉50%的點,投射到(0, 0, z)上
Curves Defined by Parametric Equations
Other example from Wiki
x= Math.cos(80*j) -Math.cos(j)*Math.sin(j)
y= 2Math.sin(j)-Math.sin(80j)
x= Math.cos(9j)-Math.pow(Math.cos(100j),3);
y= Math.sin(200j)-Math.pow(Math(9j),4);
雙曲八面體 Hyperbolic Octahedron
x= Math.pow(Math.cos(j/2)*Math.cos(j),3);
y= Math.pow(Math.sin(j/2)*Math.cos(j),3);
z= Math.pow(Math.sin(j),3)
心形線 heart Curve
x= Math.cos(2j)+Math.cos(6j)/2+Math.sin(4*j)/3;
y= Math.sin(2j)+Math.sin(6j)/2+Math.cos(4*j)/3;
About this Post
This post is written by Siqi Shu, licensed under CC BY-NC 4.0.