My answers to the Nintendo European Research & Development technical questionnaire (GAMESCIENCE2026 / GAMERENDERING2026). Each answer is paired with a real-time demo. Building these helped me develop a deeper understanding of the underlying concepts beyond what reading alone would provide.
01 // Aliasing
My first encounter with aliasing was in video games, toggling anti-aliasing in graphics settings and seeing the before/after preview, much like dragging the split handle in the demo above. At the time I understood it intuitively as smoothing out the jagged staircase appearance on diagonal lines and curved edges. I then realized this was not specific to games at all: the same jaggedness is visible on a mouse cursor, in Microsoft Paint, anywhere discrete pixels try to approximate a continuous shape. The pixel grid is simply a spatial sampler, and any detail finer than a pixel cannot be faithfully represented.
The deeper understanding came later in my Computer Vision master's, where aliasing is formalized through the Nyquist-Shannon theorem: when sampling a continuous signal, you must sample at least twice the highest frequency present. If you do not, high-frequency content folds back and appears as a false lower-frequency artifact. In graphics this manifests spatially (jagged edges), on textures (moire patterns when texture detail is finer than a pixel), and temporally (flickering on fast-moving objects between frames). Try lowering the resolution in the demo and increasing the frequency to see these artifacts appear clearly.
Common mitigations include anti-aliasing techniques (MSAA, TAA) and mipmapping, which pre-filters texture frequencies before sampling.
02 // Triangle Meshes & Representations
Triangles are the universal rendering primitive because they are always planar and convex. Three points define a unique plane, making interpolation of attributes like UVs and normals unambiguous. GPUs are specifically designed to rasterize triangles efficiently, so everything ultimately gets triangulated at render time. Toggle between the modes in the demo above to see the same 3D head represented as triangles, wireframe edges, and a point cloud (the vertex and face counts in the corner show the underlying data).
In 3D modeling software however, artists typically work with quads, because they support edge loops, cleaner topology and modifiers, making geometry easier to manipulate and rig. These are triangulated only at the rendering stage.
Other representations exist, and the research community has not converged on a single one. Each has its own strengths and use cases. Point clouds represent geometry as a dense set of colored 3D points, commonly produced by LiDAR or photogrammetry pipelines. NeRFs then emerged as a breakthrough: they represent a scene as an implicit neural function mapping positions and ray directions to color and density, enabling photorealistic novel view synthesis, though rendering is slow as it requires sampling along many rays. More recently, 3D Gaussian Splatting represents the scene as a collection of 3D Gaussians (ellipsoids), each with position, scale, rotation, opacity and color encoded via spherical harmonics. It is conceptually close to a point cloud, but each point is inflated into a soft volumetric blob.
Note: these representations are very dependent on the quality of the acquisition process.
03 // Z-Buffer
Z-fighting was something I encountered frequently in Blender when working on large-scale scenes like landscapes or cities: two surfaces at similar depths flickering and competing for the same pixels. The usual fix was adjusting the clip start value, pushing the near plane further from the camera to spread the depth range more usefully. The demo above reproduces this exact situation. Try adjusting the near plane and gap sliders to see z-fighting appear and disappear across the five distance columns.
The Z-buffer stores a depth value per pixel to resolve occlusion between fragments. By default, GPUs store non-linear projected depth, a consequence of perspective division that concentrates most precision near the camera and leaves very little for distant geometry. This is hardware-native and efficient, but it is exactly why z-fighting appears on far or coplanar surfaces. Toggle the depth view in the demo to visualize how much of the available range is consumed at each distance.
A common modern solution is reversed-Z: mapping the far plane to 0 and the near plane to 1. This exploits how floating-point numbers distribute their precision (denser near zero), effectively giving much more depth resolution to distant geometry where it is needed most. Unreal Engine uses this by default.
Linear depth stores the actual camera-space distance, giving uniform precision across the range and making it convenient for post-processing effects like SSAO or fog. It is not hardware-native though, so it is typically reconstructed in a post-processing pass rather than stored directly. Logarithmic depth goes further, maintaining near-uniform precision across enormous ranges, useful for space or open-world scenes, at the cost of per-fragment computation.
For format, 24-bit (D24S8, paired with a stencil buffer) is the standard. 32-bit float is preferred for deferred pipelines or very large scenes. 16-bit saves memory on mobile but brings z-fighting back. Use the bit-depth toggle in the demo to see the difference firsthand.
04 // Mip Selection
Mipmapping is one of those techniques that is so well implemented that most people, including myself, never consciously notice it. Textures just look right at every distance. It was only when I started digging into this question that I understood the internal logic. The demo above makes this visible by color-coding each mip level, so you can see exactly which level the GPU selects at each point on the receding ground plane.
For each pixel, the GPU estimates how much the UV coordinates change across neighboring pixels (the screen-space derivatives dU/dx, dU/dy, dV/dx, dV/dy). These measure how compressed or stretched the texture appears on screen at that pixel. From the largest of these the GPU computes a value called λ (lambda), roughly the log ² of that rate of change. A λ = 0 means the texture maps 1:1 to screen pixels, so mip 0 (full resolution) is used. A λ = 2 means the texture is minified by a factor of 4, so mip level 2 is selected instead.
Several filtering modes determine how that selection is made. Switch between them in the demo to compare. Nearest snaps to the closest integer mip level, cheap but causing visible hard transitions between levels. Bilinear smooths sampling within every single mip level. Trilinear goes further and interpolates between two adjacent mip levels, eliminating the hard transitions at the cost of some blurring. Anisotropic filtering addresses the remaining weakness at oblique angles: the texture footprint is elongated in one direction. Isotropic methods over-blur because they treat the footprint as a circle and pick a mip sized for the largest dimension. Anisotropic filtering detects this elongation and takes multiple samples along the stretched axis, allowing a sharper mip to be used without aliasing. It is the preferred mode in real-time engines.
05 // 3D Rotations
When building the synthetic data acquisition pipeline at GoPro, I needed to apply random orientations to the fisheye camera rigs in Blender programmatically via the Python API. I used quaternions directly rather than Euler angles, because randomizing three Euler angles independently does not produce a uniform distribution of orientations and introduces order dependency. The demo above shows why this matters: drag the pitch slider toward 90 degrees and watch two of the three Euler axes collapse into one, the classic gimbal lock problem.
Euler angles (roll, pitch, yaw) are the most intuitive and compact (3 floats) and easy to expose in editor UIs, which is why Blender and Unity show them in their inspectors. However they suffer from gimbal lock when two axes align, are order-dependent (XYZ vs ZYX gives different results, hence the rotation order dropdown in Blender), and interpolate poorly.
Rotation matrices (3×3) are what the GPU ultimately uses and compose naturally via multiplication with no gimbal lock. The middle column in the demo shows the three basis vectors updating in real time. But they are redundant (9 floats for 3 degrees of freedom), accumulate numerical drift over repeated multiplications, and do not interpolate cleanly with naive lerp.
Axis-angle stores a rotation as a unit axis and a scalar angle, which is geometrically intuitive, but awkward to compose or interpolate directly.
Quaternions are the standard choice in game engines and the one I reached for in practice. They encode axis-angle compactly in 4 floats, support smooth interpolation via SLERP, compose efficiently via quaternion multiplication, and are trivial to renormalize. The main caveat is the double-cover property: q and -q represent the same rotation, which can cause interpolation to take the long way around if not handled carefully. The right column in the demo displays the live quaternion values (w, x, y, z) as you manipulate the sliders.
In practice the three representations coexist: quaternions for internal computation and interpolation, Euler angles exposed to artists in editors, and rotation matrices passed to the GPU.
06 // Color Representation
The most fundamental representation is RGB, directly matching how screens emit light. It is hardware-native and simple, but not perceptually uniform and not intuitive for artists to manipulate. The demo above lets you pick a color in all three spaces (RGB, HSV, and OKLab) and see it converted in real time, making the tradeoffs between each representation tangible.
A critical distinction in rendering is between sRGB and linear RGB. sRGB applies a gamma curve to allocate more precision to dark tones, matching human perception. The gradient comparison in the demo shows how the same ramp looks in sRGB versus linear space. However all lighting and shading computations must happen in linear space. Failing to make this distinction is a classic source of incorrect rendering results.
HSV and HSL separate color into hue, saturation and brightness, much more intuitive for artists and color pickers, though not suitable for lighting calculations.
For physically based rendering, HDR formats (RGBA16F, RGBA32F) are essential. They store values beyond the [0, 1] range to represent real light intensities across a wide dynamic range, enabling tone mapping before final output.
YCbCr separates luminance from chrominance, exploiting the fact that our eyes are more sensitive to brightness than color. This allows chroma subsampling (4:2:0) for efficient video compression, relevant in camera pipelines like the one I worked with at GoPro.
Perceptually uniform spaces like Lab or OKLab ensure that equal numerical distances correspond to equal perceived differences, making them ideal for color interpolation, procedural palette generation, or measuring perceptual similarity between two images. Notice in the OKLab picker how moving equal distances in any direction produces roughly equal perceived color shifts, unlike the RGB picker where the same movement can produce dramatically different perceptual changes.
This all became very concrete during my GoPro pipeline work, where I had to match synthetic Blender renders to real fisheye camera footage. A render and a real camera frame of the same scene can look completely different in brightness and color response, and if I fed mismatched data to the model it would learn the wrong thing.
07 // Collision Detection
To accelerate collision detection, the standard approach is a two-phase pipeline: a cheap broad phase to eliminate impossible pairs, followed by a precise narrow phase on remaining candidates. Click anywhere in the demo above to run a BVH query and watch the traversal in real time. The counter shows how many tests the BVH skips compared to brute force.
Several data structures exist for the broad phase. A uniform spatial hash grid is simple and fast for evenly distributed objects of similar size, but struggles with size variation. Octrees and BSP trees subdivide space recursively and are efficient for static geometry, but costly to update dynamically. Sort and Prune (SAP) projects bounding volumes onto axes and sweeps for overlapping intervals, which is efficient for slow-moving objects.
My choice would be a BVH (Bounding Volume Hierarchy), as demonstrated above. It is a tree where each node contains a bounding volume (typically an AABB) wrapping all its children. During a query, if a node's bounding volume does not intersect, the entire subtree is skipped. You can see pruned branches drawn as dashed outlines in the demo. This reduces average complexity from O(N²) to roughly O(N log N).
BVHs are built using a splitting heuristic. A common one is the SAH (Surface Area Heuristic), which chooses splits that minimize expected traversal cost weighted by the surface area of child nodes. For dynamic scenes, full rebuilds are expensive. A common optimization is refitting: updating bounding volumes bottom-up without changing the tree structure, which is fast but degrades quality if objects move significantly, requiring periodic full rebuilds.
BVHs are also the structure behind GPU hardware ray tracing (DXR, Vulkan RT), making them the dominant choice in modern real-time engines for both collision and ray intersection queries.
08 // Integration Schemes
Integration schemes determine how a simulation advances its state (positions, velocities) over time. The demo above runs four methods side by side on the same undamped spring system, making the stability differences immediately visible: watch Forward Euler's amplitude grow, Backward Euler's shrink, while Symplectic Euler and RK4 stay bounded.
Explicit schemes, like Forward Euler, compute the next state using only current information. They are simple and cheap per step, but conditionally stable: if the timestep dt is too large relative to the stiffness of the system, errors compound and the simulation diverges. Increase the dt slider in the demo to see Forward Euler blow up while the others remain stable. For stiff systems like cloth or springs with high stiffness, this forces very small timesteps, which can make explicit integration impractical.
Implicit schemes, like Backward Euler, evaluate forces at the next (unknown) state. This requires solving a system of equations at each step, typically a sparse linear system, making each step more expensive and complex to implement. The key advantage is unconditional stability: the simulation remains bounded regardless of dt. The tradeoff is numerical damping, and the phase space plot in the demo shows Backward Euler's trajectory spiraling inward as energy is artificially lost.
A useful middle ground is symplectic (semi-implicit) Euler: update velocity explicitly, then use the new velocity to update position. This is nearly as cheap as explicit integration but conserves energy much better, making it widely used in game engine rigid body solvers. Its phase space trajectory in the demo stays close to the reference ellipse.
Higher order methods like Runge-Kutta 4 (RK4) improve accuracy by evaluating derivatives at multiple intermediate points per step, at the cost of more force evaluations per step. They are useful when accuracy is critical and stiffness is moderate.
In practice, game engines often mix schemes: symplectic Euler for rigid bodies, implicit solvers for cloth and soft bodies where stiffness demands it.
09 // Preconditioning
Many optimization and simulation problems reduce to solving a large sparse linear system Ax = b. Iterative solvers like Conjugate Gradient are typically used, but their convergence speed depends heavily on the condition number of A (the ratio of its largest to smallest eigenvalue). The demo above illustrates this directly: the left panel shows gradient descent on a poorly conditioned system (elongated elliptical contours), while the right panel shows the same problem after preconditioning (circular contours, condition number near 1).
A high condition number means the solver converges slowly, requiring many iterations, and the system is numerically sensitive. Watch the step counters in the demo: the preconditioned solver reaches the minimum in far fewer steps, and its path is nearly straight rather than zigzagging.
Preconditioning addresses this by transforming the system into an equivalent one with a lower condition number. A preconditioner P is introduced so that instead of solving Ax = b directly, the solver operates on P⁻¹Ax = P⁻¹b. The ideal P makes P⁻¹A close to the identity matrix (eigenvalues clustered near 1), while remaining cheap to invert.
Common choices include the Jacobi preconditioner (just the diagonal of A), which is trivial to apply but gives modest gains. Incomplete LU or incomplete Cholesky factorizations give much stronger improvements by approximating the full factorization while preserving sparsity. Multigrid methods are particularly powerful for geometry-based problems by solving at multiple resolution levels.
In physics simulation this is directly relevant: cloth, soft body or finite element solvers involve exactly these large sparse systems, and a good preconditioner can reduce the number of solver iterations from thousands to tens, which is the difference between an interactive and an offline simulation. Increase the condition number slider in the demo to see the raw solver struggle more while the preconditioned solver remains efficient.
10 // Lagrangian Forces
The Lagrangian formalism describes a mechanical system through its kinetic and potential energy (L = T - V) rather than explicitly tracking forces. The equations of motion are then derived from the Euler-Lagrange equations. The demo above shows a double pendulum, a system that is notoriously difficult to analyze with Newtonian force diagrams but falls naturally out of the Lagrangian approach using just two generalized coordinates (the two angles).
The forces that fit naturally into this framework are conservative forces: forces that can be derived from a scalar potential energy function V(q) as F = -dV/dq. Examples include gravity, spring forces and elastic deformation. Instead of specifying force vectors, you only need to define a scalar potential, and the framework produces the correct equations of motion automatically. The left panel in the demo shows this: a conservative double pendulum where total energy (kinetic + potential) remains constant over time.
Constraint forces are also elegantly handled. In Newtonian mechanics, constraints like joints or rigid attachments introduce reaction forces that must be solved for explicitly. In the Lagrangian formalism, generalized coordinates can be chosen to satisfy constraints implicitly, making those forces vanish from the equations. Alternatively, Lagrange multipliers can enforce constraints directly.
Non-conservative forces like friction, damping and drag do not fit naturally, as they cannot be derived from a potential. They require explicit treatment as generalized forces or through a Rayleigh dissipation function. The right panel in the demo demonstrates this contrast: with damping enabled, energy dissipates over time and the pendulum gradually comes to rest, showing a force that the pure Lagrangian cannot capture without augmentation.
This is directly relevant to physics simulation: elastic bodies, cloth and articulated rigid body systems are dominated by conservative forces and constraints, which is precisely why Lagrangian-inspired formulations are attractive for game physics solvers.