BBC's guide to development
  • General

    • About
    • Tools
    • Git(hub)
    • Showpad
    • Hosting
    • Maintenance
    • Security
    • Go live checklist
  • Front-end development

    • Bundlers
    • CSS/SCSS
    • Javascript
    • Vue
    • PHP
    • Mails
    • Dev Faq
  • Functions
  • Mixins
  • General

    • OOP Structure
  • Component Classes

    • Accordion
    • App
    • Component
    • HighwayApp
    • Popup
    • PNG Sequencer
    • Tab
  • Manager Classes

    • BountListenerMgr
    • Cache
    • Configuration
    • InViewStateMgr
    • Instance Manager
    • Event dispatcher
  • Factories

    • SwiperFactory
  • PDF

    • AssetLoader
    • BasePdfDoc
    • TemplatePdfDoc
    • CustomPdfDoc
  • Utility functions

    • canvas
    • Connection Status
    • css
    • dev
    • placeholder
    • dom
    • fetch
    • json
    • object
    • scroll
    • scrollbar
    • spreadsheets
    • string
    • url
  • General

    • ComponentMgr
    • ThreeJsViewer
  • Components

    • ComponentMgr
    • GltfModel
    • Snappable
    • Socket
    • ThreeJsViewer
    • ThreeJsViewerCamera
  • Loaders

    • ConfigurationSerializer
    • GltfBlockParser
  • Utils

    • CanvasInputAdapter
    • CollisionManager
    • SocketGridExpander
    • blender
    • headless
  • General

    • Troubleshooting
    • Legacy
  • Components

    • AssetBar
    • ConfigGenerator
    • ShowpadApp
  • Managers

    • Assets
    • AppsDb
    • Config
  • Utils

    • Connection Status
    • general
    • showpad-interactive
    • showpad-upload
  • Components

    • Accordion
    • BackButton
    • Breadcrumb
    • ByltButton
    • Hamburger
    • Icon
    • Logo
    • Loader
    • Modal
    • Popup
    • Prompt
    • ProgressBar
    • TextLoader
  • Composables

    • useDebugMode
    • useConnectionStatus
  • Utils

    • dom
    • props
  • General

    • General
    • Tracking
  • Components

    • Accordion
    • ActionButton
    • AssetItem
    • AssetList
    • BackButton
    • ConfigGenButton
    • Logo
    • Media
    • Modal
    • Popup
    • Prompt
    • SPButton
    • SPRouterView
    • SPTrackedRouterLink
    • TextLoader
    • View
  • Composables

    • useConnectionStatus
  • Stores

    • useAppsDbStore
    • useBreadcrumbStore
    • useShowpadAPIStore
    • useShowpadSDKStore
    • useSpConfigStore
    • useSpStore
    • useSpTrackingStore
  • The New Kit

    • General
    • Installation & Usage
    • ACF Blocks
    • PHPCS
    • Functions
    • Vite
    • WP Config
    • Staging Deployment
  • Best Practices

    • Page Structure
    • Fonts/Typography
  • Todo
GitHub
  • General

    • About
    • Tools
    • Git(hub)
    • Showpad
    • Hosting
    • Maintenance
    • Security
    • Go live checklist
  • Front-end development

    • Bundlers
    • CSS/SCSS
    • Javascript
    • Vue
    • PHP
    • Mails
    • Dev Faq
  • Functions
  • Mixins
  • General

    • OOP Structure
  • Component Classes

    • Accordion
    • App
    • Component
    • HighwayApp
    • Popup
    • PNG Sequencer
    • Tab
  • Manager Classes

    • BountListenerMgr
    • Cache
    • Configuration
    • InViewStateMgr
    • Instance Manager
    • Event dispatcher
  • Factories

    • SwiperFactory
  • PDF

    • AssetLoader
    • BasePdfDoc
    • TemplatePdfDoc
    • CustomPdfDoc
  • Utility functions

    • canvas
    • Connection Status
    • css
    • dev
    • placeholder
    • dom
    • fetch
    • json
    • object
    • scroll
    • scrollbar
    • spreadsheets
    • string
    • url
  • General

    • ComponentMgr
    • ThreeJsViewer
  • Components

    • ComponentMgr
    • GltfModel
    • Snappable
    • Socket
    • ThreeJsViewer
    • ThreeJsViewerCamera
  • Loaders

    • ConfigurationSerializer
    • GltfBlockParser
  • Utils

    • CanvasInputAdapter
    • CollisionManager
    • SocketGridExpander
    • blender
    • headless
  • General

    • Troubleshooting
    • Legacy
  • Components

    • AssetBar
    • ConfigGenerator
    • ShowpadApp
  • Managers

    • Assets
    • AppsDb
    • Config
  • Utils

    • Connection Status
    • general
    • showpad-interactive
    • showpad-upload
  • Components

    • Accordion
    • BackButton
    • Breadcrumb
    • ByltButton
    • Hamburger
    • Icon
    • Logo
    • Loader
    • Modal
    • Popup
    • Prompt
    • ProgressBar
    • TextLoader
  • Composables

    • useDebugMode
    • useConnectionStatus
  • Utils

    • dom
    • props
  • General

    • General
    • Tracking
  • Components

    • Accordion
    • ActionButton
    • AssetItem
    • AssetList
    • BackButton
    • ConfigGenButton
    • Logo
    • Media
    • Modal
    • Popup
    • Prompt
    • SPButton
    • SPRouterView
    • SPTrackedRouterLink
    • TextLoader
    • View
  • Composables

    • useConnectionStatus
  • Stores

    • useAppsDbStore
    • useBreadcrumbStore
    • useShowpadAPIStore
    • useShowpadSDKStore
    • useSpConfigStore
    • useSpStore
    • useSpTrackingStore
  • The New Kit

    • General
    • Installation & Usage
    • ACF Blocks
    • PHPCS
    • Functions
    • Vite
    • WP Config
    • Staging Deployment
  • Best Practices

    • Page Structure
    • Fonts/Typography
  • Todo
