惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
博客园 - 三生石上(FineUI控件)
GbyAI
GbyAI
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
Engineering at Meta
Engineering at Meta
I
InfoQ
T
Tailwind CSS Blog
N
Netflix TechBlog - Medium
S
SegmentFault 最新的问题
H
Help Net Security
博客园 - 【当耐特】
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
博客园 - 叶小钗
T
The Blog of Author Tim Ferriss
腾讯CDC
雷峰网
雷峰网
Martin Fowler
Martin Fowler
The GitHub Blog
The GitHub Blog
D
Docker

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
I Built an AI 3D Model Generator — Here's How I Handle Me...
Dyle Nazarro · 2026-05-25 · via DEV Community

I shipped www.imgto3d.ai a few months ago. It's an AI-powered 3D model generator: you upload an image, pick a generator (upscale, denoise, face recovery, or full restoration), and get back a .glb file you can drop into Blender, Unity, or straight into a web page.

The AI backend part — calling Replicate, polling for results, handling credits — was straightforward. The part that actually made me lose sleep? Getting those generated 3D assets to render smoothly inside a Next.js app without turning the user's phone into a hand warmer.

This is the story of how I built the frontend, and the specific performance tricks I had to pull to make browser-based 3D preview not feel like a PowerPoint from 2003.


What the Product Actually Does

ai3dgen.com runs four generators:

  • Upscale — takes a low-poly preview and bumps the geometry density.
  • Denoise / Unblur — cleans up noisy AI outputs.
  • Face Recovery — fixes distorted facial topology on character models.
  • Restoration — full pipeline for damaged or low-quality inputs.

Users pick their settings, burn some credits, and get an email when the mesh is ready. The critical moment is when they click "Preview" and expect to spin the model around in 3D right there in the browser.

That preview is where everything almost fell apart.


The Problem: AI Doesn't Care About Your Frame Rate

When users want to turn a 2D image into a 3D model online, they expect two things: fast processing and a crisp, interactive preview. The AI pipeline delivers beautiful meshes — thousands of polygons, 4K textures, the works. But it delivers them with zero optimization. There's no LOD. No texture atlasing. No one ran it through a decimation modifier.

My first attempt was embarrassingly naive: dump the raw .glb into a <Canvas> and call it a day. On my M2 MacBook, buttery smooth. On a 2021 Android phone? Tab crash. iOS Safari? "A problem occurred with this webpage so it was reloaded." Every. Single. Time.

I realized I wasn't just building a product — I was building a 3D runtime that had to survive the wild west of consumer hardware.


The Stack

  • Next.js 14 (App Router) — SSR for SEO, client components for the viewer.
  • Tailwind CSS — because I'm not writing raw CSS in 2026.
  • React Three Fiber (R3F) + @react-three/drei — declarative 3D scenes as React components. If you're still wrapping raw Three.js in useEffect, you're making life harder than it needs to be.
  • Replicate API — the AI pipeline that generates the actual meshes.

R3F makes it easy to get something on screen. It does not make it easy to get something performant on screen. That part is on you.


The Viewer Component (Production Code)

Here's the actual component running in production. Every line in here exists because something broke in production first.

import React, { Suspense, useMemo } from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls, useGLTF, Center, Environment } from '@react-three/drei';

function Model({ url }: { url: string }) {
  // useGLTF caches by URL. But if your URL is a signed S3 link 
  // that changes every refresh, congrats — you just disabled caching.
  const { scene } = useGLTF(url, true);

  // Clone the scene so multiple instances don't share materials
  const clonedScene = useMemo(() => scene.clone(), [scene]);

  return (
    <primitive 
      object={clonedScene} 
      scale={1.5} 
      castShadow 
      receiveShadow 
    />
  );
}

function Loader() {
  return (
    <div className="absolute inset-0 flex flex-col items-center justify-center bg-[#0d0e12] text-white z-10">
      <div className="h-10 w-10 border-2 border-gray-700 border-t-cyan-500 rounded-full animate-spin" />
      <p className="mt-4 text-sm text-gray-400">Decoding mesh...</p>
    </div>
  );
}

export default function Viewer3D({ modelUrl }: { modelUrl: string }) {
  return (
    <div className="relative w-full h-[500px] bg-[#0d0e12] rounded-xl overflow-hidden border border-gray-800">
      <Suspense fallback={<Loader />}>
        <Canvas
          camera={{ position: [0, 1.5, 4], fov: 45 }}
          gl={{ 
            antialias: true, 
            preserveDrawingBuffer: true,
            powerPreference: "high-performance"
          }}
          dpr={[1, 2]} // Clamp pixel ratio — retina screens will murder your FPS otherwise
        >
          <ambientLight intensity={0.6} />
          <directionalLight 
            position={[5, 5, 5]} 
            intensity={1.0} 
            castShadow 
            shadow-mapSize-width={1024}
            shadow-mapSize-height={1024}
          />

          <Center>
            <Model url={modelUrl} />
          </Center>

          <OrbitControls 
            enablePan={false}
            enableZoom={true}
            minDistance={1.5}
            maxDistance={8}
            maxPolarAngle={Math.PI / 1.5} // Prevent going below the ground
            autoRotate
            autoRotateSpeed={1.0}
          />

          <Environment preset="city" />
        </Canvas>
      </Suspense>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode


Three Things I Learned the Hard Way

1. Draco Compression Is Non-Negotiable

The AI backend originally served raw GLBs. A typical output was 45MB. On a 3G connection, that's 30+ seconds of staring at a spinner. Users bounce before the first vertex loads.

I added server-side Draco compression. File sizes dropped to ~8MB. Visually identical. The catch? You need to tell useGLTF to use a Draco decoder, and that decoder needs a .wasm file. Host that .wasm on a different domain without CORS headers? Silent failure. The model just never loads, and you get no error in Sentry.

// In your app entry or a useEffect
useGLTF.setDRACOLoader(new DRACOLoader().setDecoderPath('/draco/'));

Enter fullscreen mode Exit fullscreen mode

Also, Draco decoding is CPU-intensive. I throttle it by limiting concurrent loads — if a user rapidly switches between models in the gallery, I cancel the previous request. Otherwise, the main thread chokes and the UI freezes.

2. iOS Safari Is the Final Boss

Apple's WebGL implementation on iOS has a hard memory limit. Not a suggestion — a hard ceiling. Exceed it, and Safari nukes your tab without warning. No exception. No graceful degradation. Just reload.

I discovered this testing a high-poly model with an 8K texture. Chrome desktop? Perfect 60 FPS. iPhone 14? Instant reload.

My fixes:

  • Cap texture resolution at 2K for mobile (detected via navigator.hardwareConcurrency + touch event sniffing).
  • Clamp dpr to [1, 1.5] on mobile instead of [1, 2].
  • Implemented a "Lite Mode" toggle that swaps to a lower-poly version of the mesh. I hate that it exists, but users on older devices love it.

3. Don't Trust the Default Lighting

A 3D model under default lighting looks like a plastic toy from a gas station. I spent way too long tweaking individual lights before realizing R3F's <Environment> component with a preset solves 90% of the problem. It gives you realistic reflections and ambient occlusion for free.

The other 10%? A subtle contact shadow under the model using <ContactShadows> from Drei. It grounds the object and makes it feel like it's sitting on a surface, not floating in a void.


Why This Architecture?

I chose Next.js + R3F because I needed two things that usually fight each other:

  1. SEO — the marketing pages need to rank. Next.js App Router handles that.
  2. Interactive 3D — the viewer needs to be a rich client experience. R3F handles that.

The split is clean: server components for landing pages, metadata, and auth. Client components wrapped in 'use client' only where the Canvas lives. I keep the bundle small by dynamically importing the viewer:

const Viewer3D = dynamic(() => import('@/components/Viewer3D'), { ssr: false });

Enter fullscreen mode Exit fullscreen mode

This way, the Three.js payload only hits users who actually click "Preview." Everyone else gets a fast, lightweight landing page.


Where It Stands Now

ai3dgen.com is live. It handles four distinct generation pipelines, serves 12 languages, and processes thousands of conversions per week. The 3D viewer went from "crash my phone" to "smooth on a 4-year-old mid-range Android."

The hardest part wasn't the AI. It was respecting the user's hardware.


Try It / Break It

If you're building something with in-browser 3D — an AI tool, a product configurator, a portfolio — the ecosystem is mature enough that you don't need a graphics PhD. R3F + Drei + a bit of performance paranoia gets you 90% of the way there.

If you want to stress-test your own 3D viewer, image-to-3d AI will give you some gloriously unoptimized GLBs to throw at it. Upload an image, grab the mesh, and see if your renderer survives.

How are you handling 3D assets in your web apps? Running into the same memory limits, or have you jumped to WebGPU already? Drop a comment — always curious to see how others are solving this.