Skip to content

Commit

Permalink
Merge pull request #470 from ux3d/mikktspace-tangents
Browse files Browse the repository at this point in the history
Generate tangents with MikkTSpace
  • Loading branch information
UX3D-kanzler authored Jul 4, 2023
2 parents 47a1919 + eba3903 commit 6e096a3
Show file tree
Hide file tree
Showing 4 changed files with 507 additions and 4 deletions.
12 changes: 9 additions & 3 deletions app_web/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ import { Observable, Subject, from, merge } from 'rxjs';
import { mergeMap, filter, map, multicast } from 'rxjs/operators';
import { gltfModelPathProvider, fillEnvironmentWithPaths } from './model_path_provider.js';

async function main()
{

import mikktspaceWasm from '../../source/libs/mikktspace_module.js';

let mikktspace = null;

async function main() {
mikktspace = await mikktspaceWasm("libs/mikktspace_module_bg.wasm");

const canvas = document.getElementById("canvas");
const context = canvas.getContext("webgl2", { alpha: false, antialias: true });
const ui = document.getElementById("app");
Expand Down Expand Up @@ -363,4 +369,4 @@ async function main()
window.requestAnimationFrame(update);
}

export { main };
export { main, mikktspace };
139 changes: 138 additions & 1 deletion source/gltf/primitive.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { initGlForMembers } from './utils.js';
import { GltfObject } from './gltf_object.js';
import { gltfBuffer } from './buffer.js';
import { gltfAccessor } from './accessor.js';
import { gltfImage } from './image.js';
import { ImageMimeType } from './image_mime_type.js';
import { gltfTexture } from './texture.js';
Expand All @@ -9,13 +10,14 @@ import { gltfSampler } from './sampler.js';
import { gltfBufferView } from './buffer_view.js';
import { DracoDecoder } from '../ResourceLoader/draco.js';
import { GL } from '../Renderer/webgl.js';
import { mikktspace } from '../../app_web/src/main.js';

class gltfPrimitive extends GltfObject
{
constructor()
{
super();
this.attributes = [];
this.attributes = {};
this.targets = [];
this.indices = undefined;
this.material = undefined;
Expand Down Expand Up @@ -53,6 +55,7 @@ class gltfPrimitive extends GltfObject

if (this.extensions !== undefined)
{
// Decode Draco compressed mesh:
if (this.extensions.KHR_draco_mesh_compression !== undefined)
{
const dracoDecoder = new DracoDecoder();
Expand All @@ -69,6 +72,15 @@ class gltfPrimitive extends GltfObject
}
}

if (this.attributes.TANGENT === undefined)
{
console.info("Generating tangents using the MikkTSpace algorithm.");
console.time("Tangent generation");
this.unweld(gltf);
this.generateTangents(gltf);
console.timeEnd("Tangent generation");
}

// VERTEX ATTRIBUTES
for (const attribute of Object.keys(this.attributes))
{
Expand Down Expand Up @@ -722,6 +734,131 @@ class gltfPrimitive extends GltfObject
};

}

/**
* Unwelds this primitive, i.e. applies the index mapping.
* This is required for generating tangents using the MikkTSpace algorithm,
* because the same vertex might be mapped to different tangents.
* @param {*} gltf The glTF document.
*/
unweld(gltf) {
// Unwelding is an idempotent operation.
if (this.indices === undefined) {
return;
}

const indices = gltf.accessors[this.indices].getTypedView(gltf);

// Unweld attributes:
for (const [attribute, accessorIndex] of Object.entries(this.attributes)) {
this.attributes[attribute] = this.unweldAccessor(gltf, gltf.accessors[accessorIndex], indices);
}

// Unweld morph targets:
for (const target of this.targets) {
for (const [attribute, accessorIndex] of Object.entries(target)) {
target[attribute] = this.unweldAccessor(gltf, gltf.accessors[accessorIndex], indices);
}
}

// Dipose the indices:
this.indices = undefined;
}

/**
* Unwelds a single accessor. Used by {@link unweld}.
* @param {*} gltf The glTF document.
* @param {*} accessor The accessor to unweld.
* @param {*} typedIndexView A typed view of the indices.
* @returns A new accessor index containing the unwelded attribute.
*/
unweldAccessor(gltf, accessor, typedIndexView) {
const stride = accessor.getComponentCount(accessor.type);

const weldedAttribute = accessor.getTypedView(gltf);
const unweldedAttribute = new Float32Array(gltf.accessors[this.indices].count * stride);

// Apply the index mapping.
for (let i = 0; i < typedIndexView.length; i++) {
for (let j = 0; j < stride; j++) {
unweldedAttribute[i * stride + j] = weldedAttribute[typedIndexView[i] * stride + j];
}
}

// Create a new buffer and buffer view for the unwelded attribute:
const unweldedBuffer = new gltfBuffer();
unweldedBuffer.byteLength = unweldedAttribute.byteLength;
unweldedBuffer.buffer = unweldedAttribute.buffer;
gltf.buffers.push(unweldedBuffer);

const unweldedBufferView = new gltfBufferView();
unweldedBufferView.buffer = gltf.buffers.length - 1;
unweldedBufferView.byteLength = unweldedAttribute.byteLength;
unweldedBufferView.target = GL.ARRAY_BUFFER;
gltf.bufferViews.push(unweldedBufferView);

// Create a new accessor for the unwelded attribute:
const unweldedAccessor = new gltfAccessor();
unweldedAccessor.bufferView = gltf.bufferViews.length - 1;
unweldedAccessor.byteOffset = 0;
unweldedAccessor.count = typedIndexView.length;
unweldedAccessor.type = accessor.type;
unweldedAccessor.componentType = accessor.componentType;
unweldedAccessor.min = gltf.accessors[this.attributes.POSITION].min;
unweldedAccessor.max = gltf.accessors[this.attributes.POSITION].max;
gltf.accessors.push(unweldedAccessor);

// Update the primitive to use the unwelded attribute:
return gltf.accessors.length - 1;
}

generateTangents(gltf) {
const positions = gltf.accessors[this.attributes.POSITION].getTypedView(gltf);
const normals = gltf.accessors[this.attributes.NORMAL].getTypedView(gltf);
const texcoords = gltf.accessors[this.attributes.TEXCOORD_0].getTypedView(gltf);
for (let i = 0; i < positions.length; i += 3) {
mikktspace.writeFace(
positions[i], positions[i + 1], positions[i + 2],
normals[i], normals[i + 1], normals[i + 2],
texcoords[i], texcoords[i + 1]
);
}

mikktspace.generateTangents();

const tangents = new Float32Array(4 * positions.length / 3);
for (let i = 0; i < tangents.length; i++) {
let t = mikktspace.readTangent(i);
tangents[i] = t;
}

// Create a new buffer and buffer view for the tangents:
const tangentBuffer = new gltfBuffer();
tangentBuffer.byteLength = tangents.byteLength;
tangentBuffer.buffer = tangents.buffer;
gltf.buffers.push(tangentBuffer);

const tangentBufferView = new gltfBufferView();
tangentBufferView.buffer = gltf.buffers.length - 1;
tangentBufferView.byteLength = tangents.byteLength;
tangentBufferView.target = GL.ARRAY_BUFFER;
gltf.bufferViews.push(tangentBufferView);

// Create a new accessor for the tangents:
const tangentAccessor = new gltfAccessor();
tangentAccessor.bufferView = gltf.bufferViews.length - 1;
tangentAccessor.byteOffset = 0;
tangentAccessor.count = tangents.length / 4;
tangentAccessor.type = "VEC4";
tangentAccessor.componentType = GL.FLOAT;

// Update the primitive to use the tangents:
this.attributes.TANGENT = gltf.accessors.length;
gltf.accessors.push(tangentAccessor);

// Update the primitive to use the tangents:
this.attributes.TANGENT = gltf.accessors.length - 1;
}
}

export { gltfPrimitive };
Expand Down
Loading

0 comments on commit 6e096a3

Please sign in to comment.