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

add calculate tangents #1389

Closed
wants to merge 2 commits into from
Closed
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
10 changes: 0 additions & 10 deletions modules/engine/src/geometry/geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,6 @@ export default class Geometry {
this._setAttributes(attributes, indices);

this.vertexCount = vertexCount || this._calculateVertexCount(this.attributes, this.indices);

// stubRemovedMethods(this, [
// 'setNeedsRedraw', 'needsRedraw', 'setAttributes'
// ], 'Immutable');

// stubRemovedMethods(this, [
// 'hasAttribute', 'getAttribute', 'getArray'
// ], 'Use geometry.attributes and geometry.indices');

// deprecateMethods(this, ['getAttributes'])
}

get mode() {
Expand Down
1 change: 1 addition & 0 deletions modules/engine/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export {KeyFrames} from './animation/key-frames';

// Utils
export {default as ClipSpace} from './utils/clip-space';
export {calculateTangents} from './utils/tangent';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use and underscore export until we do API audit?

64 changes: 64 additions & 0 deletions modules/engine/src/utils/tangent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {Vector2, Vector3} from 'math.gl';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Name file after primary export (calculate-tangents.js)?


const scratchPos0 = new Vector3();
const scratchPos1 = new Vector3();
const scratchPos2 = new Vector3();

const scratchUV0 = new Vector2();
const scratchUV1 = new Vector2();
const scratchUV2 = new Vector2();

const scratchT1 = new Vector3();
const scratchT2 = new Vector3();
const scratchB1 = new Vector3();
const scratchB2 = new Vector3();

export function calculateTangents(positions, texCoords, indices) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

With out looking close, since we are not passing normals, I assume they are calculated from triangles. Would it make sense to pass in normals if we have them (to generate bitangents from crossing tangents and normals)

Since it is easy to calculate bitangents in shader, provide option for only calculating tangents?

const tangents = new Float32Array(positions.length);
const bitangents = new Float32Array(positions.length);
const length = indices ? indices.length : positions.length / 3;

for (let i = 0; i < length; i += 3) {
const locations = indices ? [indices[i], indices[i + 1], indices[i + 2]] : [i, i + 1, i + 2];
const [v0, v1, v2] = locations;
scratchPos0.set(positions[v0 * 3], positions[v0 * 3 + 1], positions[v0 * 3 + 2]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would be nice with comments explaining each intermediary value

scratchUV0.set(texCoords[v0 * 2], texCoords[v0 * 2 + 1]);

scratchPos1.set(positions[v1 * 3], positions[v1 * 3 + 1], positions[v1 * 3 + 2]);
scratchUV1.set(texCoords[v1 * 2], texCoords[v1 * 2 + 1]);

scratchPos2.set(positions[v2 * 3], positions[v2 * 3 + 1], positions[v2 * 3 + 2]);
scratchUV2.set(texCoords[v2 * 2], texCoords[v2 * 2 + 1]);

const deltaPos1 = scratchPos1.subtract(scratchPos0);
const deltaPos2 = scratchPos2.subtract(scratchPos0);

const deltaUV1 = scratchUV1.subtract(scratchUV0);
const deltaUV2 = scratchUV2.subtract(scratchUV0);

const r = 1 / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);

// vec3 tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y)*r;
// vec3 bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x)*r;
const t1 = scratchT1.set(deltaPos1.x, deltaPos1.y, deltaPos1.z).multiplyScalar(deltaUV2.y);
const t2 = scratchT2.set(deltaPos2.x, deltaPos2.y, deltaPos2.z).multiplyScalar(deltaUV1.y);
const tangent = t1.subtract(t2).multiplyScalar(r);

const b1 = scratchB1.set(deltaPos2.x, deltaPos2.y, deltaPos2.z).multiplyScalar(deltaUV1.x);
const b2 = scratchB2.set(deltaPos1.x, deltaPos1.y, deltaPos1.z).multiplyScalar(deltaUV2.x);
const bitangent = b1.subtract(b2).multiplyScalar(r);

for (const v of locations) {
const index = v * 3;
tangents[index] += tangent.x;
tangents[index + 1] += tangent.y;
tangents[index + 2] += tangent.z;

bitangents[index] += bitangent.x;
bitangents[index + 1] += bitangent.y;
bitangents[index + 2] += bitangent.z;
}
}

return {tangents, bitangents};
}
229 changes: 229 additions & 0 deletions test/apps/tangents/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import {AnimationLoop, Model, CylinderGeometry, calculateTangents} from '@luma.gl/engine';
import {Buffer, clear} from '@luma.gl/webgl';
import {setParameters} from '@luma.gl/gltools';
import {Matrix4} from '@math.gl/core';
import {Vector3, radians} from '@math.gl/core';
import Controller from './controller';

const vs = `\
#define SHADER_NAME simpleVs;

attribute vec3 positions;

uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;

void main(void) {
gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * vec4(positions, 1.0);
}
`;

const fs = `\
#define SHADER_NAME simpleFs;

precision highp float;

uniform vec4 u_Color;

void main(void) {
gl_FragColor = u_Color;
}
`;

const tangentVs = `\
#define SHADER_NAME simpleVs;

attribute vec3 positions;
attribute vec3 colors;
attribute vec3 instancePositions;
attribute vec3 instanceNormals;
attribute vec3 instanceTangents;
attribute vec3 instanceBitangents;

uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;

varying vec3 vColor;

void main(void) {
vColor = colors;
mat3 instanceMatrix = mat3(instanceTangents, instanceBitangents, instanceNormals);

vec4 pos = vec4(normalize(instanceMatrix * positions) * 0.2, 1.0);
pos.xyz += instancePositions;
pos = u_ModelMatrix * pos;

gl_Position = u_ProjectionMatrix * u_ViewMatrix * pos;
}
`;

