diff --git a/src/pg2b3dm.slnx b/src/pg2b3dm.slnx
index b89b1497..501764eb 100644
--- a/src/pg2b3dm.slnx
+++ b/src/pg2b3dm.slnx
@@ -22,4 +22,7 @@
+
+
+
diff --git a/src/samples/GltfExport/GltfExport.csproj b/src/samples/GltfExport/GltfExport.csproj
new file mode 100644
index 00000000..16c1146d
--- /dev/null
+++ b/src/samples/GltfExport/GltfExport.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net8.0
+ gltfexport
+ GltfExport
+ true
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/samples/GltfExport/Options.cs b/src/samples/GltfExport/Options.cs
new file mode 100644
index 00000000..a4395f31
--- /dev/null
+++ b/src/samples/GltfExport/Options.cs
@@ -0,0 +1,40 @@
+using CommandLine;
+using SharpGLTF.Materials;
+
+namespace GltfExport;
+
+public class Options
+{
+ [Option("connection", Required = true, HelpText = "Database connection string.")]
+ public string Connection { get; set; } = string.Empty;
+
+ [Option('o', "output", Required = false, Default = "output", HelpText = "Output directory.")]
+ public string Output { get; set; } = "output";
+
+ [Option('t', "table", Required = true, HelpText = "Database table, include database schema if needed.")]
+ public string Table { get; set; } = string.Empty;
+
+ [Option('c', "column", Required = false, Default = "geom", HelpText = "Geometry column.")]
+ public string GeometryColumn { get; set; } = "geom";
+
+ [Option("shaderscolumn", Required = false, Default = "", HelpText = "Shaders column.")]
+ public string ShadersColumn { get; set; } = string.Empty;
+
+ [Option("idcolumn", Required = false, Default = "id", HelpText = "Id column.")]
+ public string IdColumn { get; set; } = "id";
+
+ [Option("default_color", Required = false, Default = "#FFFFFF", HelpText = "Default color, in RGB(A) order.")]
+ public string DefaultColor { get; set; } = "#FFFFFF";
+
+ [Option("default_metallic_roughness", Required = false, Default = "#008000", HelpText = "Default metallic roughness.")]
+ public string DefaultMetallicRoughness { get; set; } = "#008000";
+
+ [Option("double_sided", Required = false, Default = true, HelpText = "Default double sided.")]
+ public bool DoubleSided { get; set; } = true;
+
+ [Option("default_alpha_mode", Required = false, Default = AlphaMode.OPAQUE, HelpText = "Default glTF material AlphaMode. Other values: BLEND and MASK. Defines how the alpha value is interpreted.")]
+ public AlphaMode DefaultAlphaMode { get; set; } = AlphaMode.OPAQUE;
+
+ [Option("alpha_cutoff", Required = false, Default = 0.5f, HelpText = "Default glTF material AlphaCutoff (used with MASK alpha mode).")]
+ public float AlphaCutoff { get; set; } = 0.5f;
+}
diff --git a/src/samples/GltfExport/Program.cs b/src/samples/GltfExport/Program.cs
new file mode 100644
index 00000000..d50d3ef9
--- /dev/null
+++ b/src/samples/GltfExport/Program.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using CommandLine;
+using Newtonsoft.Json;
+using Npgsql;
+using SharpGLTF.Materials;
+using Wkb2Gltf;
+using Wkx;
+using WkbTriangle = Wkb2Gltf.Triangle;
+
+namespace GltfExport;
+
+class Program
+{
+ static void Main(string[] args)
+ {
+ var version = Assembly.GetEntryAssembly()?.GetName().Version;
+ Console.WriteLine($"Tool: GltfExport {version}");
+
+ Parser.Default.ParseArguments(args).WithParsed(o =>
+ {
+ Console.WriteLine($"Table: {o.Table}");
+ Console.WriteLine($"Geometry column: {o.GeometryColumn}");
+ Console.WriteLine($"Id column: {o.IdColumn}");
+ Console.WriteLine($"Shaders column: {(string.IsNullOrEmpty(o.ShadersColumn) ? "-" : o.ShadersColumn)}");
+ Console.WriteLine($"Output directory: {o.Output}");
+ Console.WriteLine($"Default color: {o.DefaultColor}");
+ Console.WriteLine($"Default metallic roughness: {o.DefaultMetallicRoughness}");
+ Console.WriteLine($"Double sided: {o.DoubleSided}");
+ Console.WriteLine($"Default alpha mode: {o.DefaultAlphaMode}");
+ Console.WriteLine($"Alpha cutoff: {o.AlphaCutoff}");
+
+ Directory.CreateDirectory(o.Output);
+
+ var sql = BuildQuery(o.Table, o.GeometryColumn, o.IdColumn, o.ShadersColumn);
+
+ using var conn = new NpgsqlConnection(o.Connection);
+ conn.Open();
+
+ var cmd = new NpgsqlCommand(sql, conn);
+ var reader = cmd.ExecuteReader();
+
+ var written = 0;
+ var skipped = 0;
+
+ while (reader.Read())
+ {
+ var id = reader.GetFieldValue