GitHub
  • Blender Utils

Blender Utils

This file contains utility functions for converting Blender-exported GLTF data to match Three.js rendering behavior. It addresses light intensity mismatches, decay settings, and spot light cone angle validation.

Blender exports 3D assets with lighting information, but the unit systems and calculations differ from Three.js. These utilities automatically handle the conversion so your scene renders consistently between Blender and Three.js.

Key Conversion Issues Addressed

  1. Light Intensity Units: Blender exports intensity in watts, while Three.js expects candela or lumens/lux
  2. Light Decay: GLTF spec requires inverse square law, which must be explicitly set in Three.js
  3. Spot Light Cones: GLTF cone angles must be translated to Three.js angle and penumbra values

Note

  • Color temperature is already handled by Blender during export (converted to RGB), so no conversion is needed
  • Original values are always stored in the light's userData object when preserveOriginal is true, allowing you to revert changes if needed
  • Custom conversion factors can be useful if you've exported lights with non-standard intensity values

Usage Example: Complete Workflow

Here's a typical workflow for loading and adjusting a Blender scene:

import { adjustGltfLightIntensities } from '@/utils/BlenderUtils';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

const loader = new GLTFLoader();
const scene = new THREE.Scene();

loader.load('my-blender-scene.glb', (gltf) => {
    // Add the model to the scene
    scene.add(gltf.scene);
    
    // Convert all lights to match Three.js rendering
    const adjustments = adjustGltfLightIntensities(gltf.scene);
    
    console.log(`Successfully adjusted ${adjustments.length} lights`);
    
    // Your scene is now ready to render!
    renderer.render(scene, camera);
});

GetDefaultConversionFactor

Returns the appropriate intensity conversion factor for a given light type.

Conversion factors are based on standard photometric conversions: 1 W/m² = 683 lumens/m² (lux) at 555nm wavelength. Directional and spot lights use a more conservative factor of 100 for sunlight-like sources, while point, ambient, hemisphere, and rectangular area lights use the standard 683 factor.

Parameters

  • lightType: Light type as a string (e.g., 'directional', 'point') or a THREE.Light instance

Returns

A number representing the conversion factor for that light type

import { getDefaultConversionFactor } from '@/utils/BlenderUtils';

// Using a light object
const factor = getDefaultConversionFactor(directionalLight);  // returns 100

// Using a string type name
const factor = getDefaultConversionFactor('point');  // returns 683

ConvertBlenderLightIntensity

Converts the intensity value of a single light from Blender's watt units to Three.js compatible units.

Blender exports intensity in watts, but Three.js interprets it differently depending on whether physically correct lighting is enabled. This function applies the appropriate conversion factor and stores the original value for reference.

Parameters

  • light: THREE.Light object to convert
  • options: Object with optional configuration
    • conversionFactor: Custom conversion factor (overrides the default for this light type)
    • usePhysicallyCorrect: Boolean indicating if physically correct lighting is enabled (default: false)
    • preserveOriginal: Whether to store the original intensity in the light's userData (default: true)

Returns

The adjusted intensity value

import { convertBlenderLightIntensity } from '@/utils/BlenderUtils';

// Convert a directional light with default factor
const adjusted = convertBlenderLightIntensity(directionalLight);
directionalLight.intensity = adjusted;

// Use a custom conversion factor
const adjusted = convertBlenderLightIntensity(spotLight, { 
    conversionFactor: 120 
});
spotLight.intensity = adjusted;

// Access the original intensity later
console.log(spotLight.userData._blenderOriginalIntensity);

EnsureCorrectDecay

Sets the decay property to 2 (inverse square law) for point and spot lights, as required by the GLTF specification.

The GLTF spec mandates inverse square law attenuation for point and spot lights. Three.js uses a decay property where 0 = constant, 1 = linear, and 2 = inverse square. This function ensures the correct value is set.

Parameters

  • light: THREE.PointLight or THREE.SpotLight object to adjust
  • preserveOriginal: Whether to store the original decay value in userData (default: true)

Returns

Void (modifies the light in place)

import { ensureCorrectDecay } from '@/utils/BlenderUtils';

ensureCorrectDecay(pointLight);
// pointLight.decay is now set to 2

