У меня есть похожий трассировщик лучей (написанный на Python) и я наткнулся на ту же проблему: чтобы правильно отработать физику, нужно знать показатель преломления с каждой стороны границы пересечения. Это заняло довольно много времени, чтобы решить элегантно, но в конце концов я выбрал это решение/дизайн:
Дизайн
1) Сцена. У меня есть основной объект сцены (в основном массив всех объектов сцены), у вас, вероятно, будет что-то подобное. Он хранит геометрические объекты.
Методы:
intersection_points(ray)
- возвращает список всех точек пересечения, отсортированных по расстоянию от луча.
intersection_objects(ray)
- возвращает список всех объектов пересечения, отсортированных по расстоянию от луча.
containing_object(ray)
— возвращает объект, содержащий луч.
objects()
- возвращает список всех объектов в произвольном порядке.
Примечание. Сцена добавляет в список дополнительный объект: Scene_Boundary. Это гигантский ящик (или Сфера), который заключает в себе всю сцену, т.е. ВСЕ находится внутри этой границы.
2) Объекты. Сделайте так, чтобы геометрические объекты (например, ваша сфера) реализовывали эти методы.
Методы:
contains(ray)
— возвращает True, если начало луча находится внутри объекта, False, если на поверхности, и False, если снаружи
ray_is_on_surface(ray)
- возвращает True, если луч находится только на поверхности, иначе False.
intersection_points(ray)
— возвращает точку (точки) пересечения луча с объектом.
surface_normal(ray)
- возвращает вектор нормали к поверхности, на которую упал луч (это поможет с френелевским отражением и преломлением)
Для оптических расчетов объекты также должны иметь показатель преломления.
Переменные экземпляра:
Граничная задача
Задача, которую мы хотим решить: каков показатель преломления внутри (n1) и снаружи (n2) границы? Для этого мы следуем этой процедуре:
1) Проследите луч через всю сцену:
sphere # origin = (0,0,0), radius = 1
ray # origin = (0,0,0), direction = (0,0,1) Note: the ray is inside the sphere
scene.add_object(sphere)
ipoints = scene.intersection_points(ray) # [ (0,0,1), (0,0,10) ]
iobjects = scene.intersection_objects(ray) # [ Sphere, Scene_Boundary]
Помните, что они отсортированы по расстоянию от начала луча. Последний элемент в ipoints и iobjects — это пересечение луча с границей сцены. Мы воспользуемся этим позже!
2) n1 находится просто путем нахождения содержащего объекта, например:
obj1 = scene.containing_object(ray) # Scene_Boundary
n1 = obj1.refractive_index() # n1 = 1. Scene_Boundary always has refractive index of Air
3) n2 находится путем поиска на один объект вперед в списке iobject, например. в псевдокоде:
index = iobjects.index_of_object(obj1)
obj2 = iobjects[index+1]
n2 = obj2.refractive_index() # n2 = 1.5 e.g. Glass
4) Получить нормаль поверхности для последующего использования:
normal = obj1.surface_normal(ray)
У вас есть вся информация, необходимая для расчета правильного отражения и преломления. Это достаточно общее правило, чтобы работать, даже если луч находится вне объекта, но иногда мне нужно было реализовать некоторую логическую фильтрацию, чтобы сделать алгоритм более надежным, но это в основном все!
Отражение и преломление
Вы можете отразить вектор, просто зная нормаль поверхности. В Python с использованием numpy я делаю это так:
def reflect_vector(normal, vector):
d = numpy.dot(normal, vector)
return vector - 2 * d * normal
Преломление (как обсуждалось) требует значений n1 и n2:
def fresnel_refraction(normal, vector, n1, n2):
n = n1/n2
dot = np.dot(norm(vector), norm(normal))
c = np.sqrt(1 - n**2 * (1 - dot**2))
sign = 1
if dot < 0.0:
sign = -1
refraction = n * vector + sign*(c - sign*n*dot) * normal
return norm(refraction)
Наконец, вам нужно будет рассчитать коэффициент отражения для луча, где угол — это угол между направлением луча и нормалью к поверхности (предполагается, что ваш луч «неполяризованный»). Сравните это со случайным числом от 0 до 1, чтобы решить, происходит ли отражение.
def fresnel_reflection(angle, n1, n2):
assert 0.0 <= angle <= 0.5*np.pi, "The incident angle must be between 0 and 90 degrees to calculate Fresnel reflection."
# Catch TIR case
if n2 < n1:
if angle > np.arcsin(n2/n1):
return 1.0
Rs1 = n1 * np.cos(angle) - n2 * np.sqrt(1 - (n1/n2 * np.sin(angle))**2)
Rs2 = n1 * np.cos(angle) + n2 * np.sqrt(1 - (n1/n2 * np.sin(angle))**2)
Rs = (Rs1/Rs2)**2
Rp1 = n1 * np.sqrt(1 - (n1/n2 * np.sin(angle))**2) - n2 * np.cos(angle)
Rp2 = n1 * np.sqrt(1 - (n1/n2 * np.sin(angle))**2) + n2 * np.cos(angle)
Rp = (Rp1/Rp2)**2
return 0.5 * (Rs + Rp)
Заключительные комментарии
Все это взято из моего проекта оптической трассировки лучей на Python, который еще не выпущен (!), но вы можете проверить некоторые подробности здесь: http://daniel.farrell.name/freebies/pvtrace. Мне нравится Питон! Здесь перечислены несколько проектов трассировки лучей Python, http://groups.google.com/group/python-ray-tracing-community/web/list-of-python-statistical-ray-tracers. . Наконец, будьте осторожны с дробными показателями преломления в вашем примере, уравнение не сработает.
Обновить
Скриншот, на котором это реализовано в моем трассировщике лучей, доступен по адресу http://github.com/danieljfarrell/pvtrace < img src="https://i.stack.imgur.com/FVFPA.jpg" alt="альтернативный текст">
person
Daniel Farrell
schedule
03.01.2011