# Cubemap TBN Matrix

Sep 25th, 2023
558
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
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()
Tags: