Single-commit clean baseline after security scrub of niche-tells, project codenames, internal jargon, and contributor-email leaks. Contents: - 100 Rust crates (_primitives/_rust/) - 37 agent manifests (_manifests/) + generated specs (_generated/) - 67 user-invocable skills (skills/) - 33 hooks (hooks/) - Composition blocks (_blocks/) - Documentation (docs/, README.md) - TS adapter packages (_ts_packages/) - Assembler (_assembler/) - Roles (_roles/) - Templates (_templates/) - Forgejo CI (.forgejo/) Author: Denis Parfionovich <info@greendragon.info> License: see LICENSE.
12 KiB
12 KiB
| name | description | arguments | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 3d-scene | Use when building 3D scenes for web — Three.js, React Three Fiber (R3F), Spline embeds, GLTF/GLB loading, scroll-linked 3D, camera animations. Covers scene setup, model optimization, and performance budgets. |
|
3D Scene Skill
Decision Matrix — Pick Approach
| Need | Approach | Bundle Impact | Complexity |
|---|---|---|---|
| Product showcase with scroll | R3F + ScrollControls | ~150KB (Three.js) | Medium |
| Hero 3D scene (no-code) | Spline embed | ~0KB (iframe) | Low |
| Custom shaders + 3D | Three.js vanilla | ~150KB | High |
| Lightweight shader-only | OGL | ~8KB | Medium |
| Interactive configurator | R3F + Leva/dat.gui | ~150KB | Medium |
1. React Three Fiber (R3F) + Drei [E1]
Install:
npm i three @react-three/fiber @react-three/drei
Basic Scene
import { Canvas } from "@react-three/fiber";
import { OrbitControls, Environment, useGLTF } from "@react-three/drei";
function Model({ url }) {
const { scene } = useGLTF(url);
return <primitive object={scene} />;
}
function Scene() {
return (
<Canvas
camera={{ position: [0, 2, 5], fov: 45 }}
style={{ width: "100%", height: "100vh" }}
>
<Environment preset="studio" />
<Model url="/models/product.glb" />
<OrbitControls enableZoom={false} />
</Canvas>
);
}
ScrollControls (Scroll-Linked 3D) [E1]
import { Canvas, useFrame } from "@react-three/fiber";
import { ScrollControls, Scroll, useScroll } from "@react-three/drei";
function AnimatedModel() {
const scroll = useScroll();
const ref = useRef();
useFrame(() => {
const offset = scroll.offset; // 0 to 1
// Rotate model based on scroll
ref.current.rotation.y = offset * Math.PI * 2;
// Move camera/model along a path
ref.current.position.y = offset * -5;
});
return (
<mesh ref={ref}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="royalblue" />
</mesh>
);
}
function ScrollScene() {
return (
<Canvas>
<ScrollControls
pages={5} // 5 pages of scroll (500vh)
damping={0.25} // friction (seconds to catch up)
>
{/* 3D content */}
<Scroll>
<AnimatedModel />
</Scroll>
{/* HTML content overlaid */}
<Scroll html>
<div style={{ position: "absolute", top: "100vh" }}>
<h2>Section 2</h2>
</div>
<div style={{ position: "absolute", top: "200vh" }}>
<h2>Section 3</h2>
</div>
</Scroll>
</ScrollControls>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} />
</Canvas>
);
}
useScroll Utilities
const scroll = useScroll();
// scroll.offset — 0 to 1 (overall progress)
// scroll.delta — scroll speed
// scroll.range(from, distance) — 0-1 within range
// scroll.curve(from, distance) — bell curve within range
// scroll.visible(from, distance) — boolean visibility
useFrame(() => {
// Animate only in section 2 (20%-40% of scroll)
const sectionProgress = scroll.range(0.2, 0.2);
ref.current.scale.setScalar(1 + sectionProgress * 0.5);
// Fade in section 3
const visible = scroll.visible(0.4, 0.2);
material.current.opacity = visible ? scroll.curve(0.4, 0.2) : 0;
});
R3F + GSAP (Alternative to ScrollControls) [E2]
When you need GSAP's pin/snap with 3D:
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
function GSAPModel() {
const mesh = useRef();
useGSAP(() => {
gsap.to(mesh.current.rotation, {
y: Math.PI * 2,
scrollTrigger: {
trigger: "#scene-container",
start: "top top",
end: "+=3000",
pin: true,
scrub: 1,
}
});
});
return <mesh ref={mesh}>...</mesh>;
}
2. Spline Embeds [E2]
What: No-code 3D design tool with web export. Best for: Quick 3D heroes, interactive product views, landing pages.
Embed Methods
// Method 1: iframe (simplest, no bundle impact)
<iframe
src="https://my.spline.design/scene-abc123/"
frameBorder="0"
width="100%"
height="500px"
style={{ border: "none" }}
/>
// Method 2: React component (more control)
import Spline from "@splinetool/react-spline";
<Spline scene="https://prod.spline.design/abc123/scene.splinecode" />
// Method 3: Vanilla JS
import { Application } from "@splinetool/runtime";
const app = new Application(canvas);
app.load("https://prod.spline.design/abc123/scene.splinecode");
Spline Capabilities
- Scroll-linked animations (native events)
- Mouse follow / hover interactions
- State changes on click
- Physics simulations
- Responsive layout
- AI text-to-3D / image-to-3D generation
Spline Limitations
- Performance: Keep <3 lights per scene
- Polygons: Smooth subdivision max 2 levels
- Loading: Scenes can be 1-5MB+ for complex objects
- Control: Less fine-grained than custom Three.js
- Offline: Requires network to load from Spline CDN (self-hosted export available)
- Scroll sync: Less precise than R3F ScrollControls or GSAP
Spline Optimization
- Delete invisible objects (inside or behind other objects)
- Use <3 lights
- Compress on export (quality vs size tradeoff)
- Reduce subdivision levels
- Optimize CAD imports: strip internal geometry, small fillets
- Keep total polygon count reasonable for target devices
3. GLTF/GLB Loading & Optimization [E1]
The Pipeline
Source (FBX/OBJ/Blender)
→ Export as GLTF/GLB
→ Optimize with gltf-transform
→ Draco/Meshopt compression
→ KTX2 texture compression
→ Mesh quantization
→ Load in Three.js/R3F
gltf-transform Optimization [E1]
# Install
npm i -g @gltf-transform/cli
# Full optimization pipeline
gltf-transform optimize input.glb output.glb \
--compress draco \
--texture-compress webp
# Or step by step:
gltf-transform dedup input.glb deduped.glb # remove duplicate data
gltf-transform draco deduped.glb compressed.glb # geometry compression
gltf-transform webp compressed.glb textured.glb # texture to WebP
gltf-transform quantize textured.glb output.glb # mesh quantization
Real-World Size Reductions
| Stage | Example Size | Reduction |
|---|---|---|
| Raw FBX | 50 MB | baseline |
| GLTF/GLB export | 29 MB | -42% |
| Draco compression | 5 MB | -83% |
| + KTX2 textures | 2.5 MB | -91% |
| + Mesh quantization | 2 MB | -93% |
Compression Methods
| Method | Compression | Decode Speed | Three.js Version |
|---|---|---|---|
| Draco | Best (~90%) | Slower (Web Worker) | r100+ |
| Meshopt | Good (~85%) | Faster | r122+ |
| Quantization | Moderate (~50%) | Instant | r100+ |
Decision: Draco for smallest files, Meshopt for fastest client decode. Combine with KTX2 textures.
Loading in R3F
import { useGLTF, useTexture } from "@react-three/drei";
// Preload for instant display
useGLTF.preload("/models/product.glb");
function Product() {
const { scene, nodes, materials } = useGLTF("/models/product.glb");
return (
<primitive
object={scene}
scale={0.5}
position={[0, -1, 0]}
/>
);
}
// With Draco decoder
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
// In Canvas setup or loader config:
// DRACOLoader points to decoder files (usually from CDN)
4. Performance Budgets [E1]
Polygon Limits
| Device Class | Max Triangles | Draw Calls | Notes |
|---|---|---|---|
| High-end desktop | 500K-1M | <200 | Gaming GPU |
| Average desktop | 100K-300K | <100 | Integrated GPU |
| Mobile (flagship) | 50K-150K | <50 | iPhone 14+ level |
| Mobile (budget) | 20K-50K | <30 | Older Android |
Key insight: Draw call count matters MORE than polygon count. Below 100 draw calls, most devices maintain 60fps. Above 500, even powerful GPUs struggle.
Texture Budgets
| Texture | Max Size | Format | VRAM |
|---|---|---|---|
| Diffuse/Albedo | 2048x2048 | KTX2/WebP | ~5MB |
| Normal map | 1024x1024 | KTX2 | ~1.3MB |
| Roughness/Metal | 512x512 | KTX2 | ~0.3MB |
| Environment | 256x256 cube | HDR/EXR | ~2MB |
Critical: A 200KB PNG on disk = 20MB+ in VRAM! KTX2 with Basis Universal stays compressed on GPU (~10x reduction).
R3F Performance Tips
// 1. Use instancing for repeated objects
import { Instances, Instance } from "@react-three/drei";
<Instances>
{positions.map((pos, i) => (
<Instance key={i} position={pos} />
))}
</Instances>
// 2. Frustum culling (enabled by default in Three.js)
// 3. LOD (Level of Detail)
import { Detailed } from "@react-three/drei";
<Detailed distances={[0, 50, 100]}>
<HighPolyModel />
<MedPolyModel />
<LowPolyModel />
</Detailed>
// 4. Limit pixel ratio on mobile
<Canvas dpr={[1, 2]}> {/* max 2x, not device native */}
// 5. Use drei's Preload for loading screen
import { Preload } from "@react-three/drei";
<Canvas>
<Suspense fallback={null}>
<Scene />
</Suspense>
<Preload all />
</Canvas>
Monitor Performance
import { useFrame } from "@react-three/fiber";
import { Stats } from "@react-three/drei";
// Show FPS/MS/MB overlay
<Stats />
// Or manual monitoring
useFrame((state) => {
const info = state.gl.info;
// info.render.triangles — triangles per frame
// info.render.calls — draw calls per frame
// info.memory.textures — loaded textures
// info.memory.geometries — loaded geometries
});
5. Lighting & Environment [E2]
Quick Setups (drei)
// Studio lighting (product showcase)
<Environment preset="studio" />
// HDR environment
<Environment files="/hdri/warehouse.hdr" />
// Simple 3-point lighting
<ambientLight intensity={0.3} />
<directionalLight position={[5, 5, 5]} intensity={1} castShadow />
<directionalLight position={[-3, 3, -5]} intensity={0.5} />
// Contact shadows (cheap fake shadows)
<ContactShadows
position={[0, -1, 0]}
opacity={0.5}
scale={10}
blur={2}
/>
Environment Presets (drei)
Available: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse
6. Common Patterns
Product Reveal on Scroll
function ProductReveal() {
return (
<Canvas>
<ScrollControls pages={4} damping={0.3}>
<Scroll>
<ProductModel /> {/* rotates with scroll */}
</Scroll>
<Scroll html>
<section style={{ top: "100vh" }}>Feature 1</section>
<section style={{ top: "200vh" }}>Feature 2</section>
<section style={{ top: "300vh" }}>CTA</section>
</Scroll>
</ScrollControls>
<Environment preset="studio" />
</Canvas>
);
}
Floating Objects (Parallax)
import { Float } from "@react-three/drei";
<Float
speed={2} // animation speed
rotationIntensity={0.5}
floatIntensity={0.5}
>
<mesh>
<torusGeometry args={[1, 0.3, 16, 32]} />
<meshStandardMaterial color="hotpink" />
</mesh>
</Float>
Text in 3D
import { Text3D, Center } from "@react-three/drei";
<Center>
<Text3D
font="/fonts/inter_bold.json"
size={0.75}
height={0.2}
curveSegments={12}
>
Hello World
<meshNormalMaterial />
</Text3D>
</Center>
Workflow
- Define the 3D need — is it product showcase, decoration, interactive?
- Pick approach — R3F for code control, Spline for no-code, OGL for shader-only
- Prepare models — export GLTF/GLB, optimize with gltf-transform
- Set up scene — Camera, lighting, environment
- Add scroll interaction — ScrollControls or GSAP integration
- Optimize — polygon budget, texture compression, instancing
- Test performance — Stats component, Chrome DevTools, test on mobile
- Add fallbacks — static image for low-end devices, loading state