From 81b27db4c1254e38abb70369ccd6c5bc6838b2df Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Tue, 31 Jan 2023 13:23:46 -0500 Subject: [PATCH] DRACOLoader, DRACOExporter: Assign color space to vertex colors. --- editor/js/Loader.js | 2 +- examples/jsm/exporters/DRACOExporter.js | 43 +++++++++++++++- examples/jsm/loaders/DRACOLoader.js | 66 +++++++++++++++++++++---- 3 files changed, 98 insertions(+), 13 deletions(-) diff --git a/editor/js/Loader.js b/editor/js/Loader.js index 6f7f4cc414b39d..df0389449287ae 100644 --- a/editor/js/Loader.js +++ b/editor/js/Loader.js @@ -200,7 +200,7 @@ function Loader( editor ) { const loader = new DRACOLoader(); loader.setDecoderPath( '../examples/jsm/libs/draco/' ); - loader.decodeDracoFile( contents, function ( geometry ) { + loader.parse( contents, function ( geometry ) { let object; diff --git a/examples/jsm/exporters/DRACOExporter.js b/examples/jsm/exporters/DRACOExporter.js index c214bbdbfc1442..85a2e15adc4d6d 100644 --- a/examples/jsm/exporters/DRACOExporter.js +++ b/examples/jsm/exporters/DRACOExporter.js @@ -1,3 +1,5 @@ +import { Color } from 'three'; + /** * Export draco compressed files from threejs geometry objects. * @@ -100,7 +102,9 @@ class DRACOExporter { if ( colors !== undefined ) { - builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array ); + const array = createVertexColorSRGBArray( colors ); + + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); } @@ -120,7 +124,9 @@ class DRACOExporter { if ( colors !== undefined ) { - builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array ); + const array = createVertexColorSRGBArray( colors ); + + builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); } @@ -206,6 +212,39 @@ class DRACOExporter { } +function createVertexColorSRGBArray( attribute ) { + + // While .drc files do not specify colorspace, the only 'official' tooling + // is PLY and OBJ converters, which use sRGB. We'll assume sRGB is expected + // for .drc files, but note that Draco buffers embedded in glTF files will + // be Linear-sRGB instead. + + const _color = new Color(); + + const count = attribute.count; + const itemSize = attribute.itemSize; + const array = new Float32Array( count * itemSize ); + + for ( let i = 0, il = count; i < il; i ++ ) { + + _color.fromBufferAttribute( attribute, i ).convertLinearToSRGB(); + + array[ i * itemSize ] = _color.r; + array[ i * itemSize + 1 ] = _color.g; + array[ i * itemSize + 2 ] = _color.b; + + if ( itemSize === 4 ) { + + array[ i * itemSize + 3 ] = attribute.getW( i ); + + } + + } + + return array; + +} + // Encoder methods DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1; diff --git a/examples/jsm/loaders/DRACOLoader.js b/examples/jsm/loaders/DRACOLoader.js index 08ecfd6680c9b6..b92da783fb3bd7 100644 --- a/examples/jsm/loaders/DRACOLoader.js +++ b/examples/jsm/loaders/DRACOLoader.js @@ -1,8 +1,11 @@ import { BufferAttribute, BufferGeometry, + Color, FileLoader, - Loader + Loader, + LinearSRGBColorSpace, + SRGBColorSpace } from 'three'; const _taskCache = new WeakMap(); @@ -73,18 +76,25 @@ class DRACOLoader extends Loader { loader.load( url, ( buffer ) => { - this.decodeDracoFile( buffer, onLoad ).catch( onError ); + this.parse( buffer, onLoad, onError ); }, onProgress, onError ); } - decodeDracoFile( buffer, callback, attributeIDs, attributeTypes ) { + parse ( buffer, onLoad, onError ) { + + this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace ).catch( onError ); + + } + + decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace ) { const taskConfig = { attributeIDs: attributeIDs || this.defaultAttributeIDs, attributeTypes: attributeTypes || this.defaultAttributeTypes, - useUniqueIDs: !! attributeIDs + useUniqueIDs: !! attributeIDs, + vertexColorSpace: vertexColorSpace, }; return this.decodeGeometry( buffer, taskConfig ).then( callback ); @@ -188,12 +198,20 @@ class DRACOLoader extends Loader { for ( let i = 0; i < geometryData.attributes.length; i ++ ) { - const attribute = geometryData.attributes[ i ]; - const name = attribute.name; - const array = attribute.array; - const itemSize = attribute.itemSize; + const result = geometryData.attributes[ i ]; + const name = result.name; + const array = result.array; + const itemSize = result.itemSize; + + const attribute = new BufferAttribute( array, itemSize ); + + if ( name === 'color' ) { - geometry.setAttribute( name, new BufferAttribute( array, itemSize ) ); + this._assignVertexColorSpace( attribute, result.vertexColorSpace ); + + } + + geometry.setAttribute( name, attribute ); } @@ -201,6 +219,26 @@ class DRACOLoader extends Loader { } + _assignVertexColorSpace( attribute, inputColorSpace ) { + + // While .drc files do not specify colorspace, the only 'official' tooling + // is PLY and OBJ converters, which use sRGB. We'll assume sRGB when a .drc + // file is passed into .load() or .parse(). GLTFLoader uses internal APIs + // to decode geometry, and vertex colors are already Linear-sRGB in there. + + if ( inputColorSpace !== SRGBColorSpace ) return; + + const _color = new Color(); + + for ( let i = 0, il = attribute.count; i < il; i ++ ) { + + _color.fromBufferAttribute( attribute, i ).convertSRGBToLinear(); + attribute.setXYZ( i, _color.r, _color.g, _color.b ); + + } + + } + _loadLibrary( url, responseType ) { const loader = new FileLoader( this.manager ); @@ -493,7 +531,15 @@ function DRACOWorker() { } - geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) ); + const attributeResult = decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ); + + if ( attributeName === 'color' ) { + + attributeResult.vertexColorSpace = taskConfig.vertexColorSpace; + + } + + geometry.attributes.push( attributeResult ); }