(Using Blender 3.6.1 and Geometry Nodes)
Objective:
Mimic 3D porous Perovskite materials like this:
Documentation:
Starting from this bibliography entry point, it seems that the structure is made of hollow spheres staggered in a lattice of planes with square or hexahedral tiling. Spheres diameter is slightly larger than spheres center to center distance (labeled $a$ thereafter). About Nhiem Pham illustration, assuming that the (Right (X axis), Front (Y axis), Top (Z axis)) corner is facing the observer, (X, Y) tiles are squares and (Y, Z) tiles are hexahedrons. In hexagonal tiling, adjacent rows of sphere centers are shifted by $\pm \frac{1}{2}a$ in the direction tangent to rows and by $\frac{\sqrt{3}}{2}a$ in the direction perpendicular to rows. Even rows are thus shifted only in the perpendicular direction by $\sqrt{3}a$; same for odd rows.
Approach:
Use spheres to carve holes in a cube. Steps are:
- build the lattice odd layers as copies of a regular (X, Y) grid shifted along Z axis;
- build the lattice even layers as copy of the odd layers, shifted in Y and Z directions;
- place spheres on lattice vertices;
- remove the spheres volume by Boolean difference from the cube.

Parameters:
To setup the configuration, inputs of the Geometry Nodes graph are:
- Distance ($a$): spheres center to center distance
- Count ($N$): Number of spheres per direction (minimum 2, to make a 2x2x2 lattice)
- Shift: Offset of the lattice reference corner relative to the cube reference corner
- Scale: Sphere diameter divided by $a$ (1.0 yields tangent spheres)
- Subdivision: refinement level of sphere skin
- Noise ($\epsilon$): Randomization of spheres diameter and position (0.0 yields perfect stacking and uniform diameter)
- Preview: ON: Display spheres only (fast); OFF: Compute Boolean difference (slow)
Procedure:
Here is an overview of the Geometry Nodes graph, with 4 major steps after the initialization in Object Mode:

1. Initializing in Object Mode:
1.1. Add a cube and select Shade Flat.
1.2. Set its origin to the (Right, Front, Top) corner, facing the observer (NB: All coordinates afterwards are relative to this
origin).
1.3. Add a GeometryNode modifier.
2. Building the lattice:
2.1. Build the template:
2.1.1. Add a Grid builder node.
2.1.2. Set both sizes to $(N-1)a$ (minimal tile is a single square with $N=2$ and edge length equal to $a$).
2.1.3. Set both numbers of vertices to $N$.
2.2. Build the set of odd layers:
2.2.1. Compute the number of copies as $(N-1)/2+1$ (2 layers yield 1 copy; 3 and 4 layers yield 2 copies; 5 and 6 layers yield 3 copies).
2.2.2. Compute the unitary offset between odd layers as $(X, Y, Z)=(0, 0, \sqrt{3})a$.
2.2.3. Add a Duplicate Elements node to make copies of the template (NB1: the template is not included in the output geometry; NB2: duplicate ONLY the Points).
2.2.4. Add a Set Position node to shift each copy, using the Duplicate index output (starting at 0) to scale the unitary offset.
2.3. Build the set of even layers:
2.3.1. Compute the offset between adjacent layers as $(X, Y, Z)=(0, \frac{1}{2}, \frac{\sqrt{3}}{2})a$.
2.3.2. Make a single copy of the set of odd layers.
2.3.3. Shift the set of even layers.
2.4. Finalize:
2.4.1. Join both sets in a single geometry.
3. Positioning the lattice:
3.1. Change the lattice origin:
3.1.1. Add a Bounding Box node to get the lattice corners coordinates.
3.1.2. Shift the Min Y value by $\frac{1}{2}a$ because the top layer is always even.
3.1.3. Assemble the position of the (Right, Front, Top) corner as (Max X, shifted Min Y, Max Z).
3.1.4. Reverse the sign of this corner position to bring it at the cube origin after that offset is added.
3.2. Add the shift from the inputs.
3.3. Randomize vertex positions:
3.3.1. Draw a random vector with components between $-a$ and $a$ for every vertex.
3.3.2. Scale it by $\epsilon$.
3.3.3. Add it to the whole lattice offset.
3.4. Add a Set Position node to shift every lattice vertex.
4. Building and positioning the spheres:
4.1. Build the sphere:
4.1.1. Compute its radius from half $a$ and the inputs scaling factor.
4.1.2. Follow with a Icosphere builder node. Subdivisions number is controlled from the inputs.
4.1.3. Objects built inside a GeometryNode modifier have no material. To singularize faces generated by Boolean difference during the inputs tuning, a Switch node is added to control a Set Material node configured with a predefined material. This switch must be unchecked for final rendering.
4.1.4. Add a Set Shade Smooth node, controlled by the inputs, to shade smoothly the faces generated by Boolean difference. Activating this behaviour for the cube has no effect inside the GeometryNode modifier.
4.2. Randomize spheres radius:
4.2.1. Draw a random number between $1-\epsilon$ and $1+\epsilon$ to scale every sphere.
4.3. Add an Instance on Points node to place a sphere at every vertex of the lattice.
5. Cutting the spheres out of the cube:
Final geometry is controlled by a Switch node to save time during inputs tuning.
5.1. With Preview mode ON, the output geometry is the superposition of all the spheres on the unmodified cube.
5.2. With Preview mode OFF, the output geometry is returned by a Mesh Boolean node removing the spheres volume from the cube volume.
6. Shading:
6.1. Controlling purple spots: see How do I add random spotting to the same face in cycles?
6.2. Controlling rugosity: see How do I make a rough plastic surface?
Resources:
