-
Notifications
You must be signed in to change notification settings - Fork 215
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
add calculate tangents #1389
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import {Vector2, Vector3} from 'math.gl'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}; | ||
} |
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
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(); |
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; | ||
} | ||
}; | ||
} | ||
} |
There was a problem hiding this comment.
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?