The inverse problem as optimisation
The setup is deceptively simple. You have a target image and a scene whose parameters you want to recover: vertex positions, material roughness, albedo, light intensity.
Define a loss comparing your render to the target, then minimise it by gradient descent:
for step in range(n_steps):
image = render(scene, params)
loss = ((image - target) ** 2).mean()
grad = d_loss_d_params(loss) # the hard part
params -= lr * grad
Every line is routine except one. Getting requires differentiating the entire light transport simulation: millions of rays, recursive bounces, random sampling.
The obvious approach is automatic differentiation, which handles far more complicated programs than this. And for a large class of parameters it works immediately.
Make a surface rougher, and every pixel it influences changes smoothly. Brighten a light, and the image scales smoothly. Change an albedo, and colours shift smoothly. For material and lighting parameters, naive automatic differentiation of a path tracer gives correct gradients.
Then you try to move a triangle, and it silently fails.

