Advertisement
gehtsiegarnixan

Cubemap TBN Matrix

Sep 25th, 2023
772
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.07 KB | None | 0 0
  1. # written by gehtsiegarnixan
  2.  
  3. import numpy as np
  4. import math
  5. import datetime
  6. import matplotlib.pyplot as plt
  7. import matplotlib.animation as animation
  8. from pysr import PySRRegressor
  9. import pandas as pd
  10. from sympy import symbols, diff, sqrt
  11.  
  12.  
  13. def rnd_normal_vectors(num_points=2 ** 16):
  14.     # Generate random uv values
  15.     uv_grid = np.random.rand(num_points, 2)
  16.  
  17.     # Initialize an array to store the output vectors
  18.     vectors = []
  19.  
  20.     # Calculate the inf_cubemap for each uv in the grid
  21.     for uv in uv_grid:
  22.         vector = inf_cubemap(uv, correction=True)
  23.         vectors.append(vector)
  24.  
  25.     return np.array(vectors)
  26.  
  27.  
  28. def grid_normal_vectors(num_points):
  29.     # Calculate the number of points along each dimension
  30.     num_points_dim = int(np.sqrt(num_points))
  31.  
  32.     # Create a grid of uv values
  33.     u = np.linspace(0, 1, num_points_dim)
  34.     v = np.linspace(0, 1, num_points_dim)
  35.     uv_grid = np.array(np.meshgrid(u, v)).T.reshape(-1, 2)
  36.  
  37.     # Initialize an array to store the output vectors
  38.     vectors = []
  39.  
  40.     # Calculate the inf_cubemap for each uv in the grid
  41.     for uv in uv_grid:
  42.         vector = inf_cubemap(uv, correction=True)
  43.         vectors.append(vector)
  44.  
  45.     return np.array(vectors)
  46.  
  47.  
  48. def wsgrid_normal_vectors(num_samples=2 ** 16):
  49.     # bounds of the plot
  50.     v_max = math.sqrt(2) / 2
  51.  
  52.     # Create a grid of UV values
  53.     uv_values = np.linspace(-v_max, v_max, num_samples)
  54.     uv_grid = np.meshgrid(uv_values, uv_values)
  55.     uv_grid = np.dstack(uv_grid)
  56.  
  57.     # Initialize an array to store RGB colors
  58.     normals = []
  59.  
  60.     # Calculate sphere normals for each UV value and convert to RGB
  61.     for row in uv_grid:
  62.         for uv in row:
  63.             # Clamp the value inside the square root to be no less than 0
  64.             w_world_value = max(0.00001, -uv[0] ** 2 - uv[1] ** 2 + 1)
  65.             w_world = math.sqrt(w_world_value)
  66.             normal = np.array([uv[0], uv[1], w_world])
  67.  
  68.             # Append the normalized vector to the array
  69.             normals.append(normal)
  70.  
  71.     return np.array(normals)
  72.  
  73.  
  74. def cubemap(uvw, correction=True):
  75.     # project into face
  76.     cubemap_coord = uvw[:2] / uvw[2]
  77.  
  78.     if correction:
  79.         # Cass Everitt's piecewise quadratic warp
  80.         distort = 1.45109572583 - 0.451095725826 * np.abs(cubemap_coord)
  81.         cubemap_coord *= distort
  82.  
  83.     # rescale to 0-1 range
  84.     uv = cubemap_coord * 0.5 + 0.5
  85.  
  86.     return uv
  87.  
  88.  
  89. def inf_cubemap(uv, correction=True):
  90.     # rescale -1 to 1
  91.     uv = uv * 2. - 1.
  92.  
  93.     if correction:
  94.         # reverse Cass Everitt's piecewise quadratic warp
  95.         arg_sqrt = 2.105679 - 1.804383 * np.abs(uv)
  96.         arg_sqrt = np.maximum(arg_sqrt, 0)  # Ensure the argument is non-negative
  97.         uv = np.sign(uv) * -1.108412 * (-1.451096 + np.sqrt(arg_sqrt))
  98.  
  99.     # recreate normal vector with side being up
  100.     partial = np.sqrt((uv[0] ** 2) + (uv[1] ** 2) + 1.)
  101.     normal = np.array([uv[0] / partial, uv[1] / partial, 1. / partial])
  102.  
  103.     return normal
  104.  
  105.  
  106. def test_cubemap_inversion(num_samples=2 ** 16, correction=False):
  107.     # Convert the list of vectors into a NumPy array
  108.     input_values = rnd_normal_vectors(num_samples)
  109.  
  110.     for i in range(num_samples):
  111.         uvw = input_values[i]
  112.  
  113.         # Apply cubemap and then inf_cubemap
  114.         uv = cubemap(uvw, correction)
  115.         uvw_reconstructed = inf_cubemap(uv, correction)
  116.  
  117.         # Check if the reconstructed values match the original input
  118.         if not np.allclose(uvw_reconstructed, uvw, atol=1e-6):
  119.             print("Test failed!")
  120.             print("Original Input:", uvw)
  121.             print("Reconstructed Input:", uvw_reconstructed)
  122.             return
  123.  
  124.     print("All tests passed!")
  125.  
  126.  
  127. def tangent_finite_difference(normal, correction=True):
  128.     # cubemapping
  129.     uv = cubemap(normal, correction)
  130.  
  131.     # inverse cubemapping with a tiny offset in x direction
  132.     offset_uv = uv + np.array([0.00001, 0])
  133.     normal_offset = inf_cubemap(offset_uv, correction)
  134.  
  135.     # generate the tangent vector approximation
  136.     tangent_vec = normal_offset - normal
  137.     tangent_vec /= np.linalg.norm(tangent_vec)  # normalize the tangent vector
  138.  
  139.     return tangent_vec
  140.  
  141.  
  142. def tangent_derivative(normal, correction=True):
  143.     # Generate the tangent vector approximation
  144.     tangent_vec = np.array([1 + normal[1] ** 2,
  145.                             - normal[0] * normal[1],
  146.                             - normal[0]])
  147.     # skipping the correction for this as it basically looks the same but is way more complicated.
  148.     tangent_vec /= np.linalg.norm(tangent_vec)  # normalize the tangent vector
  149.  
  150.     return tangent_vec
  151.  
  152.  
  153. def bitangent(normal, correction=True):
  154.     # Generate the tangent vector approximation
  155.     tangent_vec = np.array([- normal[0] * normal[1],
  156.                             1 + normal[0] ** 2,
  157.                             - normal[1]])
  158.     tangent_vec /= np.linalg.norm(tangent_vec)  # normalize the tangent vector
  159.  
  160.     return tangent_vec
  161.  
  162.  
  163. def z_plot(num_points_dim=255, correction=True):
  164.     # Generate a grid of vectors
  165.     vectors_array = wsgrid_normal_vectors(num_points_dim)
  166.  
  167.     # Initialize arrays to store RGB colors for both methods
  168.     rgb_colors_tangent = []
  169.     rgb_colors_approximation = []
  170.  
  171.     # Calculate sphere normals for each UV value and convert to RGB
  172.     for uvw in vectors_array:
  173.         uv = cubemap(uvw, correction=False)
  174.         if np.all((0 <= uv) & (uv <= 1)):
  175.             # Calculate tangent vector for cubemap using both methods
  176.             tangent_vec = tangent_derivative(uvw, correction)
  177.             approximation_vec = tangent_finite_difference(uvw, correction)
  178.  
  179.             rgb_colors_tangent.append(tangent_vec)
  180.             rgb_colors_approximation.append(approximation_vec)
  181.         else:
  182.             rgb_colors_tangent.append([0, 0, 0])
  183.             rgb_colors_approximation.append([0, 0, 0])
  184.  
  185.     # Convert the RGB colors to NumPy arrays
  186.     rgb_colors_tangent = np.array(rgb_colors_tangent)
  187.     rgb_colors_approximation = np.array(rgb_colors_approximation)
  188.  
  189.     # Clamp the RGB values to the range [0, 1]
  190.     rgb_colors_tangent = np.clip(rgb_colors_tangent, 0, 1)
  191.     rgb_colors_approximation = np.clip(rgb_colors_approximation, 0, 1)
  192.  
  193.     # Create side-by-side plots of the RGB colors with specified color range
  194.     fig, axs = plt.subplots(1, 2, figsize=(16, 8))
  195.  
  196.     axs[0].imshow(rgb_colors_tangent.reshape(num_points_dim, num_points_dim, 3), origin='lower')
  197.     axs[0].set_xlabel('X')
  198.     axs[0].set_ylabel('Y')
  199.     axs[0].set_title('Tangent from Derivative')
  200.     axs[0].grid(True)
  201.  
  202.     axs[1].imshow(rgb_colors_approximation.reshape(num_points_dim, num_points_dim, 3), origin='lower')
  203.     axs[1].set_xlabel('X')
  204.     axs[1].set_ylabel('Y')
  205.     axs[1].set_title('Tangent from finite Difference')
  206.     axs[1].grid(True)
  207.  
  208.     plt.tight_layout()
  209.     plt.show()
  210.  
  211.  
  212. def y_plot(num_points_dim=255, correction=False, period=2):
  213.     fig, ax = plt.subplots(figsize=(8, 8))
  214.  
  215.     # Generate a grid of vectors
  216.     vectors_array = wsgrid_normal_vectors(num_points_dim)
  217.     vectors_array = vectors_array.reshape(num_points_dim, num_points_dim, 3)
  218.  
  219.     def update(i):
  220.         ax.clear()
  221.  
  222.         # flip flop animation
  223.         index = abs(i - (num_points_dim - 1))
  224.  
  225.         # get the row of uvw coordinates
  226.         vector_row = vectors_array[index]
  227.  
  228.         # Create arrays to store tangents for both methods
  229.         tangents_true = []
  230.         tangents_approximation = []
  231.         vector_row_filtered = []
  232.  
  233.         # Iterate over uvw vectors and compute cubemap values
  234.         for uvw_vector in vector_row:
  235.             uv = cubemap(uvw_vector, correction=False)
  236.             if np.all((0 <= uv) & (uv <= 1)):
  237.                 # Calculate tangent vector for cubemap using both methods
  238.                 tangent_vec_true = tangent_derivative(uvw_vector, correction)
  239.                 tangent_vec_approximation = tangent_finite_difference(uvw_vector, correction)
  240.  
  241.                 tangents_true.append(tangent_vec_true)
  242.                 tangents_approximation.append(tangent_vec_approximation)
  243.  
  244.                 # add the valid uvw vector to list
  245.                 vector_row_filtered.append(uvw_vector)
  246.  
  247.         # get y and z part of the coordinate vector
  248.         x = [uvw_vector[0] for uvw_vector in vector_row_filtered]
  249.         z = [uvw_vector[2] for uvw_vector in vector_row_filtered]
  250.  
  251.         # get x,y,z part of the tangent vector for both methods
  252.         tangent_x_true = [tangent_vec[0] for tangent_vec in tangents_true]
  253.         tangent_y_true = [tangent_vec[1] for tangent_vec in tangents_true]
  254.         tangent_z_true = [tangent_vec[2] for tangent_vec in tangents_true]
  255.  
  256.         tangent_x_approximation = [tangent_vec[0] for tangent_vec in tangents_approximation]
  257.         tangent_y_approximation = [tangent_vec[1] for tangent_vec in tangents_approximation]
  258.         tangent_z_approximation = [tangent_vec[2] for tangent_vec in tangents_approximation]
  259.  
  260.         # Create the plot for true tangent
  261.         ax.plot(x, tangent_x_true, label='Tangent x (Derivative)', color='orange')
  262.         ax.plot(x, tangent_y_true, label='Tangent y (Derivative)', color='green')
  263.         ax.plot(x, tangent_z_true, label='Tangent z (Derivative)', color='blue')
  264.  
  265.         # Create the plot for approximation tangent with dotted line style
  266.         ax.plot(x, tangent_x_approximation, label='Tangent x (Finite Diff)', color='orange', linestyle='dotted')
  267.         ax.plot(x, tangent_y_approximation, label='Tangent y (Finite Diff)', color='green', linestyle='dotted')
  268.         ax.plot(x, tangent_z_approximation, label='Tangent z (Finite Diff)', color='blue', linestyle='dotted')
  269.  
  270.         # Create the plot for sphere
  271.         ax.plot(x, z, label='Sphere', color='red')
  272.  
  273.         # Bounds of the plot
  274.         ax.set_xlim([-0.9, 1.6])
  275.         ax.set_ylim([-0.8, 1.4])
  276.  
  277.         # Add labels and legend
  278.         ax.set_xlabel('X')
  279.         ax.set_ylabel('Z')
  280.         ax.set_title(f"Y = {vector_row[0][1]:.2f}")
  281.  
  282.         ax.grid(True)
  283.         ax.legend()
  284.  
  285.     # animation
  286.     frames = (num_points_dim - 1) * 2  # double for flipflop
  287.     interval_ms = (period / num_points_dim) * 1000
  288.     ani = animation.FuncAnimation(fig, update, frames=frames, repeat=True, interval=interval_ms)
  289.  
  290.     plt.axis('equal')
  291.     plt.show()
  292.  
  293.  
  294. def derivatives(correction=True):
  295.     # Define the symbols
  296.     x, y = symbols('x y')
  297.  
  298.     # Define the function q(x, y)
  299.     q = (x / sqrt(x ** 2 + y ** 2 + 1),
  300.          y / sqrt(x ** 2 + y ** 2 + 1),
  301.          1 / sqrt(x ** 2 + y ** 2 + 1))
  302.  
  303.     if correction:
  304.         # Cass Everitt's piecewise quadratic warp
  305.         q = ((1.45109572583 - 0.451095725826 * sqrt(q[0]**2)) * q[0],
  306.              (1.45109572583 - 0.451095725826 * sqrt(q[1]**2)) * q[1],
  307.              (1.45109572583 - 0.451095725826 * sqrt(q[2]**2)) * q[2])
  308.  
  309.     # Compute the partial derivatives
  310.     u = [diff(q_i, x) for q_i in q]  # ∂q/∂x
  311.     v = [diff(q_i, y) for q_i in q]  # ∂q/∂y
  312.  
  313.     print("u_x =", u[0])
  314.     print("u_y =", u[1])
  315.     print("u_z =", u[2])
  316.  
  317.     print("v_x =", v[0])
  318.     print("v_y =", v[1])
  319.     print("v_z =", v[2])
  320.  
  321.     """
  322.    the rescaling to 0-1 just adds a 0.5 factor for all components since we normalize this vector it cancels out.
  323.    u = [(x²+1) /(x² + y² + 1)^(3/2),
  324.          -xy   /(x² + y² + 1)^(3/2),
  325.          -x    /(x² + y² + 1)^(3/2)]
  326.    v = [-xy    /(x² + y² + 1)^(3/2),
  327.        (x²+1)  /(x² + y² + 1)^(3/2),
  328.          -y    /(x² + y² + 1)^(3/2)]
  329.    can be simplified to:
  330.    tangent = normalize(1+y^2, -xy, -x)
  331.    bitangent = normalize(-xy, 1+x^2, -y)
  332.    """
  333.  
  334.  
  335. def symbolic_regression(num_samples=64*64, correction=True, iterations=5):
  336.     # Generate some input data
  337.     x = grid_normal_vectors(num_samples)
  338.  
  339.     # Initialize an array to store the output data
  340.     y = []
  341.  
  342.     # Calculate the approximation tangent for each vector in X
  343.     for uvw in x:
  344.         tangent_vec = tangent_finite_difference(uvw, correction=correction)
  345.         y.append(tangent_vec)
  346.  
  347.     # Convert the list of tangents into a NumPy array
  348.     y = np.array(y)
  349.  
  350.     # Create a PySRRegressor object and fit the data
  351.     model = PySRRegressor(niterations=iterations,
  352.                           binary_operators=["plus", "sub", "mult", "div"],
  353.                           unary_operators=["sqrt"],
  354.                           )
  355.     model.fit(x, y)
  356.  
  357.     # Set pandas options
  358.     pd.set_option('display.max_columns', None)
  359.     pd.set_option('display.expand_frame_repr', False)
  360.     pd.set_option('max_colwidth', None)
  361.  
  362.     # Print the model
  363.     print(model)
  364.  
  365.     # Format the timestamp as a string
  366.     now = datetime.datetime.now()
  367.     timestamp_str = now.strftime("%Y-%m-%d_%H%M%S.%f")
  368.     filename = f'model_output_{timestamp_str}.txt'
  369.  
  370.     # Save the model's output to a text file
  371.     with open(filename, 'w') as f:
  372.         print(model, file=f)
  373.  
  374.  
  375. def main():
  376.     correction = False
  377.     z_plot(255, correction)
  378.     y_plot(128, correction)
  379.  
  380.     # test_cubemap_inversion(correction=correction)
  381.     # derivatives(correction)
  382.     # symbolic_regression(128*128, correction, 50)
  383.  
  384.  
  385. if __name__ == "__main__":
  386.     # execute only if run as a script
  387.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement