Added alpha texture support

This commit is contained in:
Sean Lilley 2018-01-03 20:41:25 -05:00
parent b017f67c7a
commit 378479b8f1
20 changed files with 266 additions and 20 deletions

View File

@ -3,7 +3,8 @@ Change Log
### 2.2.0 ???
* Fixed handling of `usemtl` when appearing before an `o` or `g` token. [#121](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/121)
* Added ability to load alpha textures. [#124](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/124)
* Fixed handling of `usemtl` when appearing before an `o` or `g` token. [#123](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/123)
### 2.1.0 2017-12-28

View File

@ -112,6 +112,7 @@ map_Bump | normal texture | normal texture | normal texture
|`--normalTexture`|Path to the normal texture that should override textures in the .mtl file.|No|
|`--baseColorTexture`|Path to the baseColor/diffuse texture that should override textures in the .mtl file.|No|
|`--emissiveTexture`|Path to the emissive texture that should override textures in the .mtl file.|No|
|`--alphaTexture`|Path to the alpha texture that should override textures in the .mtl file.|No|
## Build Instructions

View File

@ -108,6 +108,9 @@ var argv = yargs
describe : 'Path to the emissive texture that should override textures in the .mtl file.',
type : 'string',
normalize : true
},
alphaTexture : {
describe : 'Path to the alpha texture that should override textures in the .mtl file.'
}
}).parse(args);
@ -143,7 +146,8 @@ var overridingTextures = {
occlusionTexture : argv.occlusionTexture,
normalTexture : argv.normalTexture,
baseColorTexture : argv.baseColorTexture,
emissiveTexture : argv.emissiveTexture
emissiveTexture : argv.emissiveTexture,
alphaTexture : argv.alphaTexture
};
var options = {

View File

@ -47,6 +47,7 @@ function loadMtl(mtlPath, options) {
var overridingNormalTexture = overridingTextures.normalTexture;
var overridingDiffuseTexture = overridingTextures.baseColorTexture;
var overridingEmissiveTexture = overridingTextures.emissiveTexture;
var overridingAlphaTexture = overridingTextures.alphaTexture;
// Textures that are packed into PBR textures need to be decoded first
var decodeOptions = options.materialsCommon ? undefined : {
@ -62,6 +63,9 @@ function loadMtl(mtlPath, options) {
var specularShinessTextureOptions = defined(overridingSpecularShininessTexture) ? undefined : decodeOptions;
var emissiveTextureOptions;
var normalTextureOptions;
var alphaTextureOptions = {
decode : true
};
function createMaterial(name) {
material = new Material();
@ -73,6 +77,7 @@ function loadMtl(mtlPath, options) {
material.ambientTexture = overridingAmbientTexture;
material.normalTexture = overridingNormalTexture;
material.emissiveTexture = overridingEmissiveTexture;
material.alphaTexture = overridingAlphaTexture;
materials.push(material);
}
@ -161,16 +166,24 @@ function loadMtl(mtlPath, options) {
if (!defined(overridingNormalTexture)) {
material.normalTexture = path.resolve(mtlDirectory, cleanTextureName(line.substring(9).trim()));
}
} else if (/^map_d /i.test(line)) {
if (!defined(overridingAlphaTexture)) {
material.alphaTexture = path.resolve(mtlDirectory, cleanTextureName(line.substring(6).trim()));
}
}
}
function loadMaterialTextures(material) {
loadMaterialTexture(material, 'diffuseTexture', diffuseTextureOptions, mtlDirectory, texturePromiseMap, texturePromises, options);
// If an alpha texture is present the diffuse texture needs to be decoded so they can be packed together
var diffuseAlphaTextureOptions = defined(material.alphaTexture) ? alphaTextureOptions : diffuseTextureOptions;
loadMaterialTexture(material, 'diffuseTexture', diffuseAlphaTextureOptions, mtlDirectory, texturePromiseMap, texturePromises, options);
loadMaterialTexture(material, 'ambientTexture', ambientTextureOptions, mtlDirectory, texturePromiseMap, texturePromises, options);
loadMaterialTexture(material, 'emissiveTexture', emissiveTextureOptions, mtlDirectory, texturePromiseMap, texturePromises, options);
loadMaterialTexture(material, 'specularTexture', specularTextureOptions, mtlDirectory, texturePromiseMap, texturePromises, options);
loadMaterialTexture(material, 'specularShininessTexture', specularShinessTextureOptions, mtlDirectory, texturePromiseMap, texturePromises, options);
loadMaterialTexture(material, 'normalTexture', normalTextureOptions, mtlDirectory, texturePromiseMap, texturePromises, options);
loadMaterialTexture(material, 'alphaTexture', alphaTextureOptions, mtlDirectory, texturePromiseMap, texturePromises, options);
}
return readLines(mtlPath, parseLine)
@ -205,6 +218,7 @@ function Material() {
this.specularTexture = undefined; // map_Ks
this.specularShininessTexture = undefined; // map_Ns
this.normalTexture = undefined; // map_Bump
this.alphaTexture = undefined; // map_d
}
loadMtl.getDefaultMaterial = function(options) {
@ -354,6 +368,68 @@ function getMinimumDimensions(textures, options) {
return [width, height];
}
function isChannelSingleColor(buffer) {
var first = buffer.readUInt8(0);
var length = buffer.length;
for (var i = 1; i < length; ++i) {
if (buffer[i] !== first) {
return false;
}
}
return true;
}
function createDiffuseAlphaTexture(diffuseTexture, alphaTexture, options) {
var packDiffuse = defined(diffuseTexture);
var packAlpha = defined(alphaTexture);
if (!packDiffuse) {
return undefined;
}
if (!packAlpha) {
return diffuseTexture;
}
if (!defined(diffuseTexture.pixels) || !defined(alphaTexture.pixels)) {
options.logger('Could not get decoded texture data for ' + diffuseTexture.path + ' or ' + alphaTexture.path + '. The material will be created without an alpha texture.');
return diffuseTexture;
}
var packedTextures = [diffuseTexture, alphaTexture];
var dimensions = getMinimumDimensions(packedTextures, options);
var width = dimensions[0];
var height = dimensions[1];
var pixelsLength = width * height;
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels
var scratchChannel = Buffer.alloc(pixelsLength);
// Write into the R, G, B channels
var redChannel = getTextureChannel(diffuseTexture, 0, width, height, scratchChannel);
writeChannel(pixels, redChannel, 0);
var greenChannel = getTextureChannel(diffuseTexture, 1, width, height, scratchChannel);
writeChannel(pixels, greenChannel, 1);
var blueChannel = getTextureChannel(diffuseTexture, 2, width, height, scratchChannel);
writeChannel(pixels, blueChannel, 2);
// First try reading the alpha component from the alpha channel, but if it is a single color read from the red channel instead.
var alphaChannel = getTextureChannel(alphaTexture, 3, width, height, scratchChannel);
if (isChannelSingleColor(alphaChannel)) {
alphaChannel = getTextureChannel(alphaTexture, 0, width, height, scratchChannel);
}
writeChannel(pixels, alphaChannel, 3);
var texture = new Texture();
texture.name = diffuseTexture.name;
texture.extension = '.png';
texture.pixels = pixels;
texture.width = width;
texture.height = height;
texture.transparent = true;
return texture;
}
function createMetallicRoughnessTexture(metallicTexture, roughnessTexture, occlusionTexture, options) {
if (defined(options.overridingTextures.metallicRoughnessOcclusionTexture)) {
return metallicTexture;
@ -499,9 +575,11 @@ function createSpecularGlossinessMaterial(material, options) {
var normalTexture = material.normalTexture;
var occlusionTexture = material.ambientTexture;
var diffuseTexture = material.diffuseTexture;
var alphaTexture = material.alphaTexture;
var specularTexture = material.specularTexture;
var glossinessTexture = material.specularShininessTexture;
var specularGlossinessTexture = createSpecularGlossinessTexture(specularTexture, glossinessTexture, options);
var diffuseAlphaTexture = createDiffuseAlphaTexture(diffuseTexture, alphaTexture, options);
var emissiveFactor = material.emissiveColor.slice(0, 3);
var diffuseFactor = material.diffuseColor;
@ -524,10 +602,15 @@ function createSpecularGlossinessMaterial(material, options) {
glossinessFactor = 1.0;
}
var transparent = false;
if (defined(alphaTexture)) {
transparent = true;
} else {
var alpha = material.alpha;
diffuseFactor[3] = alpha;
transparent = alpha < 1.0;
}
var transparent = alpha < 1.0;
if (defined(diffuseTexture)) {
transparent = transparent || diffuseTexture.transparent;
}
@ -539,7 +622,7 @@ function createSpecularGlossinessMaterial(material, options) {
name : material.name,
extensions : {
KHR_materials_pbrSpecularGlossiness: {
diffuseTexture : diffuseTexture,
diffuseTexture : diffuseAlphaTexture,
specularGlossinessTexture : specularGlossinessTexture,
diffuseFactor : diffuseFactor,
specularFactor : specularFactor,
@ -560,9 +643,11 @@ function createMetallicRoughnessMaterial(material, options) {
var normalTexture = material.normalTexture;
var occlusionTexture = material.ambientTexture;
var baseColorTexture = material.diffuseTexture;
var alphaTexture = material.alphaTexture;
var metallicTexture = material.specularTexture;
var roughnessTexture = material.specularShininessTexture;
var metallicRoughnessTexture = createMetallicRoughnessTexture(metallicTexture, roughnessTexture, occlusionTexture, options);
var diffuseAlphaTexture = createDiffuseAlphaTexture(baseColorTexture, alphaTexture, options);
if (options.packOcclusion) {
occlusionTexture = metallicRoughnessTexture;
@ -589,10 +674,15 @@ function createMetallicRoughnessMaterial(material, options) {
roughnessFactor = 1.0;
}
var transparent = false;
if (defined(alphaTexture)) {
transparent = true;
} else {
var alpha = material.alpha;
baseColorFactor[3] = alpha;
transparent = alpha < 1.0;
}
var transparent = alpha < 1.0;
if (defined(baseColorTexture)) {
transparent = transparent || baseColorTexture.transparent;
}
@ -603,7 +693,7 @@ function createMetallicRoughnessMaterial(material, options) {
return {
name : material.name,
pbrMetallicRoughness : {
baseColorTexture : baseColorTexture,
baseColorTexture : diffuseAlphaTexture,
metallicRoughnessTexture : metallicRoughnessTexture,
baseColorFactor : baseColorFactor,
metallicFactor : metallicFactor,
@ -647,8 +737,9 @@ function convertTraditionalToMetallicRoughness(material) {
}
function createMaterialsCommonMaterial(material, options) {
var diffuseAlphaTexture = createDiffuseAlphaTexture(material.diffuseTexture, material.alphaTexture, options);
var ambient = defaultValue(material.ambientTexture, material.ambientColor);
var diffuse = defaultValue(material.diffuseTexture, material.diffuseColor);
var diffuse = defaultValue(diffuseAlphaTexture, material.diffuseColor);
var emission = defaultValue(material.emissiveTexture, material.emissiveColor);
var specular = defaultValue(material.specularTexture, material.specularColor);
@ -658,7 +749,9 @@ function createMaterialsCommonMaterial(material, options) {
var transparent;
var transparency = 1.0;
if (defined(material.diffuseTexture)) {
if (defined(material.alphaTexture)) {
transparent = true;
} else if (defined(material.diffuseTexture)) {
transparency = alpha;
transparent = material.diffuseTexture.transparent || (transparency < 1.0);
} else {

View File

@ -33,6 +33,7 @@ module.exports = obj2gltf;
* @param {String} [options.overridingTextures.normalTexture] Path to the normal texture.
* @param {String} [options.overridingTextures.baseColorTexture] Path to the baseColor/diffuse texture.
* @param {String} [options.overridingTextures.emissiveTexture] Path to the emissive texture.
* @param {String} [options.overridingTextures.alphaTexture] Path to the alpha texture.
* @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log.
* @param {Writer} [options.writer] A callback function that writes files that are saved as separate resources.
* @param {String} [options.outputDirectory] Output directory for writing separate resources when options.writer is not defined.

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,20 @@
# Blender MTL File: 'None'
# Material Count: 1
newmtl Material
Ns 96.078431
Ka 0.200000 0.200000 0.200000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.100000 0.100000 0.100000
Ni 1.000000
d 0.900000
Tr 0.100000
map_Ka ambient.gif
map_Ke emission.jpg
map_Kd diffuse.png
map_Ks specular.jpeg
map_Ns shininess.png
map_Bump bump.png
map_d alpha.png
illum 2

View File

@ -0,0 +1,46 @@
# Blender v2.78 (sub 0) OBJ File: ''
# www.blender.org
mtllib box-complex-material-alpha.mtl
o Cube
v -1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 0.0000
vt 0.0000 1.0000
vn -1.0000 0.0000 0.0000
vn 0.0000 0.0000 -1.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 1.0000
vn 0.0000 -1.0000 0.0000
vn 0.0000 1.0000 0.0000
usemtl Material
s off
f 1/1/1 2/2/1 4/3/1 3/4/1
f 3/5/2 4/6/2 8/7/2 7/8/2
f 7/9/3 8/10/3 6/11/3 5/12/3
f 5/13/4 6/14/4 2/15/4 1/16/4
f 3/5/5 7/17/5 5/18/5 1/16/5
f 8/19/6 4/6/6 2/15/6 6/20/6

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -16,5 +16,4 @@ map_Kd diffuse.png
map_Ks specular.jpeg
map_Ns shininess.png
map_Bump bump.png
map_d alpha.png
illum 2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -16,5 +16,4 @@ map_Kd -s 1.0 1.0 1.0 -o 0.0 0.0 0.0 diffuse.png
map_Ks -s 1.0 1.0 1.0 -o 0.0 0.0 0.0 specular.jpeg
map_Ns -s 1.0 1.0 1.0 -o 0.0 0.0 0.0 shininess.png
map_Bump -bm 0.2 bump.png
map_d alpha.png
illum 2

View File

@ -20,7 +20,7 @@ var transparentMaterialPath = 'specs/data/box-transparent/box-transparent.mtl';
var diffuseTexturePath = 'specs/data/box-textured/cesium.png';
var transparentDiffuseTexturePath = 'specs/data/box-complex-material/diffuse.png';
var alphaTexturePath = 'specs/data/box-complex-material/alpha.png';
var alphaTexturePath = 'specs/data/box-complex-material-alpha/alpha.png';
var ambientTexturePath = 'specs/data/box-complex-material/ambient.gif';
var normalTexturePath = 'specs/data/box-complex-material/bump.png';
var emissiveTexturePath = 'specs/data/box-complex-material/emission.jpg';
@ -48,7 +48,7 @@ var options;
describe('loadMtl', function() {
beforeAll(function(done) {
return Promise.all([
loadTexture(diffuseTexturePath)
loadTexture(diffuseTexturePath, decodeOptions)
.then(function(texture) {
diffuseTexture = texture;
}),
@ -333,6 +333,33 @@ describe('loadMtl', function() {
expect(material.alphaMode).toBe('BLEND');
expect(material.doubleSided).toBe(true);
});
it('packs alpha texture in base color texture', function() {
options.metallicRoughness = true;
var material = loadMtl._createMaterial({
diffuseTexture : diffuseTexture,
alphaTexture : alphaTexture
}, options);
var pbr = material.pbrMetallicRoughness;
expect(pbr.baseColorTexture).toBeDefined();
var hasBlack = false;
var hasWhite = false;
var pixels = pbr.baseColorTexture.pixels;
var pixelsLength = pixels.length / 4;
for (var i = 0; i < pixelsLength; ++i) {
var alpha = pixels[i * 4 + 3];
hasBlack = hasBlack || (alpha === 0);
hasWhite = hasWhite || (alpha === 255);
}
expect(hasBlack).toBe(true);
expect(hasWhite).toBe(true);
expect(pbr.baseColorFactor[3]).toEqual(1);
expect(material.alphaMode).toBe('BLEND');
expect(material.doubleSided).toBe(true);
});
});
describe('specularGlossiness', function() {
@ -401,6 +428,33 @@ describe('loadMtl', function() {
expect(material.alphaMode).toBe('BLEND');
expect(material.doubleSided).toBe(true);
});
it('packs alpha texture in diffuse texture', function() {
options.specularGlossiness = true;
var material = loadMtl._createMaterial({
diffuseTexture : diffuseTexture,
alphaTexture : alphaTexture
}, options);
var pbr = material.extensions.KHR_materials_pbrSpecularGlossiness;
expect(pbr.diffuseTexture).toBeDefined();
var hasBlack = false;
var hasWhite = false;
var pixels = pbr.diffuseTexture.pixels;
var pixelsLength = pixels.length / 4;
for (var i = 0; i < pixelsLength; ++i) {
var alpha = pixels[i * 4 + 3];
hasBlack = hasBlack || (alpha === 0);
hasWhite = hasWhite || (alpha === 255);
}
expect(hasBlack).toBe(true);
expect(hasWhite).toBe(true);
expect(pbr.diffuseFactor[3]).toEqual(1);
expect(material.alphaMode).toBe('BLEND');
expect(material.doubleSided).toBe(true);
});
});
describe('materialsCommon', function() {
@ -535,5 +589,31 @@ describe('loadMtl', function() {
var values = material.extensions.KHR_materials_common.values;
expect(values.ambient).toEqual([0.0, 0.0, 0.0, 1.0]);
});
it('packs alpha texture in diffuse texture', function() {
options.materialsCommon = true;
var material = loadMtl._createMaterial({
diffuseTexture : diffuseTexture,
alphaTexture : alphaTexture
}, options);
var values = material.extensions.KHR_materials_common.values;
var hasBlack = false;
var hasWhite = false;
var pixels = values.diffuse.pixels;
var pixelsLength = pixels.length / 4;
for (var i = 0; i < pixelsLength; ++i) {
var alpha = pixels[i * 4 + 3];
hasBlack = hasBlack || (alpha === 0);
hasWhite = hasWhite || (alpha === 255);
}
expect(hasBlack).toBe(true);
expect(hasWhite).toBe(true);
expect(values.transparency).toBe(1.0);
expect(values.transparent).toBe(true);
expect(values.doubleSided).toBe(true);
});
});
});

View File

@ -5,7 +5,7 @@ var pngTexturePath = 'specs/data/box-complex-material/shininess.png';
var jpgTexturePath = 'specs/data/box-complex-material/emission.jpg';
var jpegTexturePath = 'specs/data/box-complex-material/specular.jpeg';
var gifTexturePath = 'specs/data/box-complex-material/ambient.gif';
var grayscaleTexturePath = 'specs/data/box-complex-material/alpha.png';
var grayscaleTexturePath = 'specs/data/box-complex-material-alpha/alpha.png';
var transparentTexturePath = 'specs/data/box-complex-material/diffuse.png';
describe('loadTexture', function() {

View File

@ -78,7 +78,8 @@ describe('obj2gltf', function() {
metallicRoughnessOcclusionTexture : textureUrl,
normalTexture : textureUrl,
baseColorTexture : textureUrl,
emissiveTexture : textureUrl
emissiveTexture : textureUrl,
alphaTexture : textureUrl
},
separateTextures : true,
outputDirectory : outputDirectory
@ -100,7 +101,8 @@ describe('obj2gltf', function() {
occlusionTexture : textureUrl,
normalTexture : textureUrl,
baseColorTexture : textureUrl,
emissiveTexture : textureUrl
emissiveTexture : textureUrl,
alphaTexture : textureUrl
},
separateTextures : true,
outputDirectory : outputDirectory