Comments 62
id Software по-прежнему использует систему координат из игры Wolfenstein 3D 1992 года

Это не система координат Wolfenstein. Это обычная классическая система координат, где Z — это высота.
У вас разве на геометрии в школе Y высотой был в трехмерном пространстве?
Ось Y = верх/низ

Так вот откуда в KOMPAS-3D это извращение, там как раз используют OpenGL, все школьники и студенты привыкли что Z — высотная координата, но как только ты стал инженером — забудь все чему тебя учили.

Не всегда! Сначала оси Z — вообще не было в компьютерной графике. Отсюда и Y в качестве высоты, а когда появилась трёхмерность, то Z стала глубиной.


Вся суть в том, что:


  • в геометрии, алгебре, физике итп: XY — это плоскость стола, и потому Z считается высотой от стола
  • а в компьютерной графике: XY — это плоскость монитора, и потому Z считается глубиной от экрана
геометрии, алгебре, физике итп: XY — это плоскость стола,

не стола, а доски
В большинстве серьезного 3д-софта используется Y-up: Maya, XSI, Houdini, Nuke, итд.
А из софта с Z-up я могу вспомнить только 3ds max и Unreal Engine.

Самому интуитивно Z-up, но не из соображений школьной геометрии, а т.к. начинал с макса еще в школе (т.е. я с ним около 15 лет уже) — синдром утенка, как-никак.

А Y-up сложилось чисто исторически в 3д-софте, т.к. если смотреть через экран компьютера, то X и Y — координаты плоскости, а вот Z — глубина в экран.
Очень интересно! Только непонятно, зачем использовать GDI в оконном режиме, если в нём также можно использовать DirectDraw. Наверное, это сделали чтобы не связываться с вариациями цветовых настроек у пользователей, хотя, при этом должно было немного упасть быстродействие.
Это сейчас всё так просто кажется, а тогда с картами вроде S3 Trio 64 не всё так однозначно было.
Оконный режим и потом, уже после перехода к 3D, много лет головной болью был.
Так GDI в любом случае медленнее работает, чем прямая запись через DirectDraw (с блиттером или без). Просто, в оконном режиме в DirectDraw нужно знать, какой формат представления цвета выбран в настоящий момент и рисовать именно в этом формате (а форматов этих довольно много, вплоть до палитровых). В случае же использования GDI все преобразования (с потерей производительности) выполнит сама Windows. Я так думаю, просто оконный режим был сделан «для галочки».
GDI работает медленно, но он железно будет работать. DirectDraw же на древних видеокартах и в фуллскрине-то не всегда толком работал (емнип, там ещё и количество видеопамяти имело значение, а карточки с 1 Мб (когда _вся_ она тратилась на экранный буфер) были не редкостью).
А что конкретно могло не работать в DirectDraw, тем более в полноэкранном режиме? Я такого не встречал даже на S3, которая для PCI.

Там ведь всё рисование-то:
LPDIRECTDRAWSURFACE7 lpddssecondary->Lock(NULL,&ddsd,DDLOCK_SURFACEMEMORYPTR|DDLOCK_WAIT,NULL);
UINT* Video_Buffer=(UINT *)ddsd.lpSurface;
 lPitch32=ddsd.lPitch>>2;
 //рисуем
 UINT *vptr=Video_Buffer;
 UINT *sptr=ScreenBuffer;
 for(y=0;y<480;y++,vptr+=lPitch32)
 {
  UINT *vptrx=vptr;  
  for(int x=0;x<640;x++,vptrx++,sptr++) *(vptrx)=*(sptr);
 }
 lpddssecondary->Unlock(NULL);
 lpddsprimary->Flip(NULL,DDFLIP_WAIT);


Обычный проход по памяти с заполнением пикселя (как в MS-DOS). Большего в Q2 и не требовалось. А тут даже блиттер не нужен.

