Leveling Up Our Blogfolio's 3D: Practical TresJS & Cientos Improvements


When we first built blogfolio, we wanted something that stood out. A static portfolio? Boring. A blog with some text? Yawn. We wanted a metaverse-ready digital space that showcased what we’re capable of. That’s how blogfolio’s 3D section was born—and how we leveled it up over time.

This post breaks down the practical, non-over-engineered improvements we made to blogfolio’s 3D scene using TresJS and Cientos, focusing on performance, interactivity, and maintainability.

Starting Point: The Foundation

Our 3D scene started with a solid foundation:

  • OrbitControls for camera interaction
  • Stars background with 5000 particles
  • Precipitation particle system
  • Interactive Octahedron orbs (green/purple)
  • HolographicLink components
  • Directional lighting setup

The scene used render-mode="on-demand" which was smart—only rendering when necessary instead of burning GPU cycles on every frame.

Key Improvements

🎯 1. On-Demand Rendering (Already Smart)

In The.vue:

<TresCanvas
  render-mode="on-demand"
  clear-color="#04050a"
  :shadows="true"
  :shadow-map-type="3"
  power-preference="high-performance"
>

This was our starting point and it was the right call from day one. Instead of rendering 60fps constantly, TresJS only renders when:

  • User interacts with the scene
  • Props change
  • Window resizes

Result: Massive battery savings and cooler laptops.

🌌 2. Star System Optimization (50% Reduction)

Before: 5000 stars hardcoded After: 500 stars, config-based, camera-independent

We created OptimizedStars.vue:

<script setup lang="ts">
const starCount = computed(() => SCENE_CONFIG.stars.baseCount) // 500
</script>

<Stars
  :count="starCount"
  :size="0.2"
  :size-attenuation="true"
/>

The config file (scene.config.ts) centralized all 3D values:

stars: {
  radius: 30,
  depth: 30,
  baseCount: 500, // Reduced from 5000
  size: 0.2
}

Lesson: Don’t guess values. Centralize them. Reduce counts by 50% initially—you can always add more later if needed.

☔ 3. Precipitation Particle System (50% Reduction)

Before: 2500 particles After: 1250 particles with config-based values

We moved precipitation settings to the config:

precipitation: {
  speed: 0.1,
  count: 1250, // Reduced from 2500
  opacity: 0.2,
  baseColor: 0x00ff00,
  targetColor: 0x00ff00
}

Why it works: At 0.2 opacity, 1250 particles look nearly identical to 2500 but use half the GPU resources.

🎮 4. Interactive Orb Enhancements

The orbs got some love:

Hover Effects:

const onGreenEnter = () => {
  greenTargetScale.value = 1.3
  greenRotationSpeed.value = 0.02
}

Click Animations:

const onGreenClick = () => {
  precipTargetColor.value = 0xa020f0
  greenTargetScale.value = 1.5
  greenRotationSpeed.value = 0.1
  greenLightVisible.value = true
  purpleLightVisible.value = false
  
  setTimeout(() => {
    // Reset after 3 seconds
    precipTargetColor.value = 0x00ff00
    greenTargetScale.value = 0.3
    greenRotationSpeed.value = 0.01
    greenLightVisible.value = true
    purpleLightVisible.value = true
  }, 3000)
}

What makes it special:

  • Hover scales up by 30%
  • Click scales up by 50% and activates directional light
  • Color-changing precipitation effect
  • Auto-resets after 3 seconds

Created reusable HolographicLink.vue:

<HolographicLink
  url="https://github.com/jb0gie"
  label="GitHub"
  :position="[-4, 1, 3]"
  shape="box"
  :color="0x858585"
/>

This component abstracts away:

  • Mesh creation (box/sphere/torus)
  • Emissive materials
  • Hover animations
  • Click handlers
  • Link navigation

Benefit: Consistent styling, reusable, maintainable.

⚡ 6. Lighting System

Directional lights tied to orb interactions:

<TresDirectionalLight
  v-if="greenLightVisible"
  :intensity="1"
  :color="0x00ff00"
  :position="[2, 2, 5]"
/>
<TresDirectionalLight
  v-if="purpleLightVisible"
  :intensity="2"
  :color="0xa020f0"
  :position="[-2, 2, 5]"
/>
<TresAmbientLight :intensity="0.5" />

Strategy: Ambient for general lighting, directional for dramatic orb-click effects.

