Всем привет. Недавно я занялся нахождением возможности бросить луч не используя классический метод с перемножением обратных матриц. Меня эта идея зацепила и я стал исследовать, возможно ли как-то сделать то же самое, но без обратных матриц. И вот что получилось. Есть видео и также описание код приложу в туториал.
Начнём с того, что нам понадобятся нормали сторон камеры. Я приведу свой пример из кода. Вот как выглядит получение нормалей всех сторон камеры. Код на C.
void camera_commit (struct camera *cam)
{
float p[3];
float f[3];
vec3_add (p, cam->pos, cam->front);
vec3_cross (f, cam->front, cam->up);
vec3_norm (cam->right, f);
vec3_cross (cam->real_up, cam->front, cam->right);
lookat(cam->view, cam->pos, p, cam->up);
}
Для вычислений позиции X на расстоянии нам понадобиться масштабирование экрана, которое вычисляется по формуле:
Идея очень простая и действенная. Мы берём фронт камеры и умножаем на нужную нам дальность.
vec3_mul_scalar (v0, cam->front, z_far);
Потом нам надо вычислять настоящие расстояния для ближней точки и для дальней.
float near_x = (float) xx / (h / kh);
float near_y = (float) yy / (v * (aspect / kh));
float far_x = (near_x) / (2.f * kh);
float far_y = near_y / 2.f;
float x0 = ((far_x * ((z_far + 1.f) * kh)));
float y0 = ((far_y * ((z_far + 1.f))));
z_far нам надо складывать с единицей. Честно сказать, я немного подзабыл некоторый замысел этих вычислений, я просто несколько дней не вылазил из расчетов. Но на единицу я прибавил, чтобы вроде как увеличить коэффициент для дальности и соответственно для правильного расположения координат.
Потом сдвигаем пр��вую и верхнюю нормаль камеры в стороны:
vec3_mul_scalar (v1, cam->right, x0);
vec3_add (v2, v0, v1);
vec3_mul_scalar (v1, cam->real_up, y0);
vec3_sub (v0, v2, v1);
vec3_copy (ray->dir, v0);
vec3_mul_scalar (v1, cam->right, near_x);
vec3_mul_scalar (v2, cam->real_up, near_y);
vec3_add (v3, cam->front, v1);
vec3_sub (v0, v3, v2);
vec3_copy (ray->origin, v0);
ray->dir это направление. ray->origin это точка начала. Думаю по коду можно разобраться что здесь происходит.
И всё, луч готов.
Приведу полный код функции, которую конечно же надо немного подправить, так как я тестировал на ней другие лучи вместе.
void make_ray_from_cursor (struct game *game, struct camera *cam, struct ray *ray, struct ray *check_ray, int x, int y, float z_far, float up_y)
{
float v0[3];
float v1[3];
float v2[3];
float v3[3];
float d0[3];
float d1[3];
int xx = x - game->screen->w / 2;
int yy = game->screen->h / 2 - y;
float aspect = game->screen->aspect;
float h = game->screen->fw * 0.5f;
float v = game->screen->fh * 0.5f;
float null_vector[3] = {0.f, 0.f, 0.f};
float kh = (aspect + 1.f) / (1.f + aspect / 2.f);
float near_x = (float) xx / (h / kh);
float near_y = (float) yy / (v * (aspect / kh));
float far_x = (near_x) / (2.f * kh);
float far_y = near_y / 2.f;
float x0 = ((far_x * ((z_far + 1.f) * kh)));
float y0 = ((far_y * ((z_far + 1.f))));
vec3_mul_scalar (v0, cam->front, z_far);
vec3_mul_scalar (v1, cam->right, x0);
vec3_add (v2, v0, v1);
vec3_mul_scalar (v1, cam->real_up, y0);
vec3_sub (v0, v2, v1);
vec3_copy (ray->dir, v0);
vec3_mul_scalar (v1, cam->right, near_x);
vec3_mul_scalar (v2, cam->real_up, near_y);
vec3_add (v3, cam->front, v1);
vec3_sub (v0, v3, v2);
vec3_copy (ray->origin, v0);
ray->pos[0] = cam->pos[0];
ray->pos[1] = cam->pos[1];
ray->pos[2] = cam->pos[2];
opengl_ray_setup (ray);
translate (ray->transform, cam->pos[0], cam->pos[1], cam->pos[2]);
float view[16];
mat4x4_mul (view, ray->transform, cam->view);
mat4x4_mul (ray->model, view, ray->projection);
}
Таким образом мы теперь знаем ещё один способ бросить луч и мне кажется, что это будет работать быстрее, чем мой способ нахождения двух обратных матриц, а потом и их перемножение.
Комментарии (9)