А вот на I740 OpenGL глючил в Q2 — это я хорошо помню. :)
Не смотря на всё это, портов Quake 2 можно пересчитать по пальцам (в данном случае, имеется в виду активно развивающиеся): quake2xp, q2pro, и yquake2 (вот мнение автора yquake2 по поводу самого движка). Это на случай, если кто-то желает переиграть после прочтения статьи.
да, в 2002-2004 движков было гораздо больше. Хотя, если смотреть с сегодняшней точки зрения, То что умерло, туда ему и дорога. Хотя кмКу2 многое в себя из тех разработок втянул. Наверное, по этому он и выглядит до сих пор так допотопно, вызывая аналогии с ку2макс (что и не удивительно, тк кода от туда была взята куча.)
Чтоб вы понимали, насколько круто сделан программный растеризатор:
— тратится всего 4 такта процессора Pentium на пиксель экрана (при условии всех кэш попаданий, конечно)
еще раз: каждые 4 такта движок выдавал пиксель на экране, т.е. вычислялись координаты нужного текселя в текстуре и с учетом всех преобразований нужный цвет записывался в видеопамять.

На самом деле, если брать среднее значение, то тратилось, конечно, больше чем 4 такта. 64 такта тратилось на серию из 16 пикселей. Дело в том, что для корректного перспективного проецировния текстуры в экран, требуется хотя бы одно деление на каждый пиксель. Деление во времена Pentium занимало немало. Что-то порядка 40 тактов (точно уже и не упомню). Поэтому все движки того времени так или иначе линейно интерполировали результаты делений на серию пикселей, обычно на 16. Визуально это выглядело, как лесенка, когда плоскость полигона с текстурой была почти параллельна направлению взгляда. Так вот, еще один трюк, который меня в свое время очень впечатлил: движок запускал операцию деления на сопроцессоре и в это время выдавал 16 пикселей, линейно интерполируя результат предыдущего деления. Т.е. 64 такта на 16 пикселей и 40 тактов на деление в итоге отрабатывали параллельно. Это было очень круто.
Кстати, по поводу GL_LIGHTING. В 2004 году написал я вот такой вот движок: ссылка
В нём нет ни одной карты освещённости. Он использует только GL_LIGHTING. Фишка в том, что можно для каждой грани выбрать 8 источников света (самых главных) и для них выполнить разбиение грани на компоненты, в которых из этих 8 источников некоторые включены, а некоторые выключены (не освещают эти компоненты). Что это даёт на практике? Чёткое цветное освещение с замечательными тенями и при этом любой источник можно динамически изменять прямо в процессе работы программы. Насколько я понимаю, нигде больше такой подход никто не применял — я ничего подобного не встречал. И вот последние 13 лет я всё никак не допишу статью со всей математикой для таких движков. :)
Выложите скрины, чтоли?)
А вообще на первый взгляд по описанию похоже на вертексное освещение.
В нём нет ни одной карты освещённости. Он использует только GL_LIGHTING… Насколько я понимаю, нигде больше такой подход никто не применял — я ничего подобного не встречал.

У меня в освещение через GL_LIGHTING http://www.moddb.com/mods/wolfgl-3d
Правда, пока без теней и цвета.


В будущем планируется добавить ещё два режима на выбор:


  • аппаратное освещение Ламберта (входит в стандарт OpenGL и поддерживается видеокартами)
  • шейдеры

PS посмотрел Ваш движок, водичка в стиле Unreal — очень впечатляет!

Водичка — обычная текстура. :) Да, текстуры собирались отовсюду. :)
Прикольно. :) Только почему такой FPS низкий? У меня такой FPS софтверный движок мой даёт. :)
Кстати, если интересно, вот ещё мои движки, который под MS-DOS, он 2002 года с мелкими модификациями в 2006 и 2015. И это полноценная игра с графикой из Doom (движок тоже на BSP, но разбиение до линий, а не полигонов). А который для Windows написан прошлым летом, когда я решил этот MS-DOS движок переделать в отдельный движок. :) Дальше энтузиазм угас. :)

Ссылка

Скриншот:


Хороший свет. Немного разрушает погружение неровный спекулар(



Я так понимаю, вы нарочно Point Light вперед от лампочки выдвинули? Сделайте скрин, если бы Лайты стояли на уровне лампочек, пожалуйста.
Да, медия не особо блещет, особенно учитывая что используется орен-наяр с ггх спекулем. А лампочки отодвинуты специально, чтобы тени уродской позади не было. Попозже отодвину к стенке, сразу будет ясно, что отодвинуты они не просто так. Вообще надо было давно сделать как в дум3 разделенный центр лайта и его зона действия.
вот подвинул источники к стене. Получилось как и ожидалось гадость.
image
Да, здоровые безобразные тени))) А так блики меня до того устраивали. Они в отличии от блин-фонга, реально круто освещают сурфы
меня не устраивают чудовищные тени если лайт близко к стене.
В идеале необходимо избежать и чудовищных теней и чудовищных бликов. Это, например, хорошее решение:
как в дум3 разделенный центр лайта и его зона действия.

