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);
    }
  `,
 ...