Jijiki
16.11.2025 04:59вы молодец, но просто надо протестировать на 3д сцене, в разных положениях камеры, потом надо быть уверенным правильно ли вы считаете обратную матрицу.
синк матрикс показывал трик как обходить обратку тоже помню, я щас не парюсь у меня стоит обратка, причем если это выбор, можно пустить наверно луч от обьекта микро хак(но я не уверен в правильности этого)
ну мы вытаскиваем матрицу наружу, да, и считаем векторами тоже самое, так и в какойто библиотеке обратная матрица частично может быть посчитана векторами
и ведь еще есть sse4.2 avx2 если поддерживается можно с этим компилировать
Скрытый текст
// 1. Нормализованные координаты float xNDC = (mouseX / win->width) * 2.0f - 1.0f; float yNDC = 1.0f - (mouseY / win->height) * 2.0f; // 2. точка в NDC у Near plane glm::vec4 clipCoords = glm::vec4(xNDC, yNDC, -1.0f, 1.0f); glm::mat4 invVP = glm::inverse(projection * view); glm::vec4 worldCoords = invVP * clipCoords; worldCoords /= worldCoords.w; // 3. луч glm::vec3 rayOrigin = worldCoords; // glm::vec3 rayDir = glm::normalize(glm::vec3(worldCoords) - rayOrigin)*200.0f; glm::vec3 temp = camera->cameraFront * 2000000.f; Ray ray = CreateRay(worldCoords, temp);но у меня выбор центральным дескриптором экрана модельку
кстати, а если игра от третьего лица, камера прикреплена к модельке

xverizex Автор
16.11.2025 04:59Спасибо. Мой вариант вообще без использования матриц. Я нашел способ, чтобы работать только с нормалями камеры.
xverizex Автор
Похоже, туториал вышел не очень, раз минусуют. А жаль. Я так старался, пять дней потратил на расчеты и вроде как нашел новый способ бросания луча.
Sadler
Народ на Хабре очень разношёрстный, и не всем очевидно, зачем это нужно, вряд ли многие вычитывали и верифицировали эту работу: насколько оно работает, и работает ли эффективно. Лично я стараюсь юзать готовые движки и утилиты, чтобы не уходить в дебри векторной геометрии, кватернионов и всего прочего без необходимости. Поэтому для меня raycast -- это либо GetWorld()->LineTraceSingleByChannel , либо StartShapeTestLosProbe, за исключением случаев, когда нужно что-то совсем уж нестандартное. Из кастома в последний раз рисовал расчёт ближайшего расстояния (и точки соприкосновения) от произвольной точки до bounding box в мировых координатах, это максимум, что мне не лениво было накодить :).
Плюсанул: поддерживаю желание работать и что-то оптимизировать на низком уровне, хотя тоже внимательно не вычитывал, сорян.
OldFisher
Туториал и правда вышел не очень. Будь в нём некоторое введение, чёткая постановка задачи и описание идеи, лежащей в основе предложенного способа, он был бы намного лучше. Отсутствие у читателя в голове того контекста, что есть у автора и который почему-то кажется ему само собой разумеющимся, не очень-то помогает понять уже готовое решение, запечённое в функцию, также лишённую контекста.
Насчёт самой решаемой проблемы тоже можно сказать пару вещей.
Во-первых, неплохо бы дать оценку эффективности предложенного метода: сколько операций он позволяет сэкономить, как эта экономия соотносится с имеющимися затратами на вычисление проекции и как - со всеми затратами на отрисовку кадра.
Во-вторых, трассировкой лучей занималось уже немало хороших грамотных специалистов и весьма возможно, что они уже придумали хорошие способы повысить эффективность и в этом вопросе. Может быть, стоило поискать эти решения. Возможно, они даже превосходят предлагаемое (а вопрос их сравнения тем временем возвращает нас к "во-первых").
Jijiki
в основном лучи пускают как я понял прямо в шейдере, лучше наверно пускать в компута шейдере, соотв всё в шейдере, на процессоре дороговато может быть, кто-то я видел и в картинку на SDL пускает, но там нюансов много
у вулкана есть ускоряющий слой рей трейса так же
ну из нюансов proj*view можно предподсчитывать, чтобы не повторять этот счет, но на лоу среднем оборудовании без трассировки не ощутимо с террейном, игроком на камере от 3 лица, и двумя движущимися анимированными модельками, с ускорением и деревьями там же помимо лучей, надо ускоряющее дерево будет еще(типо куб-сфера площадь)
какие-то подходы могут оч быстро работать, всё зависит от того, будет ли желание поддерживать такое решение, например математику можно написать свою, но тогда придётся позаботиться, чтобы её использование было удобное и не запутанное, поэтому чаще люди просто используют глм, или как прототип или как потестить, просто перенос всего мат аппарата долгий и затратный, еще бекапы надо делать, а тут либа доступная )
xverizex Автор
Как в шейдере кстати пускать луч я не знаю или не совсем понимаю что вы имеете ввиду, так как у меня сам луч рисуется в шейдере, но вычисляется начальная точка и конечная на CPU. Без матриц.