Ну добавить в движок это легко, но вот переосвещать всю игру и оба мишен пака, ну его нафиг.
в ку2волд это использовали как дополнение к освещению моделей. Я о ку2 именно говорю.
Я примерно тогда же игрался с этим, На выходе фигня. — честный ппл лучше…
Фишка в том, что можно для каждой грани выбрать 8 источников света (самых главных) и для них выполнить разбиение грани на компоненты, в которых из этих 8 источников некоторые включены, а некоторые выключены (не освещают эти компоненты).

Правильно ли я понимаю, что перед рисованием группы полигонов Вы включаете и выключаете источники освещения, а затем проделываете это со следующей группой полигонов?

Там вот что делается:
1) Рисуется карта. Исходные многоугольники являются выпуклыми (пол+потолок+стены).
2) Берётся полигон и определяются главные для него источники света (по яркости). Таких источников максимум 8 штук (гарантируется OpenGL не менее 8).
3) Для этого полигона перебираем ВСЕ остальные полигоны и для каждого источника света из выбранных главных строим тень на исходный полигон от остальных. Получаем для каждого исходного полигона набор полигонов теней, для каждого из которых известно, какие источники света отключены, а какие включены.
4) Дальше уже движок просто включает или отключает источники света при отрисовке полигонов. И всё.

Вот отдельный модуль генерации теней: ссылка
гарантируется OpenGL не менее 8

У меня на старой видеокарте при включении всех 8 источников начинались глюки.

2) Берётся полигон и определяются главные для него источники света (по яркости). Таких источников максимум 8 штук (гарантируется OpenGL не менее 8).
3) Для этого полигона перебираем ВСЕ остальные полигоны и для каждого источника света из выбранных главных строим тень на исходный полигон от остальных. Получаем для каждого исходного полигона набор полигонов теней, для каждого из которых известно, какие источники света отключены, а какие включены.
4) Дальше уже движок просто включает или отключает источники света при отрисовке полигонов. И всё.

Офигительная идея! Даже не предполагал, что в одном проходе можно указывать разные источники света для каждого полигона.

Блин, в файле shadows.h мусор от предыдущей версии. :) И оно компилируется!

Должно быть так:

#ifndef SHADOW_H
#define SHADOW_H

#define S_EPS 0.01
#define S_EPS1 0.0000001
#define S_EPS2 0.0001

#include "polygon.h"

long GetPointPositionPlane(SPoint sPoint_A,SPoint sPoint_Plane,SVector sVector_Normal);
bool GetLineIntersectionPlane(SPoint sPoint_A,SPoint sPoint_B,SPoint sPoint_Plane,SVector sVector_Normal,long mode,SPoint &sPointReturn);
long GetPointPositionInPlaneXZ(SPoint sPoint_A,SPoint sPoint_B,SPoint sPoint);
bool IsPointInPolygonXZ(SPoint sPoint,CPolygon cPolygon);
bool GetPieceIntersectionInPlaneXZ(SPoint A,SPoint B,SPoint C,SPoint D,SPoint &sPointReturn);
bool GetLineIntersectionPieceInPlaneXZ(SPoint A,SPoint B,SPoint C,SPoint D,SPoint &sPointReturn);
bool IsPointInPolygon(SPoint sPoint,CPolygon cPolygon);
bool GenerateShadow(CPolygon cPolygon_ShadowSource,CPolygon cPolygon_ShadowDestination,SPoint sPoint_Lighting,vector<CPolygon> &vector_CPolygon_Fragment);

#endif


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

Даже не предполагал, что в одном проходе можно указывать разные источники света


Честно говоря, я не понял, что вы имели в виду. :)
Вот нагляднее, как разбивается исходный полигон каждым другим для каждого источника света. И каждый фрагмент знает, какими источниками он освещён.

Вот эта функция по исходному полигону cPolygon_ShadowDestination, полигону, отбрасывающему тень cPolygon_ShadowSource, положению источника света sPoint_Lighting формирует набор фрагментов исходного полигона в vector_CPolygon_Fragment. А дальше каждый фрагмент является новым исходным полигоном для уже следующего источника света (источниками теней лучше фрагменты не брать — бессмысленно).

