Подтвердить что ты не робот

Быстрые равномерно распределенные случайные точки на поверхности единичного полушария

Я пытаюсь создать равномерные случайные точки на поверхности единичной сферы для программы трассировки лучей Монте-Карло. Когда я говорю "равномерное", я имею в виду, что точки равномерно распределены по площади поверхности. Моя нынешняя методология заключается в вычислении однородных случайных точек на полусфере, указывающих на положительную ось z и базу в плоскости x-y.

Случайная точка в полусфере представляет собой направление излучения теплового излучения для диффузного серого излучателя.

Я получаю правильный результат, когда использую следующий расчет:

Примечание: dsfmt * возвращает случайное число от 0 до 1.

azimuthal = 2*PI*dsfmt_genrand_close_open(&dsfmtt);
zenith = asin(sqrt(dsfmt_genrand_close_open(&dsfmtt)));

// Calculate the cartesian point
osRay.c._x = sin(zenith)*cos(azimuthal); 
osRay.c._y = sin(zenith)*sin(azimuthal);
osRay.c._z = cos(zenith);

Однако это довольно медленно, и профилирование показывает, что на него приходится большая часть времени выполнения. Поэтому я искал альтернативные методы:

Метод отклонения Марсалья 1972 года

do {
   x1 = 2.0*dsfmt_genrand_open_open(&dsfmtt)-1.0;
   x2 = 2.0*dsfmt_genrand_open_open(&dsfmtt)-1.0;
   S = x1*x1 + x2*x2;
} while(S > 1.0f);


osRay.c._x = 2.0*x1*sqrt(1.0-S);
osRay.c._y = 2.0*x2*sqrt(1.0-S);
osRay.c._z = abs(1.0-2.0*S);

Расчет аналитических декартовых координат

azimuthal = 2*PI*dsfmt_genrand_close_open(&dsfmtt);
u = 2*dsfmt_genrand_close_open(&dsfmtt) -1;
w = sqrt(1-u*u);

osRay.c._x = w*cos(azimuthal);
osRay.c._y = w*sin(azimuthal);
osRay.c._z = abs(u);

Хотя эти последние два метода работают с серварами быстрее, чем первые, когда я их использую, я получаю результаты, которые указывают на то, что они не генерируют однородные случайные точки на поверхности сферы, а скорее дают распределение, которое способствует экватору.

Кроме того, последние два метода дают одинаковые конечные результаты, однако я уверен, что они неверны, поскольку я сравниваю их с аналитическим решением.

Каждая найденная ссылка указывает, что эти методы производят равномерные распределения, но я не достигаю правильного результата.

Есть ли ошибка в моей реализации или я пропустил фундаментальную идею во втором и третьем методах?

4b9b3361

Ответ 1

Самый простой способ создания равномерного распределения на единичной сфере (независимо от его размерности) состоит в том, чтобы нарисовать независимые нормальные распределения и нормализовать результирующий вектор.

Действительно, например, в размерности 3 e ^ (- x ^ 2/2) e ^ (- y ^ 2/2) e ^ (- z ^ 2/2) = e ^ (- (x ^ 2 + y ^ 2 + z ^ 2)/2), поэтому совместное распределение инвариантно вращением.

Это быстро, если вы используете быстрый генератор распределения (Ziggurat или Ratio-Of-Uniforms) и быструю нормализацию (google для "быстрого обратного квадратного корня" ). Не требуется трансцендентного вызова функции.

Кроме того, Марсалья не является однородным на полусфере. У экватора будет больше точек, так как точка соответствия на 2D-диске, точка на полусфере не является изометрической. Последнее кажется правильным, хотя (однако я не делал расчет, чтобы убедиться в этом).

Ответ 2

Если вы берете горизонтальный срез единичной сферы с высотой h, ее площадь поверхности равна 2 pi h. (Так Архимед вычислил площадь поверхности сферы.) Таким образом, z-координата равномерно распределена в [0,1]:

