mirror of
https://github.com/CesiumGS/obj2gltf.git
synced 2025-02-18 16:43:52 -05:00
Async image reading/writing and other cleanup
This commit is contained in:
parent
125edddef9
commit
e54f3af37f
12
README.md
12
README.md
@ -45,12 +45,12 @@ Using obj2gltf as a command-line tool:
|
|||||||
|`--metallicRoughness`|The values in the mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots.|No, default `false`|
|
|`--metallicRoughness`|The values in the mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots.|No, default `false`|
|
||||||
|`--specularGlossiness`|The values in the mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the `KHR_materials_pbrSpecularGlossiness` extension.|No, default `false`|
|
|`--specularGlossiness`|The values in the mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the `KHR_materials_pbrSpecularGlossiness` extension.|No, default `false`|
|
||||||
|`--materialsCommon`|The glTF will be saved with the KHR_materials_common extension.|No, default `false`|
|
|`--materialsCommon`|The glTF will be saved with the KHR_materials_common extension.|No, default `false`|
|
||||||
|`--metallicRoughnessOcclusionTexture`|Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a pbrMetallicRoughness material.
|
|`--metallicRoughnessOcclusionTexture`|Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a pbrMetallicRoughness material.
|
||||||
|`--specularGlossinessTexture`|Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.
|
|`--specularGlossinessTexture`|Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.
|
||||||
|`--occlusionTexture`|Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file. Ignored if metallicRoughnessOcclusionTexture is also set.
|
|`--occlusionTexture`|Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. Ignored if metallicRoughnessOcclusionTexture is also set.
|
||||||
|`--normalTexture`|Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file.
|
|`--normalTexture`|Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|
||||||
|`--baseColorTexture`|Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file.
|
|`--baseColorTexture`|Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|
||||||
|`--emissiveTexture`|Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file.
|
|`--emissiveTexture`|Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|
||||||
|
|
||||||
## Build Instructions
|
## Build Instructions
|
||||||
|
|
||||||
|
@ -91,32 +91,32 @@ var argv = yargs
|
|||||||
default: defaults.materialsCommon
|
default: defaults.materialsCommon
|
||||||
},
|
},
|
||||||
metallicRoughnessOcclusionTexture : {
|
metallicRoughnessOcclusionTexture : {
|
||||||
describe: 'Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a pbrMetallicRoughness material.',
|
describe: 'Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a pbrMetallicRoughness material.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
normalize: true
|
normalize: true
|
||||||
},
|
},
|
||||||
specularGlossinessTexture : {
|
specularGlossinessTexture : {
|
||||||
describe: 'Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.',
|
describe: 'Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
normalize: true
|
normalize: true
|
||||||
},
|
},
|
||||||
occlusionTexture : {
|
occlusionTexture : {
|
||||||
describe: 'Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file. Ignored if metallicRoughnessOcclusionTexture is also set.',
|
describe: 'Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. Ignored if metallicRoughnessOcclusionTexture is also set.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
normalize: true
|
normalize: true
|
||||||
},
|
},
|
||||||
normalTexture : {
|
normalTexture : {
|
||||||
describe: 'Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file.',
|
describe: 'Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
normalize: true
|
normalize: true
|
||||||
},
|
},
|
||||||
baseColorTexture : {
|
baseColorTexture : {
|
||||||
describe: 'Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file.',
|
describe: 'Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
normalize: true
|
normalize: true
|
||||||
},
|
},
|
||||||
emissiveTexture : {
|
emissiveTexture : {
|
||||||
describe: 'Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file.',
|
describe: 'Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
normalize: true
|
normalize: true
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
var Cesium = require('cesium');
|
var Cesium = require('cesium');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var PNG = require('pngjs').PNG;
|
|
||||||
var getBufferPadded = require('./getBufferPadded');
|
var getBufferPadded = require('./getBufferPadded');
|
||||||
var Material = require('./Material');
|
var Material = require('./Material');
|
||||||
|
|
||||||
@ -22,12 +21,12 @@ module.exports = createGltf;
|
|||||||
* @param {Boolean} options.specularGlossiness The values in the mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the KHR_materials_pbrSpecularGlossiness extension.
|
* @param {Boolean} options.specularGlossiness The values in the mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the KHR_materials_pbrSpecularGlossiness extension.
|
||||||
* @param {Boolean} options.materialsCommon The glTF will be saved with the KHR_materials_common extension.
|
* @param {Boolean} options.materialsCommon The glTF will be saved with the KHR_materials_common extension.
|
||||||
* @param {Object[]} options.overridingImages An array of images that override images in the .mtl file.
|
* @param {Object[]} options.overridingImages An array of images that override images in the .mtl file.
|
||||||
* @param {String} [options.metallicRoughnessOcclusionTexture] Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a pbrMetallicRoughness material.
|
* @param {String} [options.metallicRoughnessOcclusionTexture] Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a pbrMetallicRoughness material.
|
||||||
* @param {String} [options.specularGlossinessTexture] Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.
|
* @param {String} [options.specularGlossinessTexture] Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.
|
||||||
* @param {String} [options.occlusionTexture] Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file. Ignored if metallicRoughnessOcclusionTexture is also set.
|
* @param {String} [options.occlusionTexture] Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. Ignored if metallicRoughnessOcclusionTexture is also set.
|
||||||
* @param {String} [options.normalTexture] Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file.
|
* @param {String} [options.normalTexture] Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|
||||||
* @param {String} [options.baseColorTexture] Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file.
|
* @param {String} [options.baseColorTexture] Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|
||||||
* @param {String} [options.emissiveTexture] Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file.
|
* @param {String} [options.emissiveTexture] Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|
||||||
* @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log.
|
* @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log.
|
||||||
* @returns {Object} A glTF asset.
|
* @returns {Object} A glTF asset.
|
||||||
*
|
*
|
||||||
@ -177,7 +176,7 @@ function getImageName(image) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTextureName(image) {
|
function getTextureName(image) {
|
||||||
return getImageName(image);
|
return getImageName(image) + '_texture';
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTexture(gltf, image) {
|
function addTexture(gltf, image) {
|
||||||
@ -189,10 +188,7 @@ function addTexture(gltf, image) {
|
|||||||
gltf.images.push({
|
gltf.images.push({
|
||||||
name : imageName,
|
name : imageName,
|
||||||
extras : {
|
extras : {
|
||||||
_obj2gltf : {
|
_obj2gltf : image
|
||||||
source : image.source,
|
|
||||||
extension : image.extension
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -224,6 +220,7 @@ function getTexture(gltf, image) {
|
|||||||
if (!defined(textureIndex)) {
|
if (!defined(textureIndex)) {
|
||||||
textureIndex = addTexture(gltf, image);
|
textureIndex = addTexture(gltf, image);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
index : textureIndex
|
index : textureIndex
|
||||||
};
|
};
|
||||||
@ -247,9 +244,8 @@ function getEmissiveFactor(material) {
|
|||||||
return addColors(ambientColor, emissiveColor);
|
return addColors(ambientColor, emissiveColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetWidth, targetHeight) {
|
function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetPixels, targetWidth, targetHeight) {
|
||||||
// Nearest neighbor sampling
|
// Nearest neighbor sampling
|
||||||
var targetPixels = Buffer.alloc(targetWidth * targetHeight);
|
|
||||||
var widthRatio = sourceWidth / targetWidth;
|
var widthRatio = sourceWidth / targetWidth;
|
||||||
var heightRatio = sourceHeight / targetHeight;
|
var heightRatio = sourceHeight / targetHeight;
|
||||||
|
|
||||||
@ -266,24 +262,38 @@ function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetWidth, tar
|
|||||||
return targetPixels;
|
return targetPixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageChannel(image, index, targetWidth, targetHeight) {
|
var scratchResizeChannel;
|
||||||
|
|
||||||
|
function getImageChannel(image, index, targetWidth, targetHeight, targetChannel) {
|
||||||
var pixels = image.decoded; // RGBA
|
var pixels = image.decoded; // RGBA
|
||||||
var width = image.width;
|
var sourceWidth = image.width;
|
||||||
var height = image.height;
|
var sourceHeight = image.height;
|
||||||
var pixelsLength = width * height;
|
var sourcePixelsLength = sourceWidth * sourceHeight;
|
||||||
var channel = Buffer.alloc(pixelsLength);
|
var targetPixelsLength = targetWidth * targetHeight;
|
||||||
for (var i = 0; i < pixelsLength; ++i) {
|
|
||||||
|
// Allocate the scratchResizeChannel on demand if the texture needs to be resized
|
||||||
|
var sourceChannel = targetChannel;
|
||||||
|
if (sourcePixelsLength > targetPixelsLength) {
|
||||||
|
if (!defined(scratchResizeChannel) || (sourcePixelsLength > scratchResizeChannel.length)) {
|
||||||
|
scratchResizeChannel = Buffer.alloc(sourcePixelsLength);
|
||||||
|
}
|
||||||
|
sourceChannel = scratchResizeChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < sourcePixelsLength; ++i) {
|
||||||
var value = pixels.readUInt8(i * 4 + index);
|
var value = pixels.readUInt8(i * 4 + index);
|
||||||
channel.writeUInt8(value, i);
|
sourceChannel.writeUInt8(value, i);
|
||||||
}
|
}
|
||||||
if (width !== targetWidth || height !== targetHeight) {
|
|
||||||
channel = resizeChannel(channel, width, height, targetWidth, targetHeight);
|
if (sourcePixelsLength > targetPixelsLength) {
|
||||||
|
resizeChannel(sourceChannel, sourceWidth, sourceHeight, targetChannel, targetWidth, targetHeight);
|
||||||
}
|
}
|
||||||
return channel;
|
|
||||||
|
return targetChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeChannel(pixels, channel, index, width, height) {
|
function writeChannel(pixels, channel, index) {
|
||||||
var pixelsLength = width * height;
|
var pixelsLength = pixels.length / 4;
|
||||||
for (var i = 0; i < pixelsLength; ++i) {
|
for (var i = 0; i < pixelsLength; ++i) {
|
||||||
var value = channel.readUInt8(i);
|
var value = channel.readUInt8(i);
|
||||||
pixels.writeUInt8(value, i * 4 + index);
|
pixels.writeUInt8(value, i * 4 + index);
|
||||||
@ -313,32 +323,6 @@ function getMinimumDimensions(images, options) {
|
|||||||
return [width, height];
|
return [width, height];
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodePng(pixels, width, height, inputChannels, outputChannels) {
|
|
||||||
var pngInput = {
|
|
||||||
data : pixels,
|
|
||||||
width : width,
|
|
||||||
height : height
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constants defined by pngjs
|
|
||||||
var rgbColorType = 2;
|
|
||||||
var rgbaColorType = 6;
|
|
||||||
|
|
||||||
var colorType = outputChannels === 4 ? rgbaColorType : rgbColorType;
|
|
||||||
var inputColorType = inputChannels === 4 ? rgbaColorType : rgbColorType;
|
|
||||||
var inputHasAlpha = inputChannels === 4;
|
|
||||||
|
|
||||||
var pngOptions = {
|
|
||||||
width : width,
|
|
||||||
height : height,
|
|
||||||
colorType : colorType,
|
|
||||||
inputColorType : inputColorType,
|
|
||||||
inputHasAlpha : inputHasAlpha
|
|
||||||
};
|
|
||||||
|
|
||||||
return PNG.sync.write(pngInput, pngOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occlusionImage, options) {
|
function createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occlusionImage, options) {
|
||||||
var packMetallic = defined(metallicImage);
|
var packMetallic = defined(metallicImage);
|
||||||
var packRoughness = defined(roughnessImage);
|
var packRoughness = defined(roughnessImage);
|
||||||
@ -372,40 +356,41 @@ function createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occ
|
|||||||
var height = dimensions[1];
|
var height = dimensions[1];
|
||||||
var pixelsLength = width * height;
|
var pixelsLength = width * height;
|
||||||
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
|
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
|
||||||
|
var scratchChannel = Buffer.alloc(pixelsLength);
|
||||||
|
|
||||||
if (packMetallic) {
|
if (packMetallic) {
|
||||||
// Write into the B channel
|
// Write into the B channel
|
||||||
var metallicChannel = getImageChannel(metallicImage, 0, width, height);
|
var metallicChannel = getImageChannel(metallicImage, 0, width, height, scratchChannel);
|
||||||
writeChannel(pixels, metallicChannel, 2, width, height);
|
writeChannel(pixels, metallicChannel, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packRoughness) {
|
if (packRoughness) {
|
||||||
// Write into the G channel
|
// Write into the G channel
|
||||||
var roughnessChannel = getImageChannel(roughnessImage, 0, width, height);
|
var roughnessChannel = getImageChannel(roughnessImage, 0, width, height, scratchChannel);
|
||||||
writeChannel(pixels, roughnessChannel, 1, width, height);
|
writeChannel(pixels, roughnessChannel, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packOcclusion) {
|
if (packOcclusion) {
|
||||||
// Write into the R channel
|
// Write into the R channel
|
||||||
var occlusionChannel = getImageChannel(occlusionImage, 0, width, height);
|
var occlusionChannel = getImageChannel(occlusionImage, 0, width, height, scratchChannel);
|
||||||
writeChannel(pixels, occlusionChannel, 0, width, height);
|
writeChannel(pixels, occlusionChannel, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
var length = packedImages.length;
|
var length = packedImages.length;
|
||||||
var imageNames = new Array(length);
|
var imageNames = new Array(length);
|
||||||
for (var i = 0; i < length; ++i) {
|
for (var i = 0; i < length; ++i) {
|
||||||
var imagePath = packedImages[i].path;
|
imageNames[i] = getImageName(packedImages[i]);
|
||||||
imageNames[i] = path.basename(imagePath, path.extname(imagePath));
|
|
||||||
}
|
}
|
||||||
var imageName = imageNames.join('_');
|
var imageName = imageNames.join('_');
|
||||||
|
|
||||||
var pngSource = encodePng(pixels, width, height, 4, 3);
|
|
||||||
|
|
||||||
var image = {
|
var image = {
|
||||||
transparent : false,
|
transparent : false,
|
||||||
source : pngSource,
|
source : undefined,
|
||||||
|
extension : '.png',
|
||||||
path : imageName,
|
path : imageName,
|
||||||
extension : '.png'
|
decoded : pixels,
|
||||||
|
width : width,
|
||||||
|
height : height
|
||||||
};
|
};
|
||||||
|
|
||||||
return getTexture(gltf, image);
|
return getTexture(gltf, image);
|
||||||
@ -438,38 +423,39 @@ function createSpecularGlossinessTexture(gltf, specularImage, glossinessImage, o
|
|||||||
var height = dimensions[1];
|
var height = dimensions[1];
|
||||||
var pixelsLength = width * height;
|
var pixelsLength = width * height;
|
||||||
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
|
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
|
||||||
|
var scratchChannel = Buffer.alloc(pixelsLength);
|
||||||
|
|
||||||
if (packSpecular) {
|
if (packSpecular) {
|
||||||
// Write into the R, G, B channels
|
// Write into the R, G, B channels
|
||||||
var redChannel = getImageChannel(specularImage, 0, width, height);
|
var redChannel = getImageChannel(specularImage, 0, width, height, scratchChannel);
|
||||||
var greenChannel = getImageChannel(specularImage, 1, width, height);
|
var greenChannel = getImageChannel(specularImage, 1, width, height, scratchChannel);
|
||||||
var blueChannel = getImageChannel(specularImage, 2, width, height);
|
var blueChannel = getImageChannel(specularImage, 2, width, height, scratchChannel);
|
||||||
writeChannel(pixels, redChannel, 0, width, height);
|
writeChannel(pixels, redChannel, 0);
|
||||||
writeChannel(pixels, greenChannel, 1, width, height);
|
writeChannel(pixels, greenChannel, 1);
|
||||||
writeChannel(pixels, blueChannel, 2, width, height);
|
writeChannel(pixels, blueChannel, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packGlossiness) {
|
if (packGlossiness) {
|
||||||
// Write into the A channel
|
// Write into the A channel
|
||||||
var glossinessChannel = getImageChannel(glossinessImage, 0, width, height);
|
var glossinessChannel = getImageChannel(glossinessImage, 0, width, height, scratchChannel);
|
||||||
writeChannel(pixels, glossinessChannel, 3, width, height);
|
writeChannel(pixels, glossinessChannel, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
var length = packedImages.length;
|
var length = packedImages.length;
|
||||||
var imageNames = new Array(length);
|
var imageNames = new Array(length);
|
||||||
for (var i = 0; i < length; ++i) {
|
for (var i = 0; i < length; ++i) {
|
||||||
var imagePath = packedImages[i].path;
|
imageNames[i] = getImageName(packedImages[i]);
|
||||||
imageNames[i] = path.basename(imagePath, path.extname(imagePath));
|
|
||||||
}
|
}
|
||||||
var imageName = imageNames.join('_');
|
var imageName = imageNames.join('_');
|
||||||
|
|
||||||
var pngSource = encodePng(pixels, width, height, 4, 4);
|
|
||||||
|
|
||||||
var image = {
|
var image = {
|
||||||
transparent : false,
|
transparent : true,
|
||||||
source : pngSource,
|
source : undefined,
|
||||||
|
extension : '.png',
|
||||||
path : imageName,
|
path : imageName,
|
||||||
extension : '.png'
|
decoded : pixels,
|
||||||
|
width : width,
|
||||||
|
height : height
|
||||||
};
|
};
|
||||||
|
|
||||||
return getTexture(gltf, image);
|
return getTexture(gltf, image);
|
||||||
@ -628,12 +614,16 @@ function createMetallicRoughnessMaterial(gltf, images, material, options) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function luminance(color) {
|
||||||
|
return color[0] * 0.2125 + color[1] * 0.7154 + color[2] * 0.0721;
|
||||||
|
}
|
||||||
|
|
||||||
function convertTraditionalToMetallicRoughness(material) {
|
function convertTraditionalToMetallicRoughness(material) {
|
||||||
// Translate the blinn-phong model to the pbr metallic-roughness model
|
// Translate the blinn-phong model to the pbr metallic-roughness model
|
||||||
// Roughness factor is a combination of specular intensity and shininess
|
// Roughness factor is a combination of specular intensity and shininess
|
||||||
// Metallic factor is 0.0
|
// Metallic factor is 0.0
|
||||||
// This does not convert textures
|
// This does not convert textures
|
||||||
var specularIntensity = material.specularColor[0];
|
var specularIntensity = luminance(material.specularColor);
|
||||||
var specularShininess = material.specularShininess;
|
var specularShininess = material.specularShininess;
|
||||||
|
|
||||||
// Transform from 0-1000 range to 0-1 range. Then invert.
|
// Transform from 0-1000 range to 0-1 range. Then invert.
|
||||||
|
@ -9,6 +9,8 @@ module.exports = gltfToGlb;
|
|||||||
/**
|
/**
|
||||||
* Convert a glTF to binary glTF.
|
* Convert a glTF to binary glTF.
|
||||||
*
|
*
|
||||||
|
* The glTF is expected to have all resources embedded as bufferViews and a single buffer whose content is stored in a data uri.
|
||||||
|
*
|
||||||
* @param {Object} gltf A javascript object containing a glTF asset.
|
* @param {Object} gltf A javascript object containing a glTF asset.
|
||||||
* @returns {Promise} A promise that resolves to a buffer containing the binary glTF.
|
* @returns {Promise} A promise that resolves to a buffer containing the binary glTF.
|
||||||
*
|
*
|
||||||
|
@ -4,8 +4,10 @@ var fsExtra = require('fs-extra');
|
|||||||
var jpeg = require('jpeg-js');
|
var jpeg = require('jpeg-js');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var PNG = require('pngjs').PNG;
|
var PNG = require('pngjs').PNG;
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
|
||||||
var defaultValue = Cesium.defaultValue;
|
var defaultValue = Cesium.defaultValue;
|
||||||
|
var defined = Cesium.defined;
|
||||||
|
|
||||||
module.exports = loadImage;
|
module.exports = loadImage;
|
||||||
|
|
||||||
@ -75,6 +77,18 @@ function getChannels(colorType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parsePng(data) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
new PNG().parse(data, function(error, decodedResults) {
|
||||||
|
if (defined(error)) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(decodedResults);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getPngInfo(data, info, options) {
|
function getPngInfo(data, info, options) {
|
||||||
// Color type is encoded in the 25th bit of the png
|
// Color type is encoded in the 25th bit of the png
|
||||||
var colorType = data[25];
|
var colorType = data[25];
|
||||||
@ -84,13 +98,16 @@ function getPngInfo(data, info, options) {
|
|||||||
var decode = options.decode || checkTransparency;
|
var decode = options.decode || checkTransparency;
|
||||||
|
|
||||||
if (decode) {
|
if (decode) {
|
||||||
var decodedResults = PNG.sync.read(data);
|
return parsePng(data)
|
||||||
info.decoded = decodedResults.data;
|
.then(function(decodedResults) {
|
||||||
info.width = decodedResults.width;
|
info.decoded = decodedResults.data;
|
||||||
info.height = decodedResults.height;
|
info.width = decodedResults.width;
|
||||||
if (checkTransparency) {
|
info.height = decodedResults.height;
|
||||||
info.transparent = hasTransparency(info);
|
if (checkTransparency) {
|
||||||
}
|
info.transparent = hasTransparency(info);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -368,9 +368,7 @@ function loadImages(imagesOptions, objPath, options) {
|
|||||||
logger('Could not read image file at ' + imagePath + '. Material will ignore this image.');
|
logger('Could not read image file at ' + imagePath + '. Material will ignore this image.');
|
||||||
});
|
});
|
||||||
}, {concurrency : 10})
|
}, {concurrency : 10})
|
||||||
.then(function() {
|
.thenReturn(images);
|
||||||
return images;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImagesOptions(materials, options) {
|
function getImagesOptions(materials, options) {
|
||||||
|
@ -32,12 +32,12 @@ module.exports = obj2gltf;
|
|||||||
* @param {Boolean} [options.metallicRoughness=false] The values in the mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots.
|
* @param {Boolean} [options.metallicRoughness=false] The values in the mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots.
|
||||||
* @param {Boolean} [options.specularGlossiness=false] The values in the mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the KHR_materials_pbrSpecularGlossiness extension.
|
* @param {Boolean} [options.specularGlossiness=false] The values in the mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the KHR_materials_pbrSpecularGlossiness extension.
|
||||||
* @param {Boolean} [options.materialsCommon=false] The glTF will be saved with the KHR_materials_common extension.
|
* @param {Boolean} [options.materialsCommon=false] The glTF will be saved with the KHR_materials_common extension.
|
||||||
* @param {String} [options.metallicRoughnessOcclusionTexture] Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a pbrMetallicRoughness material.
|
* @param {String} [options.metallicRoughnessOcclusionTexture] Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a pbrMetallicRoughness material.
|
||||||
* @param {String} [options.specularGlossinessTexture] Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.
|
* @param {String} [options.specularGlossinessTexture] Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.
|
||||||
* @param {String} [options.occlusionTexture] Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file. Ignored if metallicRoughnessOcclusionTexture is also set.
|
* @param {String} [options.occlusionTexture] Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. Ignored if metallicRoughnessOcclusionTexture is also set.
|
||||||
* @param {String} [options.normalTexture] Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file.
|
* @param {String} [options.normalTexture] Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|
||||||
* @param {String} [options.baseColorTexture] Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file.
|
* @param {String} [options.baseColorTexture] Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|
||||||
* @param {String} [options.emissiveTexture] Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file.
|
* @param {String} [options.emissiveTexture] Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|
||||||
|
|
||||||
* @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log.
|
* @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log.
|
||||||
* @return {Promise} A promise that resolves when the glTF file is saved.
|
* @return {Promise} A promise that resolves when the glTF file is saved.
|
||||||
@ -108,10 +108,6 @@ function obj2gltf(objPath, gltfPath, options) {
|
|||||||
|
|
||||||
gltfPath = path.join(path.dirname(gltfPath), modelName + extension);
|
gltfPath = path.join(path.dirname(gltfPath), modelName + extension);
|
||||||
|
|
||||||
var jsonOptions = {
|
|
||||||
spaces : 2
|
|
||||||
};
|
|
||||||
|
|
||||||
return loadOverridingImages(options)
|
return loadOverridingImages(options)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return loadObj(objPath, options);
|
return loadObj(objPath, options);
|
||||||
@ -127,6 +123,9 @@ function obj2gltf(objPath, gltfPath, options) {
|
|||||||
var glb = gltfToGlb(gltf);
|
var glb = gltfToGlb(gltf);
|
||||||
return fsExtra.outputFile(gltfPath, glb);
|
return fsExtra.outputFile(gltfPath, glb);
|
||||||
}
|
}
|
||||||
|
var jsonOptions = {
|
||||||
|
spaces : 2
|
||||||
|
};
|
||||||
return fsExtra.outputJson(gltfPath, gltf, jsonOptions);
|
return fsExtra.outputJson(gltfPath, gltf, jsonOptions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
133
lib/writeUris.js
133
lib/writeUris.js
@ -3,6 +3,7 @@ var Cesium = require('cesium');
|
|||||||
var fsExtra = require('fs-extra');
|
var fsExtra = require('fs-extra');
|
||||||
var mime = require('mime');
|
var mime = require('mime');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
var PNG = require('pngjs').PNG;
|
||||||
var Promise = require('bluebird');
|
var Promise = require('bluebird');
|
||||||
var getBufferPadded = require('./getBufferPadded');
|
var getBufferPadded = require('./getBufferPadded');
|
||||||
|
|
||||||
@ -24,50 +25,102 @@ module.exports = writeUris;
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function writeUris(gltf, gltfPath, options) {
|
function writeUris(gltf, gltfPath, options) {
|
||||||
var separate = options.separate;
|
return encodeImages(gltf)
|
||||||
var separateTextures = options.separateTextures;
|
|
||||||
|
|
||||||
var promises = [];
|
|
||||||
|
|
||||||
var buffer = gltf.buffers[0];
|
|
||||||
var bufferByteLength = buffer.extras._obj2gltf.source.length;
|
|
||||||
|
|
||||||
var texturesByteLength = 0;
|
|
||||||
var images = gltf.images;
|
|
||||||
var imagesLength = images.length;
|
|
||||||
for (var i = 0; i < imagesLength; ++i) {
|
|
||||||
texturesByteLength += images[i].extras._obj2gltf.source.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266
|
|
||||||
var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580);
|
|
||||||
|
|
||||||
if (exceedsMaximum && !separate) {
|
|
||||||
return Promise.reject(new RuntimeError('Buffers and textures are too large to encode in the glTF. Use the --separate flag instead.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = path.basename(gltfPath, path.extname(gltfPath));
|
|
||||||
|
|
||||||
if (separateTextures) {
|
|
||||||
promises.push(writeSeparateTextures(gltf, gltfPath));
|
|
||||||
} else {
|
|
||||||
writeEmbeddedTextures(gltf);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (separate) {
|
|
||||||
promises.push(writeSeparateBuffer(gltf, gltfPath, name));
|
|
||||||
} else {
|
|
||||||
writeEmbeddedBuffer(gltf);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises)
|
|
||||||
.then(function() {
|
.then(function() {
|
||||||
deleteExtras(gltf);
|
var separate = options.separate;
|
||||||
cleanup(gltf);
|
var separateTextures = options.separateTextures;
|
||||||
return gltf;
|
|
||||||
|
var buffer = gltf.buffers[0];
|
||||||
|
var bufferByteLength = buffer.extras._obj2gltf.source.length;
|
||||||
|
|
||||||
|
var texturesByteLength = 0;
|
||||||
|
var images = gltf.images;
|
||||||
|
var imagesLength = images.length;
|
||||||
|
for (var i = 0; i < imagesLength; ++i) {
|
||||||
|
texturesByteLength += images[i].extras._obj2gltf.source.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266
|
||||||
|
var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580);
|
||||||
|
|
||||||
|
if (exceedsMaximum && !separate) {
|
||||||
|
return Promise.reject(new RuntimeError('Buffers and textures are too large to encode in the glTF. Use the --separate flag instead.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = path.basename(gltfPath, path.extname(gltfPath));
|
||||||
|
|
||||||
|
var promises = [];
|
||||||
|
if (separateTextures) {
|
||||||
|
promises.push(writeSeparateTextures(gltf, gltfPath));
|
||||||
|
} else {
|
||||||
|
writeEmbeddedTextures(gltf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (separate) {
|
||||||
|
promises.push(writeSeparateBuffer(gltf, gltfPath, name));
|
||||||
|
} else {
|
||||||
|
writeEmbeddedBuffer(gltf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(function() {
|
||||||
|
deleteExtras(gltf);
|
||||||
|
cleanup(gltf);
|
||||||
|
return gltf;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function encodePng(image) {
|
||||||
|
// Constants defined by pngjs
|
||||||
|
var rgbColorType = 2;
|
||||||
|
var rgbaColorType = 6;
|
||||||
|
|
||||||
|
var png = new PNG({
|
||||||
|
width : image.width,
|
||||||
|
height : image.height,
|
||||||
|
colorType : image.transparent ? rgbaColorType : rgbColorType,
|
||||||
|
inputColorType : rgbaColorType,
|
||||||
|
inputHasAlpha : true
|
||||||
|
});
|
||||||
|
|
||||||
|
png.data = image.decoded;
|
||||||
|
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var chunks = [];
|
||||||
|
var stream = png.pack();
|
||||||
|
stream.on('data', function(chunk) {
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
stream.on('end', function() {
|
||||||
|
resolve(Buffer.concat(chunks));
|
||||||
|
});
|
||||||
|
stream.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeImage(image) {
|
||||||
|
var imageExtras = image.extras._obj2gltf;
|
||||||
|
if (!defined(imageExtras.source) && defined(imageExtras.decoded) && imageExtras.extension === '.png') {
|
||||||
|
return encodePng(imageExtras)
|
||||||
|
.then(function(encoded) {
|
||||||
|
imageExtras.source = encoded;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeImages(gltf) {
|
||||||
|
// Dynamically generated metallicRoughnessOcclusion and specularGlossiness
|
||||||
|
// textures need to be encoded to png's prior to being saved.
|
||||||
|
var encodePromises = [];
|
||||||
|
var images = gltf.images;
|
||||||
|
var length = images.length;
|
||||||
|
for (var i = 0; i < length; ++i) {
|
||||||
|
encodePromises.push(encodeImage(images[i]));
|
||||||
|
}
|
||||||
|
return Promise.all(encodePromises);
|
||||||
|
}
|
||||||
|
|
||||||
function deleteExtras(gltf) {
|
function deleteExtras(gltf) {
|
||||||
var buffer = gltf.buffers[0];
|
var buffer = gltf.buffers[0];
|
||||||
delete buffer.extras;
|
delete buffer.extras;
|
||||||
|
@ -92,6 +92,8 @@ describe('createGltf', function() {
|
|||||||
var gltf = createGltf(boxObjData, defaultOptions);
|
var gltf = createGltf(boxObjData, defaultOptions);
|
||||||
|
|
||||||
expect(gltf.materials.length).toBe(1);
|
expect(gltf.materials.length).toBe(1);
|
||||||
|
expect(gltf.scene).toBe(0);
|
||||||
|
expect(gltf.scenes[0].nodes[0]).toBe(0);
|
||||||
expect(gltf.nodes.length).toBe(1);
|
expect(gltf.nodes.length).toBe(1);
|
||||||
expect(gltf.meshes.length).toBe(1);
|
expect(gltf.meshes.length).toBe(1);
|
||||||
|
|
||||||
@ -114,6 +116,8 @@ describe('createGltf', function() {
|
|||||||
var gltf = createGltf(groupObjData, defaultOptions);
|
var gltf = createGltf(groupObjData, defaultOptions);
|
||||||
|
|
||||||
expect(gltf.materials.length).toBe(3);
|
expect(gltf.materials.length).toBe(3);
|
||||||
|
expect(gltf.scene).toBe(0);
|
||||||
|
expect(gltf.scenes[0].nodes[0]).toBe(0);
|
||||||
expect(gltf.nodes.length).toBe(4);
|
expect(gltf.nodes.length).toBe(4);
|
||||||
expect(gltf.nodes[0].mesh).toBeUndefined();
|
expect(gltf.nodes[0].mesh).toBeUndefined();
|
||||||
expect(gltf.nodes[0].children.length).toBe(3);
|
expect(gltf.nodes[0].children.length).toBe(3);
|
||||||
@ -320,7 +324,7 @@ describe('createGltf', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('specularGlosiness', function() {
|
describe('specularGlossiness', function() {
|
||||||
it('sets default material values for specularGlossiness', function() {
|
it('sets default material values for specularGlossiness', function() {
|
||||||
var options = clone(defaultOptions);
|
var options = clone(defaultOptions);
|
||||||
options.specularGlossiness = true;
|
options.specularGlossiness = true;
|
||||||
@ -440,7 +444,7 @@ describe('createGltf', function() {
|
|||||||
expect(kmc.values.doubleSided).toBe(false);
|
expect(kmc.values.doubleSided).toBe(false);
|
||||||
|
|
||||||
expect(texture).toEqual({
|
expect(texture).toEqual({
|
||||||
name : 'cesium',
|
name : 'cesium_texture',
|
||||||
sampler : 0,
|
sampler : 0,
|
||||||
source : 0
|
source : 0
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user