bool GenerateShadow(CPolygon cPolygon_ShadowSource,CPolygon cPolygon_ShadowDestination,SPoint sPoint_Lighting,vector &vector_CPolygon_Fragment);

Там всё очень просто. Главное, что этот набор фрагментов нужно построить на этапе подготовки карты в редакторе. А в самом движке только использовать, не выполняя никаких пересчётов теней, а только меняя характеристики источников света (но не координаты).

В принципе, для одного наиболее сильного источника света можно пересчитывать поправки для координат тени, если источник света будет "слегка покачиваться", или переместится не слишком далеко.

Там при перемещении источника света может кардинально поменяться набор фрагментов. Поэтому, лучше не трогать координаты источника света. А в реальном времени это вычислять очень дорого.
Надо попробовать собраться и хоть что-то оформить, как статью. :) Вот сейчас смотрю код и нифига не понимаю, вот зачем нужно добавлять к полигону точек тени точки пересечения теневого объёма с плоскостью полигона, на который падает тень. и которые находятся внутри полигона, на который падает тень. Комментирую фрагмент — видны ошибки работы с некоторых случаях. :) Склероз — штука страшная. :)
Поэтому, лучше не трогать координаты источника света. А в реальном времени это вычислять очень дорого.

Простите, а какой тогда смысл? Я думал, это дешевые реалтайм источники света и подумал: «о, это клево». А если это бейкд, то лучше уж тогда лайтмапы — они дают очень красивые результаты

Не двигать — не значит "не выключать!" :)
Произвольно включаемые и выключаемые источники света — это тоже динамическое освещение, даже если они и не меняют координаты.

А смысл в том, что карты освещения не позволяют очень просто менять, например, цвет источника света и не позволяют строить геометрически чёткие тени, да и памяти требуют дофига. И напомню, что всё это делалось в 2004 году, когда у меня был GeForce MX 440. :) И никаких пиксельных шейдеров. И в том же Doom-3 на тот момент вообще нигде не использовалось динамическое корректное цветное освещение (со всеми полутонами при переналожении теней).

Кстати, почти дописал статью. :) Либо здесь, либо на geektimes отправлю. :)
Не могу. :) Посмотрите год в файле. 2008! Это был последний раз, когда я ещё собрался и написал пару формул. Дальше я даже не прикасался к статье. И все формулы давно забыты, как и курс линейной алгебры. :( В общем, нифига не могу написать уже. :)
А вот модуль разбиения и движок остался. :) transit gloria mundi :)
Вот получающаяся картинка. :)



Да, освещение по вершинам — оно штатное в OpenGL и оно и используется. Только все эти вершины получены трассировкой лучей от источников света на этапе разбиения карты.

В самом движке: F1-F4 — управление рендерингом, пробел — открыть дверь, курсор — управление, enter-вычислить FPS (записывается в файл).

Сам движок по сути плоский как Doom — я в 3D карту нарисовать не могу и редактор такой не смог написать — не представлял на тот момент, как оно должно выглядеть. Да и для физического движка удобно работать с плоскостью. :)
Невозможно открыть рабочую среду в Visual Studio 2010. Необходимо использовать VS 2008.
Печально. Неужели так сложно поддерживать обратную совместимость? Судя по тенденциям в моей любимой Mac OS X, скоро прошлогодние файлы в новом Xcode нельзя будет нормально открыть.
Классная статья, автору +
Я как-то пытался разобратся тоже в коде, но уровень не позволил)
Вы сравнивали свои находки с книгой Борескова «Графика трехмерной компьютерной игры на основе OpenGL»? По-моему у него был самый глубокий анализ движка quake в 2000х.
Полагаю, лучше задавать вопросы автору оригинала, тем более, что переводчик похоже комментарии не читает.
Спасибо за труд! Не перестаю восхищаться Кармаком.

Видео с релиза QuakeWorld тоже шикарное. Особенно понравился кот на 8:15 и интервью с геймерами.

«If you stay underwater too long, real physics apply and you start gasping for air. If you fall into lava, you won't last very long—just like in real world!»

«When we finally got it… Let's say our girlfriends didn't see us for quite some time. They're Quake widows.»
Only those users with full accounts are able to leave comments. Log in, please.