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

Я разрабатываю собственный движок, чтобы отработать математические навыки и подготовиться к математическому анализу. Да, мне уже 37 лет, а я всё ещё не знаю как пользоваться правильно дифференциальными уравнениями, хоть и понимаю из одной книги как это было важно для разных открытий, в том числе и для нахождения площади или длины кривых.

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

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

Я бы хотел рассказать как я бросил луч в сцену, на создание которого, а он ещё не доделан, я потратил 4 дня. Движок мой на C и поэтому думаю, что будет понятен каждому.

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

Нейросеть мне подсказала, что можно перемножить обратные (матрицу камеры и проекции) и умножить это на вектор и потом получается что-то, с чем я не понимал как работать и пошёл дальше своим путём.

Я научился немного исследовать разные закономерности, пока пытался это сделать и вот что я сделал. Мне надо было получить начало луча на экранных координатах и долгое время это не удавалось делать. Потом всё же я, с помощью дотошных вычислений определил, что есть некое число, которое должно быть на краю экрана и это число было -1.470588. Я не понимал как найти это число, ведь оно должно же откуда то браться. Таким образом я начал подгонять под результат и мне представилась формула, где можно сложить числа так, что получиться это число. В последствии я спросил у нейросети что это за формула, которую я вывел и она сказала, что это формула масштабирования экрана. Выглядит она так.

S=\frac{aspect + 1}{1 + aspect / 2}

Я обрадовался, это же классно, когда можно вывести какую-то формулу, пусть даже она и простая.

Таким образом я смог получить координаты точки по дальности в единицу от экрана.

Так я смог получать координаты по X и Y экрана, но дальше было ещё сложнее. Я не мог бросить отрезок дальше единицы. Я просто не понимал закономерность.

Я создал несколько лучей, чтобы определить что не так.

    make_ray_from_cursor (game, cam[CAM_MAIN], ray[0], rays[0], x, y, 2.f, 0.f);
    make_ray_from_cursor (game, cam[CAM_MAIN], ray[1], rays[0], x, y, 3.f, 0.1f);
    make_ray_from_cursor (game, cam[CAM_MAIN], ray[2], rays[0], x, y, 4.f, 0.2f);
    make_ray_from_cursor (game, cam[CAM_MAIN], ray[3], rays[0], x, y, 5.f, 0.3f);
    make_ray_from_cursor (game, cam[CAM_MAIN], ray[4], rays[0], x, y, 6.f, 0.4f);

по rays я пытался определить правильно ли падает луч, но не получалось, вечно были проблемы с углами. Да, я пытался использовать тригонометрию, чтобы всё это вычислить.

Итак, вот лучи, когда я кликаю у конца экрана.

лучи
лучи

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

часть кода была такой.

    int xx = x - game->sc->w / 2;
    int yy = game->sc->h / 2 - y;

    float aspect = game->sc->aspect;
    float h = game->sc->fw * 0.5f;
    float v = game->sc->fh * 0.5f;

    float null_vector[3] = {0.f, 0.f, 0.f};

    float kh = (aspect + 1.f) / (1.f + aspect / 2.f);

    float xxx = (float) xx / (h / kh);
    float yyy = (float) yy / (v * (aspect / kh));

    float v0[3];
    float v1[3];
    float d0[3];
    float d1[3];

    vec3_mul_scalar (v0, cam->front, z_far);
  
    v0[2] = xxx;

    vec3_copy (ray->origin, null_vector);
    vec3_copy (ray->dir, v0);
    ray->dir[1] = up_y;

    printf ("xxx: %f\n", xxx);

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

v0[2] = xxx * ((z_far + 1.f) * kh);

Это было ошеломительно. До этого я догадался, когда искал закономерности и видно было, что дистанция z_far больше на kh из предыдущей z_far.

ровные лучи в длину
ровные лучи в длину

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

xxx /= 2.f * kh;
Результат
Результат

И вуаля, теперь линии рисуются прямо по курсору. Что ж. По горизонтали я сделал. Осталось добавить работу по вертикали и луч будет бросаться прямо так как я задумал. Сначала сделаю, чтобы бросался из центра, а потом чтобы из точки курсора.

