For a good and scalable project structure, what fields would you add to a camera class, apart from its position and orientation? Should I add the near and far planes? Or should that be global, what about the FOV? And worse, what about the yaw, pitch and roll variables? (which I think might be global but I'm not 100% sure).
-
$\begingroup$ I would have the camera contain its own matrix (4th column being the position and the 3x3 part being the basis vectors). I would not keep around pitch/yaw/roll in order to avoid gimbal lock, you can have the incremental rotations be built in euler angles though. Also it should contain something like a projection which has a flag for its type (e.g. perspective, orthographic, other), aspect ratio, hfov/scale, near/far - the projection matrix should be built from those. $\endgroup$– lightxbulbCommented Apr 24, 2022 at 9:12
1 Answer
This is one of those "ask 10 different programmers and get 10 different answers" types of questions. Also the "right" answer is going to depend heavily on the requirements of the program.
Having said that, good programming practices server this type of problem well. Based on that I would recommend keeping the Camera info which ultimately produces the View matrix, and the projection info which is used to generate a projection matrix decoupled.
For projection info something as simple as a POD holding near, far, aspect, and fov. This info can be passed to the camera as needed, and if it is truly necessary the camera can "have a" projection info inside it, or at least refer to one. It also provides a simple way to pass all the info needed to a "GenerateProjection" function (or similar).
For a camera class, functions that provide movement/rotation and View matrix generation typically dictate the private data and this depends on how the camera is coded. For example a camera that uses polar coordinates will be very different from one that uses quaternions. It is also handy to have functions that allow access to both the CameraToWorld matrix and the WorldToCamera aka View matrix since both are often needed for other calculations. Fields that hold constant data can be useful, for switching the camera between Z up or Y up worlds for example. These can be simple compile time code (such as using "if constexpr (IsZup) {...}") Keeping the view matrix in the class can be handy but when both the CameraToWorld matrix and WorldToCamera matrix are needed every frame, just having a function that computes, and returns them can be more useful. Both then end up sitting a per frame data structure. This allows multiple frames to be floating around in the system without any one frame being tied directly to the camera class. Again, this is a requirements thing.
A "lookAt" function can be handy as a way to generate alternate camera views for things like lighting and can be used to generate a matrix for a spot light. Functions like this tend to creep into camera code because they are so similar, but also can lead to extraneous fields that the class doesn't really need. (finding ways to share code and data between competing algorithms can be challenging)