• Portfolio and blog of sydneyzh.

# Vector transformation using projection between orthonormal bases

$a_{\text{object}}$ is a vector in the object space, which has an orthonormal basis $(u, v, w)$.

$$a_{\text{object}}(u, v, w) = \text{u} u + \text{v} v + \text{w} w.$$

The projection relation between $(u, v, w)$ and the world space orthonormal basis $(x, y, z)$ is:

$$u(x, y, z) = \text{x}_u x + \text{y}_u y + \text{z}_u z,$$ $$v(x, y, z) = \text{x}_v x + \text{y}_v y + \text{z}_v z,$$ $$v(x, y, z) = \text{x}_w x + \text{y}_w y + \text{z}_w z.$$

$a_{\text{world}}$ can be solved using the above equations.

The following code describes the process of cosine-weighted sampling on a hemisphere with a random zenith direction. Firstly, a sample is produced in the object(tangent) space where the zenith direction is always pointing upwards. The sample is then transformed to the world space uisng orthonormal basis projection.

# orthonormal basis order
t_idx=0 # tangent (left)
bn_idx=1 # binormal (front)
n_idx=2 # normal (zenith/up)

def get_onb(n):
binormal = []
tangent = []
t = [n[0], n[1], n[2] + 1]
binormal = normalize(cross(n, t))
tangent = cross(n, binormal)
return [tangent,  binormal, n]

def onb_inverse_transform(onb, p):
# transform point p from tangent space to world space
return add(scale(onb[t_idx], p[t_idx]), scale(onb[bn_idx], p[bn_idx]), scale(onb[n_idx], p[n_idx]))

# generate a random surface normal to construct the tangent space
nff = [- 1.0 + 2.0 * rnd(), - 1.0 + 2.0 * rnd(), rnd()]
nff = normalize(nff)
onb = get_onb(nff)

for i_phi in range(0, num_sample_phi):
for i_theta in range(0, num_sample_theta):
phi = map_ep_to_phi(rnd())
theta_diffuse = map_ep_to_theta_diffuse(rnd())

# tangent space sample
x = spherical_to_x(phi, theta_diffuse)
y = spherical_to_y(phi, theta_diffuse)
z = spherical_to_z(phi, theta_diffuse)
data_x_object.append(x)
data_y_object.append(y)
data_z_object.append(z)

# world space sample
p = [x, y, z]
p = onb_inverse_transform(onb, p)
data_x_world.append(p[0])
data_y_world.append(p[1])
data_z_world.append(p[2])


Result (Left: tangent space. Right: world space. The blue dot indicates where the random surface normal intersects the unit sphere.)