Photogrammetry Tiler

Tile photogrammetry models from OBJ or COLLADA into 3D Tiles.


Extract the contents of

The main executable is photogrammetry-tiler and can be found under bin.


Windows binaries require Visual C++ Redistributable for Visual Studio 2017. Download and run the installer.

Using the tiler

The following command will tile Model.obj into 3D Tiles and place it in a new directory called Tileset.

bin/photogrammetry-tiler -i Model.obj -o Tileset

We can fuse multiple models together into a tileset as well.

bin/photogrammetry-tiler -i Model1.obj Model2.obj Model3.obj -o Tileset

We can also specify longitude and latitude in degrees and height in meters relative to the WGS84 reference ellipsoid so the tileset will appear in the correct location when streamed in CesiumJS.

bin/photogrammetry-tiler -i Model.obj -o Tileset --longitude=-105.06 --latitude=41.9 --height=12

These coordinates specify where the model’s origin should be on the map. See Positioning the output below for more details.

The photogrammetry tiler can take advantage of multicore systems by concurrently tiling separate spatial areas of the input model(s). By default, a concurrency level is automatically set based on available system resources. This can be configured manually with the --concurrency option.

The example below enables up to four-way concurrency.

bin/photogrammetry-tiler -i Model.obj -o Tileset --concurrency=4

When running inside Docker, it is recommended to set this manually since containerization prevents the tiler from querying system resources.


Tiles can be compressed using lossless gzip compression with the --gzip option. This requires setting the content-encoding to gzip in the HTTP header on the server.

Draco compression for meshes is enabled by default and greatly optimizes streaming speed at minimal precision loss. See our blog post to learn more. Use the --compression-level option to set compression level between 0 (least compression) and 10 (highest compression), as shown below.

bin/photogrammetry-tiler -i Model.obj -o Tileset --compression-level=5

Draco compression can be disabled using --disable-geometry-compression.

Available options

Below is a list of command line options to configure the tiler.

Option Description Required Default
--help, -h Display help message.    
--version, -v Display version number.    
--input, -i The path(s) to the OBJ and/or DAE file(s) to be tiled.  
--output, -o The path to the output directory or .3dtiles database file. Will overwrite the existing directory if it exists, or otherwise create a new directory.  
--quiet Suppress output during tiling.   false
--verbose Show verbose output.   false
--memory, -m A guideline in megabytes for how much memory may be used for tiling. When running inside Docker, it’s recommended to set this manually since containerization prevents the tiler from querying system resources.   System available.
--concurrency, -x Maximum number of processors to use for concurrent tile processing. Actual utilization depends on system resources. When running inside Docker, it’s recommended to set this manually since containerization prevents the tiler from querying system resources.   System available.
--input-up-axis Treat the given axis as up. This may need to be set to Y for COLLADA files that have internal axis specifications.   Z
--longitude The longitude in EPSG:4326 coordinates (degrees) at which to place the model’s origin.    
--latitude The latitude in EPSG:4326 coordinates (degrees) at which to place the model’s origin.    
--height The height in meters relative to the WGS84 ellipsoid at which to place the model’s origin.    
--scale, -s The uniform scale to apply to the model. The tiler assumes the units for the input model(s) are in meters.   1.0
--compression-level Draco compression level between 0 and 10. In general, the highest setting, 10, will have the most compression but worst decompression speed. 0 will have the least compression, but best decompression speed. Compression level does not affect precision.   7
--disable-geometry-compression Disable Draco geometry compression. See the Compression section for more details.   false
--gzip Save tiles with gzip compression.   false
--double-sided When true, double-sided geometry will be produced. Use when the geometry is not closed or to be able to view it from the inside in CesiumJS.   false
--tileset-version A version number or string to assign to the tileset. This is written to tileset.json and can be used as metadata to track changes.    

Next steps

Now that we’ve tiled our photogrammetry models into 3D Tiles, the next step is to stream them into CesiumJS. All we need is a web server to host our tiles, and then we can pass a URL to our tileset as shown below.

var viewer = new Cesium.Viewer('cesiumContainer');

var tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
    url : '<URL to tileset.json>'


The Hosting 3D Content tutorial walks you through setting up the Cesium ion Asset Server and streaming 3D content with CesiumJS.

Positioning the output

The longitude, latitude, and height options allow you to control the position of the model’s origin, which we define as the Cartesian coordinate (0, 0, 0) in model space. All positions in the specified OBJ files (or all transformed positions in the specified COLLADA files) are Cartesian offsets in meters relative to this origin.

For example, if a house model’s origin is the tip of the chimney, most of the house’s coordinates will be negative, because they specify points below the chimney and therefore below (0, 0, 0).

# house.obj - OBJ files specify position using "v" and inline comments using "#"

# tip of chimney
v 0 0 0

# second floor (US/Canadian convention), below tip of chimney
v 0 0 -6

# ground floor, below tip of chimney
v 0 0 -9

When tiling this dataset, the input coordinates should be the longitude, latitude, and height of the model’s origin, with height relative to the WGS84 reference ellipsoid.

So if the tip of the chimney (the model’s origin) is 9 meters above the ground floor, and if the target CesiumJS app does not use terrain, an input height of 9 will place the model’s ground floor directly on the ellipsoid surface.

If the app does use terrain, we need to take this into account when placing the model. Assume we want the house to be on a hill 10 meters above the WGS84 reference ellipsoid. We add 10 meters (terrain height) to the 9 meters of the model origin height. Providing an input height of 19 will place the house dataset with model’s ground floor on the terrain.

If we take into account only the model’s origin position and provide an input height of 9, the house will end up below terrain in CesiumJS because the height input is relative to the WGS84 ellipsoid and not relative to terrain. In other words, the tileset is on the ellipsoid surface, but the ellipsoid surface is buried beneath terrain.

Similarly, if we provide an input height of 10, the house will be under terrain in CesiumJS because we have taken the terrain into account, but not the height of the model’s origin.

The examples above focus on height, but longitude and latitude are relative to the model’s origin in the same way.

The produced tileset’s position can also be offset in CesiumJS as shown in this Sandcastle example.


Tileset is sideways in CesiumJS

Check the input-up-axis option, which defaults to Z. A sideways tileset could indicate that this must be changed to input-up-axis=Y as shown below.

bin/photogrammetry-tiler -i Model.obj -o Tileset --memory=8192 --input-up-axis=Y

Tileset isn’t in the right location in CesiumJS

Make sure --longitude, --latitude, and --height are specified when running the tiler. longitude and latitude are in degrees, with longitude between (-180, 180) and latitude between (-90, 90). Height is in meters.

Note that these options are case sensitive. See Positioning the output above for more details.

Tileset is too small or too large in CesiumJS

The tiler assumes the units for the input model(s) are in meters. Use the --scale option to adjust.

No textures in the output model in CesiumJS

If using OBJs, make sure the textures are accessible via the .mtl and the .obj file can access the .mtl. The tiler supports .jpeg/.jpg and .png textures with individual files up to 16,384x16,384 in resolution.

For best results use power-of-two textures.

Some triangles are missing from the tileset

The input models may not be using a consistent winding order, so some triangles may be oriented inwards, causing them to render on the wrong side. Use the --double-sided option to generate a tileset that forces all triangles to face both ways. This may cause a slight performance hit in CesiumJS.


Third-party licenses used by the tilers can be found in LICENSE.json.

Patent Pending: US 62/775,934

Community Support

Cesium has an active community of developers and users. Along with members of the Cesium team, they support all kinds of technical questions.

Ask the forum