This article is the 3nd in a series of articles that will hopefully teach
you about 3D math. Each one builds on the previous lesson so you may find
them easiest to understand by reading them in order.
Translation
Rotation
Scaling ⬅ you are here
Matrix Math
Orthographic Projection
Perspective Projection
Cameras
Matrix Stacks
Scene Graphs
Scaling is just as easy as translation .
We multiply the vertex positions by our desired scale. Here are the changes
to the shader from our previous example .
struct Uniforms { color : vec4f , resolution : vec2f , translation : vec2f , rotation : vec2f , scale : vec2f , }; struct Vertex { @location ( 0 ) position : vec2f , }; struct VSOutput { @builtin ( position ) position : vec4f , }; @group ( 0 ) @binding ( 0 ) var <uniform> uni : Uniforms ; @vertex fn vs ( vert : Vertex ) -> VSOutput { var vsOut : VSOutput ; // Scale the position let scaledPosition = vert . position * uni . scale ; // Rotate the position let rotatedPosition = vec2f ( vert . position . x * uni . rotation . y - vert . position . y * uni . rotation . x , vert . position . x * uni . rotation . x + vert . position . y * uni . rotation . y scaledPosition . x * uni . rotation . y - scaledPosition . y * uni . rotation . x , scaledPosition . x * uni . rotation . x + scaledPosition . y * uni . rotation . y ); // Add in the translation let position = rotatedPosition + uni . translation ; // convert the position from pixels to a 0.0 to 1.0 value let zeroToOne = position / uni . resolution ; // convert from 0 <-> 1 to 0 <-> 2 let zeroToTwo = zeroToOne * 2.0 ; // covert from 0 <-> 2 to -1 <-> +1 (clip space) let flippedClipSpace = zeroToTwo - 1.0 ; // flip Y let clipSpace = flippedClipSpace * vec2f ( 1 , - 1 ); vsOut . position = vec4f ( clipSpace , 0.0 , 1.0 ); return vsOut ; } hide deleted
And, like before, we need to update our uniform buffer to have room for
the scale value.
// color, resolution, translation, rotation, padding const uniformBufferSize = ( 4 + 2 + 2 + 2 ) * 4 + 8 ; // color, resolution, translation, rotation, scale const uniformBufferSize = ( 4 + 2 + 2 + 2 + 2 ) * 4 ; const uniformBuffer = device . createBuffer ({ label : 'uniforms' , size : uniformBufferSize , usage : GPUBufferUsage . UNIFORM | GPUBufferUsage . COPY_DST , }); const uniformValues = new Float32Array ( uniformBufferSize / 4 ); // offsets to the various uniform values in float32 indices const kColorOffset = 0 ; const kResolutionOffset = 4 ; const kTranslationOffset = 6 ; const kRotationOffset = 8 ; const kScaleOffset = 10 ; const colorValue = uniformValues . subarray ( kColorOffset , kColorOffset + 4 ); const resolutionValue = uniformValues . subarray ( kResolutionOffset , kResolutionOffset + 2 ); const translationValue = uniformValues . subarray ( kTranslationOffset , kTranslationOffset + 2 ); const rotationValue = uniformValues . subarray ( kRotationOffset , kRotationOffset + 2 ); const scaleValue = uniformValues . subarray ( kScaleOffset , kScaleOffset + 2 ); hide deleted
and at render time we need to update the scale
const settings = { translation : [ 150 , 100 ], rotation : degToRad ( 30 ), scale : [ 1 , 1 ], }; const radToDegOptions = { min : - 360 , max : 360 , step : 1 , converters : GUI . converters . radToDeg }; const gui = new GUI (); gui . onChange ( render ); gui . add ( settings . translation , '0' , 0 , 1000 ). name ( 'translation.x' ); gui . add ( settings . translation , '1' , 0 , 1000 ). name ( 'translation.y' ); gui . add ( settings , 'rotation' , radToDegOptions ); gui . add ( settings . scale , '0' , - 5 , 5 ). name ( 'scale.x' ); gui . add ( settings . scale , '1' , - 5 , 5 ). name ( 'scale.y' ); function render () { ... // Set the uniform values in our JavaScript side Float32Array resolutionValue . set ([ canvas . width , canvas . height ]); translationValue . set ( settings . translation ); rotationValue . set ([ Math . cos ( settings . rotation ), Math . sin ( settings . rotation ), ]); scaleValue . set ( settings . scale ); // upload the uniform values to the uniform buffer device . queue . writeBuffer ( uniformBuffer , 0 , uniformValues );
And now we have scale. Drag the sliders.
One thing to notice is that scaling by a negative value flips our geometry.
Another thing to notice is it scales from 0, 0 which for our F is the
top left corner. That makes sense since we’re multiplying the positions
by the scale they will move away from 0, 0. You can probably
imagine ways to fix that. For example you could add another translation
before you scale, a pre scale translation. Another solution would be
to change the actual F position data. We’ll go over another way soon.
I hope these last 3 posts were helpful in understanding
translation , rotation
and scale. Next we’ll go over the magic that is matrices
that combines all 3 of these into a much simpler and often more useful form.