Digital Twin

AWS IoT TwinMaker Alternatives: Build 3D Digital Twins Without Cloud Lock-In

You can build interactive 3D digital twins with open web technology instead of a cloud-specific twin service. Compare the options and bind live MQTT data to a glTF model in the browser.

Browser-based 3D factory scene with live MQTT values, illustrating an AWS IoT TwinMaker alternative built on open web 3D libraries
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

An AWS IoT TwinMaker alternative makes sense when you want interactive 3D digital twins but prefer open formats, standard protocols and the freedom to run anywhere. This guide compares the realistic alternatives, from other platforms to fully open web stacks, and then shows the core technique: binding live MQTT data to nodes of a glTF model in the browser. You will finish with a working pattern you can extend into a full 3D twin.

For background on twins in general, see the digital twin technology guide.

What TwinMaker Bundles Together

AWS IoT TwinMaker combines several pieces that a 3D twin needs:

  • Entities and components that model your assets and link them to data sources.
  • Connectors to other AWS data services for time-series data, video and documents.
  • A scene composer for placing 3D models and attaching data-driven tags and color rules.
  • Visualization through a Grafana plugin or an application kit for custom web apps.

When your data already lives in those AWS services, this is convenient. The trade-off is that the twin, its scenes and its data bindings are expressed in service-specific concepts. Moving to another cloud, or to an on-premises plant network, means rebuilding them. Platform roadmaps also change over time, so check the current status of any managed twin service before committing a long-lived project to it.

What a Portable 3D Twin Needs

Strip away the branding and every 3D twin has the same five layers:

Layer Job Portable options
Geometry The 3D model of the plant or machine glTF/GLB exported from CAD or Blender
Rendering Draw and interact with the scene in a browser Three.js, Babylon.js, CesiumJS for geospatial
Live data Deliver current values to the scene MQTT over Secure WebSocket
Binding Map data fields to nodes, colors, labels, animations A small JSON mapping file plus your code
History Trends, playback, analytics Time-series database plus Grafana or your own charts

Each layer has mature open choices, and each can be replaced without touching the others. That independence is the real answer to lock-in.

The Alternatives Compared

Approach 3D authoring Data path Lock-in Effort
Other cloud twin services Vendor scene editor Vendor IoT and data services High Low to medium
Game engines (Unity, Unreal) Full engine editor Plugins or custom MQTT clients Medium (engine licensing, runtimes) Medium to high
Three.js + MQTT Code, glTF from any tool MQTT over WSS directly in the browser Low Medium
Babylon.js + MQTT Code plus Babylon tooling MQTT over WSS Low Medium
Grafana + panels + MQTT-fed database Limited 3D, strong charts Telegraf or a subscriber writes to a database Low Low

Game engines shine for photorealistic training and VR; see the Unity digital twin guide. For operations dashboards that anyone can open in a browser, web libraries are usually the better fit. The web-based 3D digital twin guide compares Three.js and Babylon.js in more depth.

Binding MQTT Data to a glTF Model

The heart of any TwinMaker replacement is the binding layer: "when this topic changes, update that 3D node". Keep the binding in data, not code, so non-developers can maintain it.

Here is a mapping file. Each entry links a topic and payload field to a named node in the glTF scene and says what to do with the value:

[
  { "topic": "furnace1/state", "field": "temp_c", "node": "Furnace_Body",
    "action": "color", "ranges": [[0, 600, "#4caf50"], [600, 900, "#ff9800"], [900, 2000, "#f44336"]] },
  { "topic": "conveyor1/state", "field": "running", "node": "Conveyor_Belt",
    "action": "visible-flag" },
  { "topic": "tank3/state", "field": "level_pct", "node": "Tank3_Liquid",
    "action": "scale-y", "max": 100 }
]

And the browser module that applies it. Install three and mqtt with npm and bundle with Vite or similar. The model plant.glb must contain nodes with the names used in the mapping.

import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import mqtt from "mqtt";
import bindings from "./bindings.json";

const PREFIX = "connectmq/demo/tm-alt-2h7/site1";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 500);
camera.position.set(20, 15, 20);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.HemisphereLight(0xffffff, 0x404040, 3));

const nodes = {};
new GLTFLoader().load("/plant.glb", (gltf) => {
  scene.add(gltf.scene);
  for (const b of bindings) {
    const obj = gltf.scene.getObjectByName(b.node);
    if (!obj) { console.warn("Missing node", b.node); continue; }
    obj.traverse((o) => { if (o.isMesh) o.material = o.material.clone(); });
    nodes[b.node] = obj;
  }
});

function apply(b, value) {
  const obj = nodes[b.node];
  if (!obj) return;
  if (b.action === "color") {
    const hit = b.ranges.find(([lo, hi]) => value >= lo && value < hi);
    if (hit) obj.traverse((o) => o.isMesh && o.material.color.set(hit[2]));
  } else if (b.action === "visible-flag") {
    obj.traverse((o) => o.isMesh && o.material.emissive?.set(value ? 0x224422 : 0x000000));
  } else if (b.action === "scale-y") {
    obj.scale.y = Math.max(0.01, value / b.max);
  }
}