azimuthal = 2*PI*dsfmt_genrand_close_open(&dsfmtt);
osRay.c._z = dsfmt_genrand_close_open(&dsfmtt);

xyproj = sqrt(1 - osRay.c._z*osRay.c._z);
osRay.c._x = xyproj*cos(azimuthal); 
osRay.c._y = xyproj*sin(azimuthal);

Кроме того, вы можете сэкономить некоторое время, вычислив cos(azimuthal) и sin(azimuthal) вместе - см. этот вопрос о стеке для обсуждения для обсуждения.

Отредактировано для добавления: ОК, теперь я вижу, что это всего лишь небольшая настройка вашего третьего метода. Но он вырезает шаг.

Ответ 3

Вы пытались избавиться от asin?

azimuthal = 2*PI*dsfmt_genrand_close_open(&dsfmtt);
sin2_zenith = dsfmt_genrand_close_open(&dsfmtt);
sin_zenith = sqrt(sin2_zenith);

// Calculate the cartesian point
osRay.c._x = sin_zenith*cos(azimuthal); 
osRay.c._y = sin_zenith*sin(azimuthal);
osRay.c._z = sqrt(1 - sin2_zenith);

Ответ 4

Это должно быть быстрым, если у вас быстрый RNG:

// RNG::draw() returns a uniformly distributed number between -1 and 1.

void drawSphereSurface(RNG& rng, double& x1, double& x2, double& x3)
{
    while (true) {
        x1 = rng.draw();
        x2 = rng.draw();
        x3 = rng.draw();
        const double radius = sqrt(x1*x1 + x2*x2 + x3*x3);
        if (radius > 0 && radius < 1) {
            x1 /= radius;
            x2 /= radius;
            x3 /= radius;
            return;
        }
    }   
}

Чтобы ускорить его, вы можете переместить вызов sqrt внутри блока if.

Ответ 5

Я думаю, что проблема, которую вы испытываете с неравномерными результатами, состоит в том, что в полярных координатах случайная точка на окружности неравномерно распределена на радиальной оси. Если вы посмотрите на область на [theta, theta+dtheta]x[r,r+dr], для фиксированных theta и dtheta, область будет отличаться от разных значений r. Intuitivly, есть "больше области" дальше от центра. Таким образом, для этого необходимо масштабировать свой случайный радиус. У меня нет доказательства, лежащего вокруг, но масштабирование r=R*sqrt(rand), при этом r является радиусом круга и rand начинает случайное число.

Ответ 6

Второй и третий методы фактически создают равномерно распределенные случайные точки на поверхности сферы со вторым методом (Marsaglia 1972) обеспечивая наивысшие времена работы примерно в два раза быстрее на четырехъядерном процессоре Intel Xeon с тактовой частотой 2,8 ГГц.

Как отмечено Alexandre C, существует дополнительный метод, использующий нормальное распределение, которое расширяется до n-сфер лучше, чем методы, которые я представил.

Эта ссылка даст вам дополнительную информацию о выборе равномерно распределенных случайных точек на поверхности сферы.

Мой первоначальный метод, обозначенный TonyK, не создает равномерно распределенных точек и, скорее, смещает полюсы при создании случайных точек. Это необходимо из-за проблемы, которую я пытаюсь решить, но я просто предположил, что она будет генерировать равномерно случайные точки. Как предложено Pablo, этот метод можно оптимизировать, удалив вызов asin(), чтобы сократить время выполнения примерно на 20%.

Ответ 7

Первая попытка (неправильная)

point=[rand(-1,1),rand(-1,1),rand(-1,1)];
len = length_of_vector(point);

Редакция:

А что?

while(1)
 point=[rand(-1,1),rand(-1,1),rand(-1,1)];
 len = length_of_vector(point);
 if( len > 1 )
     continue;
 point = point / len
     break

Acception здесь приблизительно 0,4. Это означает, что вы откажетесь от 60% решений.