Вот полный код функции.

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)
{
	int xx = x - game->sc->w / 2;
	int yy = game->sc->h / 2 - y;

	float aspect = game->sc->aspect;
	float h = game->sc->fw * 0.5f;
	float v = game->sc->fh * 0.5f;

	float null_vector[3] = {0.f, 0.f, 0.f};

	float kh = (aspect + 1.f) / (1.f + aspect / 2.f);

	float xxx = (float) xx / (h / kh);
	float yyy = (float) yy / (v * (aspect / kh));

	xxx /= 2.f * kh;

	float v0[3];
	float v1[3];
	float d0[3];
	float d1[3];

	vec3_mul_scalar (v0, cam->front, z_far);
	v0[2] = xxx * ((z_far + 1.f) * kh);

	vec3_copy (ray->origin, null_vector);
	vec3_copy (ray->dir, v0);
	ray->dir[1] = up_y;

	printf ("xxx: %f\n", xxx);
#if 0


	vec3_mul_scalar (v0, cam->right, xxx);
	vec3_sub (d0, cam->front, v0);

	float r[3];
	float rr[3];

	vec3_mul_scalar (v1, cam->real_up, yyy);
	vec3_add (d1, d0, v1);

	vec3_copy (r, null_vector);

	vec3_copy (ray->origin, r);
	vec3_copy (ray->dir, d1);
#else

	ray->origin[0] = 0.f;
	ray->origin[1] = 0.f;
	ray->origin[2] = 0.f;
#endif

	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);
}

Что ж, надеюсь, что моя статья понадобиться кому-нибудь. Хотя мне кажется, что мой вариант не такой классный, как должен быть, ведь существуют методы бросания луча и скорее всего они лучшие из лучших. Тем не менее, я хочу творить и производить расчеты и создавать свой движок, на котором я потом буду делать игры.

Комментарии (5)


  1. FarhodN
    15.11.2025 03:17

    Непонятно. Что теперь модно хвастаться своим не знанием?


    1. xverizex Автор
      15.11.2025 03:17

      Я восхищаюсь наукой, но не получается в полной мере участвовать в её развитии. Разве я хвастаюсь? Хвастаются, это когда деляться успехами в какой-то области, разве нет? Описание того, чего я не знаю я не просто так привел. Я хотел показать, что уровень знаний у меня не очень большой и не смотря на это, я хочу развиваться. Это вдохновляющая статья должна быть, ведь здесь есть дух исследования. Очень жаль, что моя статья затронула лишь эти чувства в вас и заставила вас написать это. Думал, что будет лучше.

      Я например люблю один документальный фильм From Bedrooms to Billions: The Playstation Revolution и хотелось бы тоже сложными вещами в разработке игр заниматься. Очень классно, когда в те годы люди столкнулись с непростой задачей и смогли её решить. Это прям вдохновляет и я стремлюсь так же.


  1. Jijiki
    15.11.2025 03:17

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

    это число почти корень из 2 почти-почти-примерно(тоесть гипотенуза равностороннего треугольника - тут может между центром екрана краем окна и точкой взгляда - наверно-наверно,типо наверно в пределе корня из двух), потомучто корень из двух оператор корня может сократить наверно до 1 знака после запятой, а - не смогу обьяснить, какая-то мнимая компонента, надо изучать это)


    1. Jijiki
      15.11.2025 03:17

      если за стартовый-базовый эталон взять визуальное поведение Юнити - базовая сцена(там когда ставим кубик и плоскость и кубик над плоскостью), и соорудить такую же сцену с такими же еффектами(добавив своих VFX на своей сцене и другие моменты которые нужны), но на апи, то это вполне реализуемо, но да путь будет долгий из проб и ошибок

      это двойной свет(глобальный свет!(не иллюминейт) + локальный), триггер тень от локального света, еффекты, анимации, рендер, ригид/столкновения между моделями выбраными(слой столкновений), навигация. это примерно возможности 2008 года, самый пик


  1. tator
    15.11.2025 03:17

    Я не совсем понимаю почему автор не хочет искать информацию в книгах о том как используют математику в компьютерной графике. Ведь то что до вас доносят в книге лишь шаблоны которым вас обучают и объясняют как они работают и почему их используют. Не вижу ничего зазорного в том чтобы не уметь самому находить и выводить математические формулы для каждой своей потребности. Так не сложно и откатиться до выведения всех математических систем с 0