[Three.js] Vector Normalization: Why Set the Length to 1? | chaen
[Three.js] Vector Normalization: Why Set the Length to 1?
An in-depth look at why we normalize vectors, connecting game movement logic with Blender's "Apply Scale." Learn why separating direction from magnitude is crucial for consistent physics and lighting.
#Three.js
등록일 2026-01-032026-01-037
Comments
The password you enter is used to open, edit, and delete secret comments.
When studying Three.js, you often see .normalize() attached to vectors almost like a ritual. Most tutorials just say "it's useful" and move on, but that only made me more curious.
Why turn a perfectly fine vector into length 1? When is it absolutely necessary, and when can we skip it?
Thinking back, this wasn't a entirely new concept. I use Blender frequently, and I've always been told to "Apply Scale" after resizing an object. The reason was always "because things might break later," but I never truly understood why—I just did it out of habit.
Digging into Three.js normalization, I started to see that the Blender habit was actually solving the same problem in a different way.
How I Understand Normalization
Normalization is the process of keeping the direction while setting the length to exactly 1.
Discard "How far it goes"
Keep "Which direction it points"
Therefore, a normalized vector is usually used for:
Direction
View/Line of sight
Normals (Surfaces)
Movement input vectors
They all share a common trait: Direction is more important than magnitude.
Why Isn't This a Problem in Grid-Based Games?
Think of a grid game like Chess or older Pokemon. These worlds aren't continuous spaces.
Move one tile left
Move one tile right
Move one tile diagonally
Mathematically, a diagonal move is √2 ≈ 1.41, but in game rules, it's just "one move."
📌 What matters is the rule, not the exact distance value. Thus, the "diagonal is faster" problem never arises.
Why Does This Cause Issues in Three.js / WebGL?
The space handled in Three.js is completely different:
Coordinates are (x, y, z)
Values are floats (decimals)
Movement is calculated distance, not "tiles."
If you don't normalize here, problems appear immediately.
⚠️ Diagonal Movement Without Normalization
Input W + D → (1, 1). The length of this vector is √2 ≈ 1.41.
This means in the same frame:
Straight move → 1
Diagonal move → 1.41
This isn't just a feeling of speed; the actual position value moves further.
Resulting Issues:
🧱 Collision Bugs: Passing through thin walls or corners because you moved too far in a single frame.
⏱ Arrival Imbalance: A character moving diagonally always arrives at the destination faster (distance advantage, not just speed).
This is Why We Normalize (Free Movement Systems)
In free-movement games, we follow this flow:
Interpret input as Intent of Direction (e.g., means "I want to go diagonal").
Movement
Input Vector
Actual Length
After Normalization
Right
(1, 0)
1.0
1.0
Up
(0, 1)
1.0
1.0
Diagonal
(1, 1)
1.41
1.0
👀 Visually, you go diagonal.
📐 Mathematically, you move the same distance.
The Real Reason for "Apply Scale" in Blender
This is where Blender connects. Normal Vectors in Blender represent the "direction of a face" for lighting calculations. These normals are always assumed to have a length of 1.
If you scale an object and don't Apply Scale:
Internal vector lengths increase.
The normal is no longer just a "direction value."
Why Does Lighting Break? (Dot Product Perspective)
Lighting is usually calculated using the Dot Product.
Normal Case:
Face Normal length = 1
Light Direction length = 1
→ Result is 0 ~ 1 (The expected brightness).
Broken Case (Scale not applied):
Face Normal length = 100
Light Direction length = 50
→ Result = 5000
📉 This causes:
Overexposed highlights (blown out white).
Weird color artifacts.
Broken shading.
👉 Apply Scale is essentially saying: "Reset these vectors back to length 1."
⚠️ A Note of Caution in Three.js
.normalize()mutates the original vector.
js
If you need to keep the original position or magnitude, clone it first.
js
const direction =
🧭 When to Use It in Three.js?
You don't need it everywhere.
But the moment you think of "Direction," you almost always use it.
1️⃣ Custom Movement Logic
If you use OrbitControls, it's handled internally. But if you make your own:
Don't need it:mesh.position.x += 1; (Fixed coordinate move).
Must use it: Velocity-based movement.
js
Without normalization, the speed would vary depending on the distance.
2️⃣ Mouse Interactions (Raycaster)
A Ray is a Direction Vector.
js
raycaster.setFromCamera(mouse, camera);
Raycasters assume the direction vector is normalized. If it isn't, click detection might happen at wrong positions or affect performance.
3️⃣ Shaders / Materials
If you use built-in materials, Three.js handles it. But you must intervene when:
Using ShaderMaterial: In GLSL, vec3 lightDir = normalize(lightPos - vPos); is mandatory. Skipping it causes light to "explode" or flicker.
Summary
Normalization isn't just a math trick. It is:
A fair distance standard for movement.
A standard for brightness in lighting.
The intentional separation of direction and magnitude.
Now, the reason to use .normalize() is crystal clear.
(1, 1)
.normalize() → (0.7, 0.7).
Multiply by Speed.
const v = new THREE.Vector3(3, 4, 0);v.normalize(); // v is now (0.6, 0.8, 0)
v.
clone
().
normalize
();
const direction = target.clone().sub(mesh.position).normalize();mesh.position.add(direction.multiplyScalar(speed));
After heavy scale distortion: Like mesh.scale.set(10, 1, 1);.