Skip to content

Commit

Permalink
Merge pull request #481 from ux3d/mikktspace-tangents
Browse files Browse the repository at this point in the history
Mikktspace tangents
  • Loading branch information
UX3D-kanzler authored Sep 5, 2023
2 parents bb0ec49 + f30421b commit cd72885
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 11 deletions.
17 changes: 9 additions & 8 deletions app_web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,31 @@
"@khronosgroup/gltf-viewer": "..",
"axios": "^0.21.1",
"buefy": "^0.9.4",
"fast-png": "^5.0.3",
"gl-matrix": "^3.2.1",
"gltf-viewer-source": "../source",
"jpeg-js": "^0.4.3",
"normalize-wheel": "^1.0.1",
"path": "^0.12.7",
"rxjs": "^6.6.3",
"simple-dropzone": "^0.8.0",
"vue": "^2.6.12",
"vue-rx": "^6.2.0",
"fast-png": "^5.0.3",
"jpeg-js": "^0.4.3"
"vue-rx": "^6.2.0"
},
"devDependencies": {
"rollup": "^2.79.1",
"@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-commonjs": "22.0.2",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^14.1.0",
"@rollup/plugin-commonjs": "22.0.2",
"@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-wasm": "^6.1.3",
"concurrently": "^7.4.0",
"eslint": "^8.24.0",
"rollup": "^2.79.1",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-glslify": "^1.3.1",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-scss": "^3.0.0",
"concurrently": "^7.4.0",
"eslint": "^8.24.0",
"sass": "^1.55.0",
"serve": "^14.0.1"
},
Expand Down
2 changes: 2 additions & 0 deletions app_web/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import copy from 'rollup-plugin-copy';
import alias from '@rollup/plugin-alias';
import replace from '@rollup/plugin-replace';
import json from '@rollup/plugin-json';
import {wasm} from "@rollup/plugin-wasm";

export default {
input: 'src/main.js',
Expand All @@ -19,6 +20,7 @@ export default {
}
],
plugins: [
wasm(),
json(),
glslify({
include: ['../source/Renderer/shaders/*', '../source/shaders/*'],
Expand Down
3 changes: 1 addition & 2 deletions app_web/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ 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()
{
async function main() {
const canvas = document.getElementById("canvas");
const context = canvas.getContext("webgl2", { alpha: false, antialias: true });
const ui = document.getElementById("app");
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"rollup": "^2.79.1",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-glslify": "^1.3.1",
"@rollup/plugin-wasm": "^6.1.3",
"@rollup/plugin-commonjs": "^22.0.2",
"@rollup/plugin-node-resolve": "^14.1.0",
"concurrently": "^7.4.0",
Expand Down
2 changes: 2 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import commonjs from '@rollup/plugin-commonjs';
import glslify from 'rollup-plugin-glslify';
import resolve from '@rollup/plugin-node-resolve';
import copy from "rollup-plugin-copy";
import {wasm} from "@rollup/plugin-wasm";


export default {
Expand All @@ -19,6 +20,7 @@ export default {
}
],
plugins: [
wasm(),
glslify(),
resolve({
browser: false,
Expand Down
3 changes: 3 additions & 0 deletions source/ResourceLoader/resource_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { gltfTexture, gltfTextureInfo } from '../gltf/texture.js';
import { gltfSampler } from '../gltf/sampler.js';
import { GL } from '../Renderer/webgl.js';
import { iblSampler } from '../ibl_sampler.js';
import init from '../libs/mikktspace.js';
import mikktspace from '../libs/mikktspace_bg.wasm';


import { AsyncFileReader } from './async_file_reader.js';
Expand Down Expand Up @@ -110,6 +112,7 @@ class ResourceLoader
image.resolveRelativePath(getContainingFolder(gltf.path));
}

await init(await mikktspace());
await gltfLoader.load(gltf, this.view.context, buffers);

return gltf;
Expand Down
130 changes: 129 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,15 @@ import { gltfSampler } from './sampler.js';
import { gltfBufferView } from './buffer_view.js';
import { DracoDecoder } from '../ResourceLoader/draco.js';
import { GL } from '../Renderer/webgl.js';
import { generateTangents } from '../libs/mikktspace.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 +56,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 +73,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 +735,121 @@ 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 componentCount = accessor.getComponentCount(accessor.type);

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

// Apply the index mapping.
for (let i = 0; i < typedIndexView.length; i++) {
for (let j = 0; j < componentCount; j++) {
unweldedAttribute[i * componentCount + j] = weldedAttribute[typedIndexView[i] * componentCount + 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 = accessor.min;
unweldedAccessor.max = accessor.max;
gltf.accessors.push(unweldedAccessor);

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

generateTangents(gltf) {
if(this.attributes.NORMAL === undefined || this.attributes.TEXCOORD_0 === undefined)
{
return;
}

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);

const tangents = generateTangents(positions, normals, texcoords);

// 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);

}
}

export { gltfPrimitive };
Expand Down
Loading

0 comments on commit cd72885

Please sign in to comment.