kubi is a fast and flexible cubemap generator based on libvips
pip install git+https://github.com/indus/kubi.git
kubi can convert equirectangular images into a variety of common layouts. All image formats supported by vips (JPEG, PNG, TIFF, WEBP, HEIF, ...) can be used for input and output. With the DZ format of libvips it is even possible to create tiled images of the cubefaces.
When used with a glob pattern for multiple input files, kubi generates an index file once and reuses it while processing all matched images. This can lead to a significant speedup.
kubi -h
kubi [-s <size>] [-l {row,column,crossL,crossR,crossH}] srcfile [dstfile]
none | row | column |
---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
---|---|---|
crossL | crossR | crossH |
kubi -s 2048 -co tile_size=512 -co depth=onetile -co overlap=0 -co layout=google -co suffix=.jpg[Q=75] -n l r u d f b srcfile dstfile.dz
argument | explanation |
---|---|
-s 2048 |
every cubeface has an overall size of 2048px |
-co tile_size=512 |
every cubeface gets split into tiles with a size of 512px (โ 3 levels) |
-co depth=onetile |
stop tiling with onetile (vips default is one pixel!) |
-co overlap=0 |
tiles should have an overlap of 0px (vips default is one pixel!) |
-co layout=google |
use the google folder/file layout (options are dz , zoomify , google , iiif ) |
-co suffix=.jpg[Q=75] |
tiles should be JPEG with a quality of 75 |
-f r l u d f b |
defined suffixes r(ight), l(eft), u(p), ... |
srcfile |
the input file; could be a glob pattern |
dstfile.dz |
the output folder name; use .dz extension for tiles |
With some fiddling the file and folder structure could be made compatiple to 360ยฐ image viewers.
The above would work with Marzipano:
Marzipano.ImageUrlSource.fromString("<some_path>/dstfile_{f}/{z}/{y}/{x}.jpg");
system: CPU: i7-6700 CPU @ 3.40GHz, 4 cores; MEM: 64GB; OS: Win 10
input: equirectangular image with 4096x2048px
output: cubemap with a cross layout
face size | 1024px | 2048px | 4096px |
---|---|---|---|
kubi | 0.9s | 1.6s | 4.7s |
py360convert | 2.6s | 8.6s | 32.2s |
any others ? | - | - | - |
input: equirectangular image with 4096x2048px
output: cubemap as seperate tiles
face size | 1024px | 2048px | 4096px |
---|---|---|---|
kubi | 0.9s | 1.3s | 3.1s |
panorama_windows.exe | 1.5s | 4.7s | 16.9s |
any others ? | - | - | - |
If only a few cubemaps are needed, performance is probably of minor concern. But with kubi it should also be possible to process thousands of animation frames in a reasonable time:
input: multiple equirectangular images with 4096x2048px
output: multiple cubemaps with a cross layout and a face size of 2048px
count | total time | time per cubemap |
---|---|---|
1 | 1.7s | 1.7s |
2 | 2.6s | 1.3s |
3 | 3.5s | 1.2s |
5 | 5.2s | 1.0s |
10 | 9.9s | 1.0s |
20 | 18.5s | 0.9s |
In addition to regular cubemaps, two optimized mappings can be generated:
- Equi-Angular Cubemap (EAC)
C.Brown (2017): Bringing pixels front and center in VR video - Optimized Tangens Cubemap (OTC)
M.Zucker & Y.Higashi (2018): Cube-to-sphere Projections for Procedural Texturing and Beyond (Ch. 3.2 & Ch. 5)
Both transforms are univariate and therefore very easy to implement. They both significantly reduce the distortion of the cubemap and thus optimize the pixel yield. However, support in other tools and libraries is rather scarce.
error | ltr: regular cubemap, EAC, OTC |
---|---|
area | ![]() |
distance | ![]() |
To ensure the correct representation of a transformed cubemap, the reverse transformation must be performed at some point. The following example shows this for the OTC transformation in a fragment shader.
//FRAGMENT SHADER for OTC
vec3 n = vNormal.xyz; // or vPosition
n /= max(max(abs(n.x),abs(n.y)),abs(n.z));
n = atan(n * tan(0.8687));
gl_FragColor = textureCube(otcSampler, n);