File size: 2,510 Bytes
c19ca42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from typing import Tuple

import numpy as np

XYZ = Tuple[np.ndarray, np.ndarray, np.ndarray]
"""
The normalized XYZ components of a normal map. Z is guaranteed to be >= 0.
"""


def normalize_normals(x: np.ndarray, y: np.ndarray) -> XYZ:
    # The square of the length of X and Y
    l_sq = np.square(x) + np.square(y)

    # If the length of X and Y is >1, then make it 1
    l = np.sqrt(np.maximum(l_sq, 1))
    x /= l
    y /= l
    l_sq = np.minimum(l_sq, 1, out=l_sq)

    # Compute Z
    z = np.sqrt(1 - l_sq)

    return x, y, z


def gr_to_xyz(n: np.ndarray) -> XYZ:
    """
    Takes a BGR or BGRA image and converts it into XYZ normal components only by looking at the R and G channels.
    """

    x = n[:, :, 2] * 2 - 1
    y = n[:, :, 1] * 2 - 1

    return normalize_normals(x, y)


def xyz_to_bgr(xyz: XYZ) -> np.ndarray:
    """
    Converts the given XYZ components into an BGR image.
    """
    x, y, z = xyz

    r = (x + 1) * 0.5
    g = (y + 1) * 0.5
    b = z

    return np.dstack((b, g, r))


def octahedral_gr_to_xyz(n: np.ndarray) -> XYZ:
    """
    Takes a BGR or BGRA image of octahedral (RTX Remix) normals and converts it into XYZ normal components only by looking at the R and G channels.
    """
    r = n[:, :, 2] * 2 - 1
    g = n[:, :, 1] * 2 - 1

    x: np.ndarray = (r + g) / 2
    y: np.ndarray = r - x
    z: np.ndarray = 1 - np.abs(x) - np.abs(y)
    length = np.sqrt(np.square(x) + np.square(y) + np.square(z))
    x /= length
    y /= length
    z /= length  # type: ignore

    return x, y, z


def xyz_to_octahedral_bgr(xyz: XYZ) -> np.ndarray:
    """
    Converts the given XYZ components into an BGR image with normals using octahedral (RTX Remix) encoding.

    For more information about octahedral normals, see:
    https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/
    https://jcgt.org/published/0003/02/01/
    """
    x, y, z = xyz
    absolute = np.abs(x) + np.abs(y) + np.abs(z)
    x /= absolute
    y /= absolute

    # This is a trick used in RTX Remix to more efficiently encode normals.
    # Octahedral normals are defined for the whole range of values (so all possible unit vectors).
    # However, we know that we are working with hemispheric normal maps (z>=0)
    # and can use this knowledge to remap xy values to assume all possible values in [-1..1]x[-1..1].
    r = x + y
    g = x - y

    r = (r + 1) * 0.5
    g = (g + 1) * 0.5
    b = np.zeros(x.shape, dtype=np.float32)

    return np.dstack((b, g, r))