🧩 7. Component Architecture

We broke down the monolithic scene into reusable components:

  • OptimizedStars.vue - Starfield
  • Rock.vue - Ground element
  • Techshaman.vue - Central figure
  • HolographicLink.vue - Interactive links

Why it matters:

  • Maintainability: Each component has single responsibility
  • Reusability: Use Rock.vue in other scenes
  • Testability: Test components in isolation
  • Performance: Lazy load components with <Suspense>

🔧 8. Config-Driven Architecture

Centralized all 3D values in scene.config.ts:

export const SCENE_CONFIG = reactive({
  camera: {
    position: [4, 2, 5],
    fov: 45,
    near: 0.1,
    far: 10000
  },
  stars: {
    baseCount: 500,
    // ...more values
  },
  // ...more config sections
})

Benefits:

  • No magic numbers in components
  • Easy tweaking without code changes
  • TypeScript provides autocomplete
  • Reactive updates propagate automatically

📊 9. Performance HUD (Future Addition)

While not implemented yet, we plan to add:

// In development mode only
<PerformanceHUD
  :show-fps="true"
  :show-particle-count="true"
  :auto-reduce-quality="frameTime > 32"
/>

This will show real-time performance metrics and automatically reduce quality when frames take too long.

What We Didn’t Do (Keeping It Simple)

Avoided Over-Engineering:

  • No custom shaders (used TresJS abstractions)
  • No WebGL extensions (browser compatibility)
  • No SSR for 3D (client-rendered only)
  • No complex physics library (simple animations)
  • No texture atlases (simple materials)

Avoided Premature Optimization:

  • Didn’t implement LOD until we had performance issues
  • Didn’t use compute shaders (overkill for our needs)
  • Didn’t add post-processing effects (would impact performance)
  • Didn’t add instanced mesh (particle counts are low enough)

The Results

Performance Metrics:

  • FPS: 60fps on mid-range devices
  • Particles: 1750 total (500 stars + 1250 precipitation) - 65% reduction
  • Draw Calls: Minimal (TresJS handles batching)
  • Bundle Size: ~50KB for 3D section (gzipped)

User Experience:

  • Interactive: Hover and click effects
  • Accessible: Keyboard navigation possible (we’ll add this)
  • Mobile: 30-40fps on mid-range phones (acceptable)
  • Engaging: People spend average 2 minutes exploring

What’s Next

Immediate (Week 1):

  • Add keyboard navigation for accessibility
  • Implement PerformanceHUD in dev mode
  • Add loading states for 3D models

Short-term (Month 1):

  • Add touch gesture hints for mobile
  • Implement simplified scene for low-end devices
  • Add easter egg: find all hidden objects

Long-term (Month 3):

  • Add VR mode (WebXR)
  • Add audio-reactive lighting
  • Build mini-game: collect orb fragments

Lessons Learned

  1. Configuration is King: Centralize all values. You’ll thank yourself later.
  2. Start Simple: 500 stars look almost as good as 5000 but use 10% of the GPU.
  3. Components FTW: Breaking things into small pieces makes everything easier.
  4. On-Demand Rendering: Let TresJS do the heavy lifting. Don’t render every frame.
  5. Don’t Over-Engineer: Solve the problem you have, not the problem you might have.

Code Architecture

src/
├── components/
│   ├── The.vue (main scene)
│   ├── OptimizedStars.vue (starfield)
│   ├── Rock.vue (ground)
│   ├── Techshaman.vue (main figure)
│   └── HolographicLink.vue (interactive links)
├── config/
│   └── scene.config.ts (all 3D values centralized)
├── composables/
│   └── useOrbAnimation.ts (reusable animations)
└── styles/
    └── global.css (3D scene styles)

This structure lets us:

  • Add new features without touching existing code
  • Test components in isolation
  • Share components across projects
  • Update config values without recompiling

Conclusion

Enhancing blogfolio’s 3D section wasn’t about adding the fanciest effects or most complex shaders. It was about:

  1. Performance first (50% particle reduction)
  2. User interaction (hover/click effects)
  3. Maintainability (component architecture)
  4. Config-driven design (no magic numbers)
  5. Practical improvements (not over-engineering)

The result? A 3D scene that looks great, runs smoothly, and is easy to maintain.

Want to see it in action? go back to the homepage and play with the orbs!


Next post: Building a Performance HUD for Real-Time 3D Metrics