// Access the original decay value if needed
console.log(pointLight.userData._blenderOriginalDecay);

VerifySpotLightCones

Validates spot light cone angles and clamps them to valid ranges.

GLTF uses innerConeAngle and outerConeAngle, which Three.js converts to angle (outer cone) and penumbra (softness). This function verifies these values are valid: angle must be between 0 and PI/2, and penumbra must be between 0 and 1.

Parameters

  • light: THREE.SpotLight object to verify

Returns

Void (modifies the light in place if invalid values are found)

import { verifySpotLightCones } from '@/utils/BlenderUtils';

verifySpotLightCones(spotLight);
// Logs a warning if angle or penumbra are out of range
// Automatically clamps them to valid ranges

ConvertBlenderLight

Convenience function that applies all conversions to a single light at once.

This is the recommended function for converting individual lights. It handles intensity conversion, decay enforcement, and spot light cone verification in a single call.

Parameters

  • light: THREE.Light object to convert
  • options: Object with optional configuration (same as convertBlenderLightIntensity)
    • conversionFactor: Custom conversion factor
    • usePhysicallyCorrect: Whether physically correct lighting is enabled
    • preserveOriginal: Whether to store original values in userData

Returns

An object containing the conversion results:

  • intensity: The adjusted intensity value
  • decay: The decay property (for point/spot lights)
  • angle: The angle property (for spot lights)
  • penumbra: The penumbra property (for spot lights)
import { convertBlenderLight } from '@/utils/BlenderUtils';

const result = convertBlenderLight(spotLight, {
    usePhysicallyCorrect: false,
    preserveOriginal: true
});

console.log(result);
// {
//   intensity: 0.5,
//   decay: 2,
//   angle: 0.7854,
//   penumbra: 0.5
// }

ApplyBlenderShadowDefaults

Applies Blender-style shadow defaults to a GLTF scene or subtree after import.

In Blender, every mesh both casts and receives shadows by default, and shadow casting is toggled per light. GLTF export does not preserve this behavior 1:1. applyBlenderShadowDefaults restores these defaults on your loaded Three.js scene.

Parameters

  • object3d: THREE.Object3D – Root object to traverse (for example gltf.scene or a block root).

Behavior

  • Traverses the entire subtree under object3d.
  • For every mesh (node.isMesh === true):
    • Sets node.castShadow = true
    • Sets node.receiveShadow = true
  • For every light (node.isLight === true) with node.userData.castShadow === true:
    • Sets node.castShadow = true
  • If object3d is falsy, it is a no-op.
  • Mutates the scene graph in place (it is intentionally not a pure function).

Usage example

import {
    adjustGltfLightIntensities,
    applyBlenderShadowDefaults,
} from '@/utils/BlenderUtils';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

const loader = new GLTFLoader();
const scene = new THREE.Scene();

loader.load('my-blender-scene.glb', (gltf) => {
    // Normalise light intensities and decay to match Three.js expectations
    const adjustments = adjustGltfLightIntensities(gltf.scene, {
        usePhysicallyCorrect: false,
        preserveOriginal: true,
    });
    console.log(`Adjusted ${adjustments.length} lights`);

    // Restore Blender-like shadow behavior on meshes and lights
    applyBlenderShadowDefaults(gltf.scene);

    scene.add(gltf.scene);
    renderer.render(scene, camera);
});

AdjustGltfLightIntensities

Batch processes all lights in a GLTF scene, applying all necessary conversions to each one.

This is the main function to call after loading a GLTF file exported from Blender. It traverses the scene, finds all lights, and applies intensity conversion, decay enforcement, and cone angle verification to each.

Parameters

  • gltfScene: THREE.Scene or THREE.Object3D (root object from GLTFLoader)
  • options: Object with optional configuration (same as convertBlenderLightIntensity)
    • conversionFactor: Custom conversion factor for all lights
    • usePhysicallyCorrect: Whether physically correct lighting is enabled
    • preserveOriginal: Whether to store original values in userData

Returns

An array of adjustment info objects, one for each light found. Each object contains:

  • light: The THREE.Light object
  • name: The light's name (or '(unnamed)')
  • type: The light's type (e.g., 'DirectionalLight')
  • originalIntensity: The intensity before conversion
  • adjustedIntensity: The intensity after conversion
  • conversionFactor: The factor that was applied
import { adjustGltfLightIntensities } from '@/utils/BlenderUtils';

// After loading a GLTF file
const gltfLoader = new GLTFLoader();
gltfLoader.load('scene.glb', (gltf) => {
    // Adjust all lights in the scene
    const adjustments = adjustGltfLightIntensities(gltf.scene, {
        usePhysicallyCorrect: false,
        preserveOriginal: true
    });
    
    console.log(`Adjusted ${adjustments.length} lights`);
    
    // Log details about each adjustment
    adjustments.forEach(adj => {
        console.log(`${adj.name}: ${adj.originalIntensity} → ${adj.adjustedIntensity} (factor: ${adj.conversionFactor})`);
    });
});
Edit this page
Last Updated: 4/27/26, 12:56 PM
Contributors: Nicolas Jaenen