I am creating a 3D-graphics renderer in Java using only awt, and I have run into a problem with my fov. When the fov is set to something reasonable like 30degrees all the objects within the scene skew drastically towards the top-left corner of the screen.
public class Camera {
private float aspectRatio = 1.33f;
private float fov = (float) Math.toRadians(30);
private float near = 0.01f;
private float far = 1000;
private float moveSpeed = 0.02f;
private Vector3D cameraPos = new Vector3D(0, 0, 0);
private Vector3D cameraFront = new Vector3D(0, 0, 1);
private Vector3D cameraUp = new Vector3D(0, 1, 0);
private float yaw = 1.58f;
private float pitch = 0;
public Matrix4x4 perspectiveViewMatrix() {
var vM = viewMatrix(cameraPos, cameraPos.add(cameraFront), cameraUp);
return perspectiveMatrix().multiply(vM);
}
private Matrix4x4 perspectiveMatrix() {
return new Matrix4x4(new float[][] {
{ (float) (1 / (aspectRatio * Math.tan(fov / 2))), 0, 0, 0 },
{ 0, (float) (1 / (Math.tan(fov / 2))), 0, 0 },
{ 0, 0, far / (far - near), -(far * near) / (far - near) },
{ 0, 0, 1, 0 } });
}
private Matrix4x4 viewMatrix(Vector3D cameraPos, Vector3D cameraTarget, Vector3D up) {
Vector3D cDirection = cameraPos.subtract(cameraTarget).normalize();
Vector3D cRight = up.cross(cDirection).normalize();
Vector3D cUp = cDirection.cross(cRight);
return new Matrix4x4(new float[][] {
{ cRight.getX(), cRight.getY(), cRight.getZ(), 0 },
{ cUp.getX(), cUp.getY(), cUp.getZ(), 0 },
{ cDirection.getX(), cDirection.getY(), cDirection.getZ(), 0 },
{ 0, 0, 0, 1 } })
.multiply(Matrix4x4.translation(cameraPos));
}
public void moveCamera(InputHandler input, List<Object3D> objects) {
float sense = 0.01f;
for (Direction key : input.getKeys()) {
switch (key) {
// move y-axis
case UP -> moveYaxis(-moveSpeed);
case DOWN -> moveYaxis(moveSpeed);
// move z-axis
case FORWARD -> moveZaxis(-moveSpeed);
case BACKWARD -> moveZaxis(moveSpeed);
// move x-axis
case LEFT -> moveXaxis(-moveSpeed);
case RIGHT -> moveXaxis(moveSpeed);
// rotate-y
case C_LEFT -> changeYaw(sense);
case C_RIGHT -> changeYaw(-sense);
// rotate-x
case C_UP -> changePitch(sense);
case C_DOWN -> changePitch(-sense);
}
}
}
private void moveYaxis(float i) {
cameraPos.addtoY(i);
}
private void moveZaxis(float i) {
cameraPos.addTo(cameraFront.scale(i));
}
private void moveXaxis(float i) {
cameraPos.addTo(cameraFront.cross(cameraUp).normalize().scale(i));
}
private void updateCamFront() {
float dirX = (float) (Math.cos(yaw) * Math.cos(pitch));
float dirY = (float) Math.sin(pitch);
float dirZ = (float) (Math.sin(yaw) * Math.cos(pitch));
Vector3D direction = new Vector3D(dirX, dirY, dirZ);
cameraFront = direction.normalize();
}
private void changeYaw(float sense) {
yaw += sense;
updateCamFront();
}
private void changePitch(float sense) {
pitch += sense;
if (pitch > 89) {
pitch = 89;
}
if (pitch < -89) {
pitch = -89;
}
updateCamFront();
}
}
public class Renderer extends JPanel{
private static final long serialVersionUID = -4576361545526054095L;
private transient Camera camera;
private transient Scene scene;
Renderer(Camera c, Scene s){
setBackground(Color.gray);
camera = c;
scene = s;
}
public void render() {
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Enable anti-aliasing for smoother rendering
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Set the fill color to red
g2d.setColor(Color.yellow);
Matrix4x4 pvm = camera.perspectiveViewMatrix();
for (Object3D obj : scene.getObjects()) {
Mesh mesh = obj.getMesh();
List<Vector3D> vertices = mesh.getVertices();
Matrix4x4 mat = pvm.multiply(obj.getModelMatrix());
for (Triangle tri : mesh.getTriangles()) {
Vector4D result1 = mat.multiply(vertices.get(tri.v1).homogeneous());
Vector4D result2 = mat.multiply(vertices.get(tri.v2).homogeneous());
Vector4D result3 = mat.multiply(vertices.get(tri.v3).homogeneous());
double pt1x = result1.getX() / result1.getW();
double pt1y = result1.getY() / result1.getW();
double pt2x = result2.getX() / result2.getW();
double pt2y = result2.getY() / result2.getW();
double pt3x = result3.getX() / result3.getW();
double pt3y = result3.getY() / result3.getW();
// Create a GeneralPath for the current triangle
GeneralPath path = new GeneralPath();
path.moveTo(pt1x, pt1y);
path.lineTo(pt2x, pt2y);
path.lineTo(pt3x, pt3y);
path.closePath();
// Fill the triangle
g2d.fill(path);
}
}
}
}
I tried smaller values and 0.001radians seem to be the closest to what I want; but that value seems way to small to have set up the camera right.