Intro
I'll detail below how to align 3D objects in your scene with a perspective camera. However, doing so in the same scene as your other objects is actually somewhat difficult, so first off, ask yourself if you really need to align 3D objects. For example, if it's text or a flat image, you can just use HTML with CSS. It's far easier to set up and manage.
One solution I won't be covering here is to render the items you want aligned with an orthographic camera. It's quite easy to set up because orthographic math is actually really simple, but I won't detail that here as there's many examples (1, 2) on the internet of how to do that. Using an orthographic camera is probably the solution you should pursue, though.
I personally needed a 3D visor for a game I'm making, and needed the player to be able to take the visor off. At the same time, I wanted things to dynamically align with the screen resolution, and things needed to align to any edge of the screen. So that's what I'll cover.
Assumptions
I'll assume your objects won't spontaneously move further away from the camera (they can, but will move towards the canvas center as they do). This is because they would need to move along the edge of a cone to align properly, but my math skills are rusty, so I don't know how to write a formula for that. Instead I'll offer constants that work for me.
1: Figure your ratios and constants
Have a look at the screenshots that follow. In my case specifically, at a 1:1 resolution, the spaceship's dashboard is at the bottom of the screen, while the windshield's upper edge is nearly at the top. At a 16:9 resolution, this remains true. However, at a 1:1 resolution you can see less of the cockpit's sides than at 16:9.
What this means is that the x-axis (left-to-right) is effected by resolution changes, while the y-axis (up/down) is not affected by resolution changes at all. A different way to put this is that, in this case, the aspect ratio only affects the x-axis.
1:1 resolution:

Widescreen resolution:

2: Determine your center
We need a well-defined 0-center point to make things easy. I'll just be attaching my 3D objects to my camera. That way, they're in the center by default, assuming your object origin is centered. If your origin is not in the center, you use a bounding box to force things to the center.
Note: if attaching to the camera is not ideal, one option is to attach to the camera only when resizing, and then unattach when done. Attaching doesn't change world position, so this will work.
3: The forward-axis
Aligning things in 3D means it needs to be in front of your face at some predefined point. Seeing as I'm making a visor, I place my objects roughly 5 centimeters from the player's face. To keep things simple, I ensure that all my Blender models are exactly 1 meter at the longest edge (note that I really do mean exactly 1m, not "at least" or "at most").
4: The code
All the below assumes that 1 unit of distance is 1 meter. This helps keep things easy to reason about, and consistent with tools like Blender (assuming you look at dimensions and not scale).
I'll demo a center-align, a right-align, and bottom-right align. The rest are unnecesary to show here beccause that should be enough for you to figure out the rest (for example, just flip the -
sign to +
to change a align bottom to a align top)
// --- Alignment code ----------------------------------------------- //
// Place objects 5 centimeters from the person's face.
const DISTANCE_TO_FACE = -0.05;
// This number is the ratio between your distance from the camera, and what you
// define as unit length "1". I've found this to fit correctly in all
// circumstances, assuming your mesh's longest edge is exactly 1 meter.
const BASE_SCALE = 0.025;
// It's safe to set this to zero.
const MARGIN_RIGHT = 0.005;
// This defines where the top and bottom of our screen is. This number is
// dependenent on the DISTANCE_TO_FACE value.
const Y_MAX = 0.025;
// We need a means of representing window witdh as a tiny unit. This helps
// with doing so.
const WIDTH_FACTOR = 0.00001;
function alignCenter(mesh, scale=1) {
let trueAspect = window.innerWidth / window.innerHeight;
let relAspect = trueAspect > 1 ? 1 / trueAspect : trueAspect;
// Always choose a ratio that's between 0 and 1. We use this to
// dynamically resize HUD elements according to window size.
const size = relAspect * BASE_SCALE * scale;
mesh.scale.setScalar(size);
// Note: if your origin is not center, have a look at:
// https://threejs.org/docs/#api/en/core/BufferGeometry.computeBoundingBox
// You can use that to center-align anything.
}
function alignRight(mesh, scale=1) {
const offset = 0.005;
let trueAspect = window.innerWidth / window.innerHeight;
let relAspect = trueAspect > 1 ? 1 / trueAspect : trueAspect;
// Always choose the ratio that's between 0 and 1. We use this to
// dynamically resize HUD elements according to window size.
const size = relAspect * BASE_SCALE * scale;
mesh.scale.setScalar(size);
// Make this negative to align left.
mesh.position.x = (window.innerWidth * trueAspect * WIDTH_FACTOR) + MARGIN_RIGHT;
}
function alignBottomRight(mesh, scale=1) {
alignRight(mesh, scale);
// Now that we've aligned right, just align it bottom.
let trueAspect = window.innerWidth / window.innerHeight;
mesh.position.y = -Y_MAX; // Make this positive to align top.
}
// --- Geometry ----------------------------------------------------- //
// We'll build our cubes from this.
const geometry = new THREE.BoxGeometry(1, 1, 1);
//
const redMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const greenMat = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const blueMat = new THREE.MeshBasicMaterial({ color: 0x0000ff });
// The cubes we'll be aligning.
const centerCube = new THREE.Mesh(geometry, redMat);
const rightCube = new THREE.Mesh(geometry, greenMat);
const bottomRightCube = new THREE.Mesh(geometry, blueMat);
// --- Setup -------------------------------------------------------- //
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
const camera = new THREE.PerspectiveCamera(
60, window.innerWidth / window.innerHeight, 0.001, 1000);
// Setup scene.
scene.add(camera);
camera.add(centerCube, bottomRightCube, rightCube);
// Position them.
centerCube.position.z = DISTANCE_TO_FACE;
bottomRightCube.position.z = DISTANCE_TO_FACE;
rightCube.position.z = DISTANCE_TO_FACE;
function init() {
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', handleWindowResize);
handleWindowResize();
animate();
}
function animate() {
requestAnimationFrame(animate);
centerCube.rotation.y += 0.01;
rightCube.rotation.x += 0.01;
bottomRightCube.rotation.y += 0.01;
// Render the scene
renderer.render(scene, camera);
}
function handleWindowResize() {
alignCenter(centerCube, 0.5);
alignRight(rightCube, 0.5);
alignBottomRight(bottomRightCube, 0.5);
}
window.addEventListener('DOMContentLoaded', init);
body {
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://unpkg.com/three@0.135.0/build/three.min.js"></script>
Final real-world result below. The curved bar and bottom-right text are the 3D objects being aligned.