const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
  clientId: "tm-alt-2h7-view-" + Math.random().toString(16).slice(2, 8),
  protocolVersion: 5,
});
client.on("connect", () => client.subscribe(`${PREFIX}/+/state`));
client.on("message", (topic, payload) => {
  const rel = topic.slice(PREFIX.length + 1);
  const data = JSON.parse(payload.toString());
  for (const b of bindings) if (b.topic === rel && b.field in data) apply(b, data[b.field]);
});

renderer.setAnimationLoop(() => renderer.render(scene, camera));

Test it by publishing retained states to the public MQTT test broker:

mosquitto_pub -h broker.connectmq.com -p 1883 -i tm-alt-2h7-pub -r \
  -t "connectmq/demo/tm-alt-2h7/site1/furnace1/state" -m '{"temp_c":935}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i tm-alt-2h7-pub2 -r \
  -t "connectmq/demo/tm-alt-2h7/site1/tank3/state" -m '{"level_pct":62}'

Because the states are retained, the scene shows correct colors and levels the moment it loads, then updates live. That is the same experience a managed scene composer offers, built from open parts. The guide to binding IoT data to a 3D model covers more actions such as animations, labels and alarm blinking, and the Three.js documentation is the reference for the rendering side.

Pitfalls When Leaving a Managed Twin Service

  • Node names drift. Re-exporting a CAD model can rename nodes and silently break bindings. Log missing nodes at load time, as the code above does, and keep names stable in your export pipeline.
  • Shared materials. In glTF, many meshes often share one material, so coloring one node colors them all. Clone materials for bound nodes.
  • Heavy models. CAD exports can be enormous. Decimate geometry, merge static meshes and use compressed glTF to keep load times reasonable.
  • History is separate. Live MQTT data drives the scene, but trends and playback need a database. Subscribe a writer service or Telegraf to the same topics.
  • Browser credentials. Anything shipped to a browser can be read by its user. Give viewers read-oriented credentials separate from those of gateways that publish.
  • Missing state on first load. If gateways publish only changes, a freshly opened scene stays blank until something changes. Publish full state messages with the retain flag so every viewer starts from the current picture.
  • Rendering every message. High-rate telemetry can trigger more scene updates than the screen can show. Store the latest values when messages arrive and apply them once per animation frame instead.

A Migration Path in Four Moves

If you are replacing an existing managed twin rather than starting fresh, move in small, reversible steps:

  1. Export your 3D assets to glTF and confirm the node names you need for bindings.
  2. Make gateways publish to an MQTT broker in parallel with the existing data path.
  3. Rebuild scene bindings as a mapping file and compare the new viewer against the old one side by side.
  4. Move history to a time-series database fed from the same topics, then retire the old path.

Hosting the Data Layer

The live data layer is the only piece that must be always on and reachable by both the plant and the browser. A hosted MQTT broker with Secure WebSocket support solves this without opening ports at the plant: gateways connect outbound over TLS, browsers connect over WSS.

ConnectMQ provides exactly that: TLS on 8883 for gateways, Secure WebSocket on 8084 at path /mqtt for browsers, MQTT 5 support and a portal with a live topic explorer. ConnectMQ also runs a public 3D digital twin web demo, a factory with a CNC machine, robot arm, furnace, tank, conveyor and pump updated live over WSS, built on this same browser-plus-MQTT pattern. For production, swap the public endpoint for your private broker credentials from the portal: YOUR_USERNAME, YOUR_PASSWORD and YOUR_CLIENT_ID.

Build Your 3D Twin on ConnectMQ

Open web 3D libraries plus a standard MQTT broker give you an interactive twin you fully control. Start with a free ConnectMQ Serverless broker, no credit card required, and connect your first glTF scene to live data today.

Frequently Asked Questions

What is a good AWS IoT TwinMaker alternative for 3D?

For browser-based operations twins, Three.js or Babylon.js with glTF models and MQTT over Secure WebSocket is a flexible, portable choice. Game engines like Unity suit photorealistic or VR experiences.

Can I reuse my 3D models from TwinMaker?

Usually yes, if they are in glTF or GLB format. You will need to recreate data bindings and tag placements, which is easiest if you keep them in a mapping file as shown above.

Do browsers connect to MQTT directly?

Yes. MQTT.js connects over WebSocket or Secure WebSocket, so a web page can subscribe to topics without a backend relay. Use TLS and separate viewer credentials in production.

Where does historical data go without TwinMaker?

Use a time-series database fed by an MQTT subscriber such as Telegraf or a small script, and visualize it in Grafana or charts inside your twin page.

Related articles

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.