Rendering French Bread

Alexis Haraux

Final rendering, bread modeled by a 256x256x128 volumetric texture for the interior (corresponding to 4 million triangles after applying marching cubes) and 632k triangles for the crust. It took 68 minutes to render.



In France, bread is the main food for every meal. It has a very complex structure made of gas bubbles that were displaced and deformed during the baking process. These bubbles strew a translucent matrix which is also responsible for the effect of softness. Crust is also interesting, it has relief and patterns because of the expansion of bread dough during baking. More generally, food is really challenging to render, as often considered as another "uncanny valley", which makes sense since through animal evolution, species had to be able to recognize precisely their peers, and edible food. In two words, food is hard to render because it has to look appetizing.

Only a few work has been done on this topic, and the most important step has been made by Pixar in the computer animated film Ratatouille (2007). A paper [1] has been written about the techniques used and my approach has been based on the Athena Xenakis's chapter about shading (it only contains a few words about bread, without any detail about the methods used).

Modeling the Bread Interior

To model the bread interior, I generated a volumetric texture with a program I wrote in C++.

To get a convincing result, I had to simulate the behavior of bubbles in bread during baking. Gas bubbles are formed when bread leavens, and they expand with high temperature in the oven. First, bubbles which are farthest from the center of the bread expand (because only the surface of the bread is at a high temperature), as the surface gets hard. Then, the center of the bread gets hot and bubbles expand, compressing those which are near the crust.

I first tried to simulate this process by adding bubbles (empty perfect spheres with random radius) in my volumetric data, and then adding a non-linear planar deformation on each voxel.

Clean cut of bread

Texture without displacement

Texture after displacement

This naive method has two issues:

  • You cannot control the density of bread, the volumetric data is boolean, so when bubbles start to intersect, there is almost no material left.
  • Since the distribution of bubbles is uniform before the deformation, the few bubbles at the center become huge and it does not look realistic.

To solve that problem, I first decided to switch from spheres to Voronoi Noise with first nearest neighbor and Euclidean squared distance. Then I used a Gaussian distribution for the Voronoi seeds, so that more bubbles are added in the center, and give a more uniform result after displacement.

Uniform Voronoi Noise

With Voronoi noise, my volumetric data at a voxel corresponds to the squared distance between this voxel and the nearest bubble center. To apply the displacement, for each voxel I look the value at the coordinate obtained after applying the inverse displacement. With this method, voxels near the center of the bread had really small density values before all the points are near each other before displacement (because of the gaussian distribution). I had to scale this data by multiplying it by the distance between the voxel and the crust.

I extended pbrt to load this volumetric data and use Marching Cubes [2] (I used an implementation found on the website) to convert the density grid into a triangle mesh. For this purpose I wrote a new Shape class, VolumetricGrid. A different isovalue can be used for the marching cubes, depending on how big bubbles need to be.
To get the distance between an arbitrary point in the volume and the crust, I loaded an obj file of the crust model (described in the next section) and used it as a point cloud. The distance is then the distance between the point and the nearest point in the cloud. This nearest point finding was accelerated by a kd-tree.

I also used this point cloud to determine if a voxel is inside the final volume: if x is an arbitrary point, c the nearest crust point, and n the normal of the surface at this point, then x is inside the crust if dot(c-x,n) > 0.
To simulate the teared surface of the interior, I used Perlin Noise to generate a random height map and used it to "eat" the bread. I also used Perlin Noise to generate a displacement map and perturb small volumetric spheres to make crumbs.

Modeling the Crust

I modeled the crust as a triangle mesh with Blender. I first modeled a really simple mesh by extruding a NURBS curve, and applied a displacement map with Catmull-Clark subdivision to get a more complex geometry.

Simple crust model and the NURBS curve used

To model the complex structure of the crust, I took pictures of a baguette to get a diffuse texture and a normal map. I used the Shape from Shading method to get the normals of the surface of the crust: I took pictures with polarizing filters (on the camera and on the light, to get rid of specular highlights which saturate intensity values) with 6 different light directions, and derived the normals with a Least Squares method on Matlab.

I extended pbrt to load and use this RGB normal map to perturb the shading geometry of my crust mesh (pbrt only handles bump mapping).

Shape from Shading experiment

Pictures used for Shape from Shading and resulting Normal Map

Top: without polarizer filters, Bottom: with polarizer filters

I then used Photoshop to stitch pictures from different sides of the bread and computed UV coordinates with Blender's UV unwrapping.

Textures used for the crust model (diffuse, displacement map, normal map)

Here is a rendering of the crust after applying these textures:



For the rendering, I tried several methods:

  • Volumetric rendering: before I integrated marching cubes into pbrt, I used pbrt implementation of volume rendering to be able to visualize quickly the data I was generating for the bread interior. I rendered my bread as a very dense smoke, and it allowed me to debug easily my volumetric texture since the rendering is fast. However, bread interior has to be considered as a surface, even if it is transparent, and volume rendering can not handle that. The interpolation made at each point in a voxel gives a very puffy, cloudy look.

Volume rendering (two first images) and triangle mesh generated with Marching Cubes

  • Subsurface scattering: To model the translucency of the bread interior, I tried to use subsurface scattering. The dipole approximation gives really bad results, because it assumes the material is semi infinite (infinite thickness) whereas bread has a complex structure with high frequency detail, with very thin layers of transparent material. This approximation does not simulate light transmission through the material. I then implemented the multipole approximation from Donner and Jensen [3] which models subsurface scattering for thin material but still with infinite extension in the other directions. I did not get any convincing result with these methods, and thus decided to used simple brute force.

  • Path tracing: I ended up using pbrt's implementation of path tracing with the Translucent material class. When a ray hits a surface, the direction of the transmitted ray is sampled over the whole hemisphere. Pbrt uses the BRDFtoBTDF function, to reverse the hemisphere given by Lambertian Reflection. I tried to change this phase function to speed up the rendering and get less noise, but less light was collected and the result did not look good.

Intermediate Results and Final Image

breadtemp1.jpg breadtemp2.jpg


  • [1] Jun Han Cho, Athena Xenakis, Stefan Gronsky, Apurva Shah: "Anyone Can Cook – Inside Ratatouille’s Kitchen". Siggraph 2007 Course 30
  • [2] Paul Bourke: "Polygonising A Scalar Field" (http://paulbourke.net/geometry/polygonise/)

  • [3] Craig Donner, Henrik Wann Jensen, "Light diffusion in multi-layered translucent materials", presented at ACM Trans. Graph., 2005