const tangentFs = `\
precision highp float;

varying vec3 vColor;
void main(void) {
gl_FragColor = vec4(vColor, 1.0);
}
`;

const loop = new AnimationLoop({
onInitialize({gl}) {
this._controller = new Controller(gl.canvas);
setParameters(gl, {
depthTest: true,
depthFunc: gl.LEQUAL,
[gl.LINE_WIDTH]: 2
});

const modelMatrix = new Matrix4().rotateX(radians(30)).rotateY(30);
const projectionMatrix = new Matrix4();

const geometry = new CylinderGeometry({
radius: 1,
nradial: 10,
nvertical: 10
});

const {
attributes: {NORMAL, TEXCOORD_0, POSITION},
indices
} = geometry;

const {tangents, bitangents} = calculateTangents(
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be nice if calculateTangents accepted a luma.gl geometry object (looked for attributes and indices)

POSITION.value,
TEXCOORD_0.value,
indices.value
);

const normal = NORMAL.value;
const position = POSITION.value;
const scratchP = new Vector3();
const scratchT = new Vector3();
const scratchB = new Vector3();
const scratchN = new Vector3();

const count = position.length / 3;
const instanceTangents = new Float32Array(count * 3);
const instanceBitangents = new Float32Array(count * 3);

for (let i = 0; i < count; i++) {
const index = i * 3;
scratchP.set(position[index], position[index + 1], position[index + 2]);
scratchT.set(tangents[index], tangents[index + 1], tangents[index + 2]);
scratchB.set(bitangents[index], bitangents[index + 1], bitangents[index + 2]);
scratchN.set(normal[index], normal[index + 1], normal[index + 2]);

scratchT.normalize();
scratchB.normalize();

instanceTangents[i * 3 + 0] = scratchT.x;
instanceTangents[i * 3 + 1] = scratchT.y;
instanceTangents[i * 3 + 2] = scratchT.z;
instanceBitangents[i * 3 + 0] = scratchB.x;
instanceBitangents[i * 3 + 1] = scratchB.y;
instanceBitangents[i * 3 + 2] = scratchB.z;
}

const cubeModel = new Model(gl, {
vs,
fs,
geometry
});

const tangentModel = new Model(gl, {
vs: tangentVs,
fs: tangentFs,
attributes: {
positions: new Buffer(
gl,
new Float32Array([
0.0,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use // prettier-ignore to avoid such crazy autoformatting.

0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0
])
),
colors: new Buffer(
gl,
new Float32Array([
1.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.0
])
),
instanceNormals: new Buffer(gl, NORMAL.value),
instancePositions: new Buffer(gl, POSITION.value),
instanceTangents: new Buffer(gl, instanceTangents),
instanceBitangents: new Buffer(gl, instanceBitangents)
},
vertexCount: 6,
instanceCount: count
});

return {
cubeModel,
tangentModel,
modelMatrix,
projectionMatrix
};
},

onRender({gl, aspect, tangentModel, cubeModel, modelMatrix, projectionMatrix}) {
const {viewMatrix} = this._controller.getMatrices();
projectionMatrix.perspective({fov: Math.PI / 3, aspect});

clear(gl, {color: [0, 0, 0, 1]});

cubeModel
.setUniforms({
u_ModelMatrix: modelMatrix,
u_ViewMatrix: viewMatrix,
u_ProjectionMatrix: projectionMatrix,
u_Color: [1.0, 1.0, 1.0, 0.7]
})
.setDrawMode(gl.LINES)
.draw();

tangentModel
.setUniforms({
u_ModelMatrix: modelMatrix,
u_ViewMatrix: viewMatrix,
u_ProjectionMatrix: projectionMatrix
})
.setDrawMode(gl.LINES)
.draw();
}
});

loop.start();
61 changes: 61 additions & 0 deletions test/apps/tangents/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {Matrix4} from 'math.gl';

// Simple controller that keeps updating translation and rotation
export default class Controller {
constructor(canvas, {initialZoom = 3, onDrop = file => {}} = {}) {
this.mouse = {
lastX: 0,
lastY: 0
};

this.translate = initialZoom;
this.rotation = [0, 0];
this.rotationStart = [0, 0];

this._initializeEventHandling(canvas);
}

getMatrices() {
const [pitch, roll] = this.rotation;

const viewMatrix = new Matrix4()
.translate([0, 0, -this.translate])
.rotateX(pitch)
.rotateY(roll);

return {
viewMatrix
};
}

_initializeEventHandling(canvas) {
canvas.onwheel = e => {
this.translate += e.deltaY / 10;
if (this.translate < 0.1) {
this.translate = 0.1;
}
e.preventDefault();
};

canvas.onpointerdown = e => {
this.mouse.lastX = e.clientX;
this.mouse.lastY = e.clientY;

this.rotationStart[0] = this.rotation[0];
this.rotationStart[1] = this.rotation[1];

canvas.setPointerCapture(e.pointerId);
e.preventDefault();
};

canvas.onpointermove = e => {
if (e.buttons) {
const dX = e.clientX - this.mouse.lastX;
const dY = e.clientY - this.mouse.lastY;

this.rotation[0] = this.rotationStart[0] + dY / 100;
this.rotation[1] = this.rotationStart[1] + dX / 100;
}
};
}
}
Loading