rnd-team-dev / plotoptix

Data visualisation and ray tracing in Python based on OptiX 7.7 framework.
https://rnd.team/plotoptix
Other
499 stars 26 forks source link

Following rays along the way of their multiple intersections #41

Open vitalitylearning2021 opened 1 year ago

vitalitylearning2021 commented 1 year ago

Good day, I need to follow a ray along its path and acquire information of all its multiple intersections. To this end, I'm considering the following example involving the scattering from a corner reflector:

import numpy as np
from plotoptix import TkOptiX
from plotoptix import NpOptiX
import matplotlib.pyplot as plt

nu = 7
nv = 7

def displayResults(rt):

    print("Launch finished.")

    hitPositionsData = rt._hit_pos
    xHitPositions = hitPositionsData[:, :, 0]
    yHitPositions = hitPositionsData[:, :, 1]
    zHitPositions = hitPositionsData[:, :, 2]
    dHitPositions = hitPositionsData[:, :, 3]

    hitTriangle   = rt._geo_id[:, :, 1].reshape(rt._height, rt._width)

    print("Shape of rays array is {}.".format(xHitPositions.shape))

    xHitPositions = xHitPositions[hitTriangle < 0xFFFFFFFF]
    yHitPositions = yHitPositions[hitTriangle < 0xFFFFFFFF]
    dHitPositions[np.where(hitTriangle >= 0xFFFFFFFF)] = -1
    hitTriangle[np.where(hitTriangle >= 0xFFFFFFFF)] = 10

    print(dHitPositions)

    print("Shape of hitting rays array is {}.".format(xHitPositions.shape))

    plt.plot(xHitPositions, yHitPositions, 'bo')
    plt.show()

    plt.imshow(dHitPositions)
    plt.colorbar()
    plt.show()

    plt.imshow(hitTriangle)
    plt.colorbar()
    plt.show()

    plt.draw()

#verticesTriangle    = np.array([[-2, -2, 0], [2, -2, 0], [-2, 2, 0], [2,  2, 0]])
#faceTriangle        = np.array([[0, 1, 2], [1, 2, 3]])
#verticesTriangles   = np.array([[-1, -1, 0], [1, -1, 0], [-1, 1, 0], [1, 1, 0], [-1, 1, 2], [1, 1, 2]])
#faceTriangles       = np.array([[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5]])
verticesTriangles   = np.array([[0, 1, 0], [0, -1, 0], [0.5 * np.sqrt(2), 1, 0.5 * np.sqrt(2)], [0.5 * np.sqrt(2), -1, 0.5 * np.sqrt(2)], [-0.5 * np.sqrt(2), 1, 0.5 * np.sqrt(2)], [-0.5 * np.sqrt(2), -1, 0.5 * np.sqrt(2)]])
faceTriangles       = np.array([[0, 1, 2], [1, 2, 3], [0, 1, 4], [1, 4, 5]])

rt                  = NpOptiX(on_rt_accum_done = displayResults, width = nu, height = nv)
#rt                  = NpOptiX(width = nu, height = nv)

rt.set_mesh("Mesh", verticesTriangles, faceTriangles)

u                   = np.linspace(-1.9, 2.1, nu)
v                   = np.linspace(-2, 2, nv)
V, U                = np.meshgrid(v, u)
W                   = np.full((nu, nv), -1)
originsTexture      = np.stack((U, V, W, np.zeros((nu, nv)))).T

rt.set_texture_2d("origins", originsTexture)

cx                  = np.zeros((nu, nv))
cy                  = np.zeros((nu, nv))
cz                  = np.ones((nu, nv))
r                   = np.full((nu, nv), 200)
directionsTexture   = np.stack((cx, cy, cz, r)).T

rt.set_texture_2d("directions", directionsTexture)

rt.setup_camera("custom_cam", cam_type = "CustomProjXYZtoDir", textures = ["origins", "directions"])

rt.set_param(max_accumulation_frames = 1)

rt.set_uint("path_seg_range", 1, 4)

rt.start()

#rt.close()

I have the following questions:

1) I'm setting path_seg_range to enable multiple reflections. However, it seems the line does not have an effect. If I comment it out, the results seem to be the same. 2) I access the hit positions and hit distances by the rt._hit_pos array. However, for my purposes, I need to know all the hit positions and distances per ray. Is that possible by PlotOptiX?

robertsulej commented 1 year ago

_hit_pos are positions of the first hit along the ray, even if the ray continues and has multiple segments. This was useful for several applications until now, especially for research work like yours. In this way you have all the information exposed - the face involved and the hit. From this you can calculate required angles and prepare next ray segment direction. The workflow in this case is:

  1. start with ray origins and directions as you do it now
  2. collect hit positions
  3. calculate and update origins and directions in camera textures, trigger OptiX launch and loop over 2. and 3. as long as you need

Note, you can use flat material if you calculate next segment on your own. It is much faster than the default diffuse material.

Hit info for multiple segments is not available in PlotOptiX. Performance and memory would suffer a lot, and segments depend on the material logic that is specific to material shaders (how rays are refracted or what pdf function is used to draw the outgoing direction). So far I had no good use case to develop in this direction.

vitalitylearning2021 commented 1 year ago

Thank you very much for your help. I would then ask the following questions:

  1. Does PlotOptiX calculate the normals at the intersection points or is this in charge to the User to do it manually starting from the hit triangle?
  2. Since I have to trigger multiple OptiX launches to deal with multiple reflections and in order to save computational resources, I would need PlotOptiX to compute only one ray segment each time OptiX is triggered. Is there a way to set PlotOptiX to compute only one reflection per loop iteration? Should I use something like rt.set_param(max_accumulation_frames = 1) ?
  3. In the example above, the intersection information is fetched from the output buffer at the on_rt_accum_done stage by the displayResults function. How should I loop to trigger OptiX, make synchronizations and fetch information on multiple intersections? Should I use global variables?
  4. Is the flat material equivalent to a perfect reflector, something with a unitary reflection coefficient? Should I use rt.setup_material("m_flat", m_flat) and then rt.set_data("corner", mat="m_flat") to set it up?

Sorry for the many questions and thanks in advance for any help.

robertsulej commented 1 year ago

Hi,

  1. You can use normals buffer, see also this notebook. However, you need to transform vectors from camera space to world space.
  2. Yes, please, use rt.set_uint("path_seg_range", 1, 1) and flat material for objects in the scene. This will limit each ray to the first intersection and avoid calculations used for physical materials.
  3. When you have ray origins and directions calculated and camera textures ready to update, do it like this:
    rt.set_texture_2d("origins", next_origins)
    rt.set_texture_2d("directions", next_dirs, refresh=True)

    refresh=True will trigger the launch. Otherwise you can update everything w/o refreshing and trigger launch with refresh_scene()

  4. Yes, first setup the material, then assign it to objects. The flat material is only terminating the ray and assigns a fixed color value to the radiance variable propagated along each ray. It does not calculate anything else and is a fast and simple CUDA code. A perfect reflector would be this one, but you don't have access to its results in multi-segment propagation, so probably not useful here.

No worries about questions! :) This way I can always spot some improvements.