ShaderMaterial Tangent Space
by Matt
CSS
html,
body,
canvas {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
display: block;
}
JavaScript
import * as THREE from '//cdn.skypack.dev/[email protected]'
import { OrbitControls } from '//cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls.js'
// Scene, Camera
const scene = new THREE.Scene()
scene.background = new THREE.Color('black')
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
camera.position.set(0, 2, 3)
// WebGL renderer
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// OrbitControls
const controls = new OrbitControls(camera, renderer.domElement)
controls.autoRotate = true
// Geometry
const geometry = new THREE.SphereGeometry(1, 32, 32)
/**
* Apparently .computeTangents() is an insufficient approximation,
* so we compute tangents for the sphere analytically
*/
const normalAttribute = geometry.getAttribute('normal')
const n = normalAttribute.count
const tangentArray = new Float32Array(n * 3)
const tangentAttribute = new THREE.BufferAttribute(tangentArray, 3)
const v = new THREE.Vector3();
for(let i=0; i < n; i++) {
v.fromBufferAttribute (normalAttribute, i);
v.set(v.z, 0, -v.x).normalize();
tangentAttribute.setXYZ(i, v.x, v.y, v.z)
}
geometry.setAttribute( 'tangent', tangentAttribute );
// Material
const material = new THREE.ShaderMaterial({
vertexShader: `
attribute vec4 tangent;
varying vec3 vDir;
void main() {
// Create TBN matrix
vec3 n = normal.xyz;
vec3 t = tangent.xyz;
vec3 b = cross(n, t) * tangent.w;
mat3 tangentToLocal = mat3(t, b, n);
mat3 localToTangent = transpose(tangentToLocal);
vec4 cameraPositionLocal = inverse(modelMatrix) * vec4(cameraPosition, 1.0);
vec3 cameraPositionTangent = localToTangent* cameraPositionLocal.xyz;
vDir = cameraPositionTangent;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
...