Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added alpha texture support #124

Merged
merged 2 commits into from
Jan 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
* Fixed output name when running from the command line. [#126](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/126)

### 2.1.0 2017-12-28
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion bin/obj2gltf.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,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);

Expand Down Expand Up @@ -139,7 +142,8 @@ var overridingTextures = {
occlusionTexture : argv.occlusionTexture,
normalTexture : argv.normalTexture,
baseColorTexture : argv.baseColorTexture,
emissiveTexture : argv.emissiveTexture
emissiveTexture : argv.emissiveTexture,
alphaTexture : argv.alphaTexture
};

var options = {
Expand Down
115 changes: 104 additions & 11 deletions lib/loadMtl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 : {
Expand All @@ -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();
Expand All @@ -73,6 +77,7 @@ function loadMtl(mtlPath, options) {
material.ambientTexture = overridingAmbientTexture;
material.normalTexture = overridingNormalTexture;
material.emissiveTexture = overridingEmissiveTexture;
material.alphaTexture = overridingAlphaTexture;
materials.push(material);
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding a function that writes RGB at once? That way you can read the channels and then just use a single writeChannels function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the purpose of doing it this way was so we could use the same scratchChannel and only need to read a single channel of the texture at a time.


// 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better if this line was in in the else part. The cost here is reading the entire channel twice vs using an else and in this case, the else would be better.

Alternately, you can have a shortcut exit from the getTextureChannel function that returns if the channel number is beyond the number of channels available.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the alpha channel before the red channel is probably safer. There could be a case where someone sets the diffuse slot and alpha slot to the same texture, in which case getting the transparency from the red channel would be a mistake.

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;
Expand Down Expand Up @@ -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;
Expand All @@ -524,10 +602,15 @@ function createSpecularGlossinessMaterial(material, options) {
glossinessFactor = 1.0;
}

var alpha = material.alpha;
diffuseFactor[3] = alpha;
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;
}
Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -589,10 +674,15 @@ function createMetallicRoughnessMaterial(material, options) {
roughnessFactor = 1.0;
}

var alpha = material.alpha;
baseColorFactor[3] = alpha;
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;
}
Expand All @@ -603,7 +693,7 @@ function createMetallicRoughnessMaterial(material, options) {
return {
name : material.name,
pbrMetallicRoughness : {
baseColorTexture : baseColorTexture,
baseColorTexture : diffuseAlphaTexture,
metallicRoughnessTexture : metallicRoughnessTexture,
baseColorFactor : baseColorFactor,
metallicFactor : metallicFactor,
Expand Down Expand Up @@ -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);

Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions lib/obj2gltf.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Binary file added specs/data/box-complex-material-alpha/ambient.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 added specs/data/box-complex-material-alpha/bump.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added specs/data/box-complex-material-alpha/diffuse.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion specs/data/box-complex-material/box-complex-material.mtl
Original file line number Diff line number Diff line change
Expand Up @@ -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 removed specs/data/box-texture-options/alpha.png
Binary file not shown.
1 change: 0 additions & 1 deletion specs/data/box-texture-options/box-texture-options.mtl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading