the day you finally use algebra: a 3d math primer
TRANSCRIPT
- Colors, like the screen dimensions, are based on percentages rather than absolute values.
- If you come from a graphic design background, you need to convert your 255 scale to percentages.
Rosetta Stone- Had the same text in
Greek, demotic, and hieroglyphics. Was used to translate hieroglyphics
- Going to do similar thing, but with math algorithms, plain English, and code
∑5
i = 1
4i
Algoritm
I have a starting value of one. I have an end value of five. I want to multiply each value by four and add them together.
Plain English
I have a starting value of one. I have an end value of five. I want to multiply each value
by four and add them together.
It’s All Greek to Me
π
∆
i
θ
Constant: 3.14159…
Change between two values
Square root of negative one
Variable representing an angle
Consolidate slide, get rid of alpha and beta
Triangles
A shape with three sides where the angles add up to 180 degrees
Everything in our world comes back to triangles
The most stable shape
Foundation of 3D graphics
So What Can We Do Knowing This?
Change the direction a character is moving in
Check to see if the user is hitting a target area on the screen
Draw shapes and filters in specific configurations
Linear Algebra
Google “Better Explained Rotation” Matrices Complex numbers Show how to use linear algebra to do more efficient affine transforms rotation through matrix multiplications
What is Linear Algebra?
Linear Algebra allows you to perform an action on many values at the same time.
This action must be consistent across all values, such as multiplying every value by two.
What is Linear Algebra?
Values are placed in an object called a matrix and the actions performed on the values are called transforms
Linear algebra is optimized for parallel mathematical operations.
Data Types
vec2, vec3, vec4: 2D, 3D, and 4D floating point vector objects.
vec2: (x, y)
vec3: (x, y, z)
vec4: (r, g, b, a)
Data Types
mat2, mat3, mat4: 2, 3, and 4 element matrices.
mat2: Holds a 2 X 2 number matrix
mat3: Holds a 3 X 3 number matrix, used for 2D linear algebra
mat4: Holds a 4 X 4 number matrix, used for 3D linear algebra
mat4 genericMatrix = mat4( !
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0, 0, 0, 0
);
Column
Row
Column major vs Row major??
vec4 firstColumn = vec4(1.0, 1.0, 1.0, 1.0);
vec4 secondColumn = vec4(1.0, 1.0, 1.0, 1.0);
vec4 thirdColumn = vec4(1.0, 1.0, 1.0, 1.0);
vec4 fouthColumn = vec4(0, 0, 0, 0);
mat4 myMatrix = mat4( firstColumn, SecondColumn, thirdColumn, FourthColumn
);
Affine, Wha?? :(A transform is any function that alters the size, position, or rotation of an object on your screen.
Four types: Identity, Translate, Rotation, and Scale.
For a transform to be affine, the lines in your shape must be parallel.
CGAffine Transform Methods
CGAffineTransformMakeRotation (GLFloat angle);
CGAffineTransformMakeScale (CGFLoat sx, CGFloat sy);
CGAffineTransformMakeTranslation (CGFloat tx, CGFloat ty);
Affine Transform Rotate Talk about chaining transforms CAAffineTransform
new point x = a * x + c * y + tx;
new point y = b * x + d * y + ty;
Show the matrix of the vector and the actual math around it
How Does This Work?
For each point in your shape, the computer uses this calculation to figure out where the point should be.
If you have a rectangle, this gets run four times: One for each point in your shape.
void main() { highp vec2 textureCoordinateToUse = vec2(textureCoordinate.x, (textureCoordinate.y * aspectRatio + 0.5 - 0.5 * aspectRatio)); highp float distanceFromCenter = distance(center, textureCoordinateToUse); lowp float checkForPresenceWithinSphere = step(distanceFromCenter, radius); distanceFromCenter = distanceFromCenter / radius; highp float normalizedDepth = radius * sqrt(1.0 - distanceFromCenter * distanceFromCenter); highp vec3 sphereNormal = normalize(vec3(textureCoordinateToUse - center, normalizedDepth)); highp vec3 refractedVector = 2.0 * refract(vec3(0.0, 0.0, -1.0), sphereNormal, refractiveIndex); refractedVector.xy = -refractedVector.xy; highp vec3 finalSphereColor = texture2D(inputImageTexture, (refractedVector.xy + 1.0) * 0.5).rgb; // Grazing angle lighting highp float lightingIntensity = 2.5 * (1.0 - pow(clamp(dot(ambientLightPosition, sphereNormal), 0.0, 1.0), 0.25)); finalSphereColor += lightingIntensity; // Specular lighting lightingIntensity = clamp(dot(normalize(lightPosition), sphereNormal), 0.0, 1.0); lightingIntensity = pow(lightingIntensity, 15.0); finalSphereColor += vec3(0.8, 0.8, 0.8) * lightingIntensity; gl_FragColor = vec4(finalSphereColor, 1.0) * checkForPresenceWithinSphere; }
So what this calculation does is it adjusts for a non-square aspect ratio of the image. The image aspect ratio is passed in as a uniform, and this adjusts the normally 0.0-1.0 texture coordinate for the Y axis to instead be from 0.0-1.0*(imageHeight/imageWidth). It has to expand the Y axis coordinate about its center point (0.5), thus the weird addition and subtraction in there. If you don’t do this, your sphere turns into an egg in non-square images.
highp vec2 textureCoordinateToUse = vec2(textureCoordinate.x, (textureCoordinate.y * aspectRatio + 0.5 - 0.5 * aspectRatio));
So what this calculation does is it adjusts for a non-square aspect ratio of the image. The image aspect ratio is passed in as a uniform, and this adjusts the normally 0.0-1.0 texture coordinate for the Y axis to instead be from 0.0-1.0*(imageHeight/imageWidth). It has to expand the Y axis coordinate about its center point (0.5), thus the weird addition and subtraction in there. If you don’t do this, your sphere turns into an egg in non-square images.
highp float distanceFromCenter = distance(center, textureCoordinateToUse);
This is a Pythagorean distance calculation to determine how far the current pixel (texture coordinate) is from the center that we’ve provided as a uniform. It’s a sqrt(xdiff^2 + ydiff^2) calculation.
lowp float checkForPresenceWithinSphere = step(distanceFromCenter, radius);
The step() function returns 1 if the second value is greater than the first, 0 if not. I use these to avoid if() statements, which are expensive in fragment shaders (branching does not work well in massively parallel operations).
distanceFromCenter = distanceFromCenter / radius;
This normalizes the distance from the center to be 0.0 for a value at the center of the sphere, and 1.0 for a value at the edge of the sphere. Values outside that range will get filtered out by the product of the above step() calculation later on.
highp float normalizedDepth = radius * sqrt(1.0 - distanceFromCenter * distanceFromCenter);
This is where we calculate the Z height that a sphere, cut in half, would extend above the plane of the image. This is another geometrical calculation based on knowing the distance of our point from the center of the sphere.
highp vec3 sphereNormal = normalize(vec3!(textureCoordinateToUse - center, normalizedDepth));
Once we know the height of the spherical cap at that point, we can calculate the normal for that point on the sphere’s surface. Think of it as a ray that extends from the center of the sphere to the surface at this X, Y coordinate. The normal is based on the center of the sphere, so we subtract our aspect-ratio-adjusted-coordinate from the center to get the relative X, Y coordinate from the center of the sphere. The Z component is the height of the sphere we just calculated.
highp vec3 refractedVector = refract(vec3(0.0, 0.0, -1.0), !sphereNormal, refractiveIndex);
With the normal, we can calculate the refraction of light from that point on the sphere’s surface. The refraction calculation uses the surface normal, the refractiveIndex (a material property that you pass in, I think I use glass’s here), and a ray direction. I believe the ray direction here is from the eye going into the screen, although I can never remember positive/negative Z directions in OpenGL. This then generates a refracted vector, pointing in the direction light would as it refracts through a sphere.
gl_FragColor = texture2D(inputImageTexture, (refractedVector.xy + 1.0) * 0.5) * checkForPresenceWithinSphere;
We then take this refracted vector, which is in the -1.0-1.0 coordinate space, and adjust it to the 0.0-1.0 coordinate space for texture sampling. We read the texture color at the location pointed to by that vector. The earlier step() function to determine if a point was within the sphere comes into play here, where we only display a color if the point was within the sphere. If it was not, we output 0.0, 0.0, 0.0, 0.0 as an RGBA color because that’s the result of multiplying with 0.