Octahedron mapping using mipmap atlases


Octahedron with +Z in white
Row cubemap 
Hcross cubemap

Octahedron map
One could easily go with octahedron maps using standard mipmapping. This works in general, but we have seen that accessing mipmaps manually in the shader imposes some performance problems on mobile devices (in our case we want to use cubemaps encoding various levels of a given effect on each mip level, so we want to be able to address that manually).

For the general case (i.e., cube maps), it is well explained how to avoid seams when using bilinear filtering and mipmaps in the work of Tarini et al. (Tarini, M. , Ambient occlusion and edge cueing for enhancing real time molecular visualization. 2006, TVCG). Basically:
  1. Mirror the border pixels of the image from the center of its edges (i.e., mirror Y, then X, or viceversa)
  2. Offset texture addressing by half texel towards the interior of the texture. 
    • With -1..+1 uv coordinates is as easy as:
      • u *= (TEX_W - 0.5) / TEX_W
      • v *= (TEX_H - 0.5) / TEX_H
    • With 0..+1 uv coordinates:
      • u = u * (TEX_W - 0.5) / TEX_W + 0.5 / TEX_W
      • v = v * (TEX_H - 0.5) / TEX_H + 0.5 / TEX_H

    • Shader selected mipmaps using a mipmap atlas
Initially, this consists mostly of mapping the texture accesses to the correct region, but there is color bleeding when sampling on the boundaries.


      • Borders to avoid mipmap bleeding (specially in Y axis)
One can simply replicate the boundaries for one pixel to solve the color bleeding problem.



  • Stretch to fill empty space and exploit horizontal clamping (no bleeding on X axis)
On a following step, stretching the texture to fill the width solves problems in the horizontal axis, since relies on standard wrap modes (clamp, ideally). But we still have the problem in the vertical axis. First level would only need the bottom border.



  • POT & SQUARE to enable PVR compression
    • consecutively storing mips with border space in between
To enable GPU compression formats typically one needs POT and Square textures. For that we need to select the correct height for the first level so as to make it compliant with the dimension constraints.



    • aligning mipmaps to POT mipmap origins (although there are borders, will be taken into account later)
It is much easier to start from standard POT 


    • aligning mipmaps to POT mipmap origins, and applying negative offset in order to exploit empty space before, so as to allow room for higher mip levels at bottom
//Example with square mipmap atlas of 512x512
MIP0_HEIGHT_POT = POT height (e.g., 512/2 = 256)
MIP0_HEIGHT = atlas mipmap height (e.g., 240 = 256 -2 * BORDER_SIZE * log2(MIP0_HEIGHT_POT)-1)
INV_HEIGHT = 1 / ATLAS_HEIGHT

num_mipmaps = log2(MIP0_HEIGHT) //240 
border_size = 1
border_pixels = border_size * 2 * (num_mipmaps + 1)

current_mipmap_y0 = int(mip_level>0) * MIP0_HEIGHT_POT / (2^mip_level+1)
//apply negative offset to exploit space
current_mipmap_y0 -= int(mip_level > 0) * (MIP0_HEIGHT_POT - MIP0_HEIGHT - 2*border_size - mip_level)

///////////
mip_factor = 1 / pow(2, mip_level)
//Mip Y range corresponds to image height ratio against texture atlas
mip_range_y = TRUNC(mip_factor * MIP0_HEIGHT) * INV_HEIGHT;
//Mip Y0 corresponds (initially) to the POT mipmap
mip_offset = 2.0 * (1.0 - mip_factor) * MIP0_HEIGHT_POT * INV_HEIGHT
               + BORDER * INV_HEIGHT;
//negative offset to exploit empty space between POT mipmap slots
mip_offset -= float(mip_level > 0) * INV_HEIGHT * ((MIP0_HEIGHT_POT -  MIP0_HEIGHT - 2) - mip_level);


#octahedron_maps