Продолжаем баловаться с графикой

Добавлен , опубликован
Программирование
Язык:
C++
Давненько я вам не писал про свои разработки в плане графики. Не так ли? Так вот решил продолжить цикл записей. Кто не в теме, о чем я, бегом читать это. Ну ладно начнем.

Начало

Всем доброго времени суток, дорогие читатели =)
Все началось с того, что я дошел до такой стадии написания кода, что понял, что где то в архитектуре у меня косяк. Да это тяжело признавать, но лучше раньше, чем поздно. В общем пришлось очень много думать над новой архитектурой всего приложения. В общем пересмотрев еще кучу уроков, сдк и различных документаций, до меня доперло, где пошел косяк.
В общем пришлось долго и упорно переписывать кучу кода (ушло примерно дня 2 на написание и еще с пол дня наверное на отладку косяков линковки). Ну в общем после этого я сразу почувствовал разницу между текущем кодом и прошлым. С новой архитектурой как ни странно удалось даже повысить фпс. Однако был переписан еще не весь код нужных частей, но на данный момент они являются не столь важными в плане создания.
После всего этого решил доделать последний тип источника освещения, это фонарь (SpotLight). Вооружившись бумагой, ручкой, а так же здравым смыслом (хотя какой может быть здравый смысл, когда берешься за матан, хоть и элементарый =)), пытался разобраться по какому принципу меняется освещенность каждого из участка освещаемой зоны. Формула получилась довольно таки большой. Кому интересно, добро пожаловать под спойлер.
раскрыть
float alpha = acos(dot(pointDirection, lightDirection)/(length(pointDirection)*length(lightDirection)));
float phi = Light[Idx][2][3];
float theta = Light[Idx][1][3];
if (alpha <= phi)
{
	float intensive = dist / (A[0] + A[1]*dist + A[2]*pow(dist, 2.0f));
	if (alpha >= theta && alpha <= phi)
		intensive *= 1.0f - (alpha - theta) / (phi - theta);
	float3 ldir = normalize(position - input.position);
	return float4(
		Color[0] * intensive +
		Color[1] * intensive * dot(ldir, input.normal)
		, 0.0f);
}
Результат действия данного шейдера можно увидеть на скриншоте слева. На нем изображены 3 источника освещения данного типа. Два расположены на стене, имеют зеленый цвет, а так же смотрят в противоположные стороны. Так же они еще и вращаются вокруг оси Z. Получается что-то типа сирены =). В общем выглядит достаточно забавно. Еще один источник направлен на стену и дает слегка сероватое освещение. Его направление регулируется с помощью стрелок на клавиатуре, так же забавно смотрится.

Еще немного о добавленном

Кроме того решил я еще побаловаться и с пост проссингом. Решил написать простенький пост эффект в несколько проходов. Первый проход разбивает картинку на кусочки в виде шахматной доски. А второй проход заменяет черный на белый. Было огромное разочарование в том, что я не увидел результата второго прохода. Пришлось подумать над данной проблемой. В результате пересмотрев код выполнения пост процессинга в коде приложения заметил, что я постоянно обрабатывал одну и ту же текстуру, исходную, с исходной сценой. А при выводе второго прохода на экран, точки просто не проходили тест на глубину. В общем получилась такая вот биллебердистика. Что пришлось сделать. Создать новую временную текстуру куда бы рисовалась картинка, а потом её копировать в исходную текстуру. Да идея хорошая, но к сожалению копирование текстуру из одного участка памяти в другой занимает достаточное кол-во FPS. В общем после этого все встало на свои места и пост процессинг шел как надо, но вот FPS был жутко низок. Но заметив одну слабость в этом алгоритме, мне удалось поднять чуточку FPS (В этом алгоритме я использовал Z-буффер, и каждый раз его очищал перед новым проходом. Эту проблему решил отключением данного буфера на время рендера очередного квада с текстурой результата работы пост эффекта). Главное уже работает, а оптимизировать еще смогу, к тому же уже даже представляю как это можно переделать, но пока что не до этого.

Тени

Да я хотел бы уделить достаточно текста этому пункту. На это было потрачено очень много нервов, а так же времени. Начнем с того, что все началось с выбора техники построения теней. Первое, что мне попалось это обычные Shadow Maps. В принципе технология простая и не требует длительного процесса написания. Но нервов было потрачено больше, нежели написано строк кода. Начнем с того, что требуется вывести буфер глубины сцены в текстуру. Окей ладно, это просто. Устанавливаем нашу текстуру в качестве рендер таргета и рендерим туда нашу сцену. Дальше надо было спроецировать полученную текстуру на нашу сцену. В принципе как я заметил это делается не так уж и сложно (по формуле). Однако результат меня не обрадовал <_<
На первом скриншоте сразу видно, что определение глубины сцены считается полностью не верно. Но на втором скриншоте прекрасно видно, что вычисляется правильно, но вот проекция данной текстуры идет не правильная. В общем потратив на эту проблему пару суток, какая то магия помогла мне в этой проблеме. Сам даже не понял как удалось её решить.
На скриншотах выше я изложил все стадии построения теней. Как видите смотрятся они не очень. Имею слишком много артефактов, но ведь тени есть =) И это очень круто. После стольких мучений такой прогресс =) Но получаемый FPS был низок (30 кадров). Это просто грустно. Но я опять таки нашел то слабое место, благодаря которому я снова поднял FPS до вполне хорошей отметки 150. Вся проблема заключалась опять же в копировании текстуры.
Подробнее об алгоритме
И так, что же это за алгоритм такой и в чем его суть. Сам долго вникал как это все происходит. И так для начала немного терминов:
  • Z-буфер или Буфер глубины - удаление пикселя от зрителя
А теперь о самом алгоритме:
  • Берется каждый источник света и в его позицию устанавливается камера
    • Производится рисование всей сцены в текстуру (DepthTexture - текстура глубины), где в пиксельном шейдере мы пишем заместо цвета точки, глубину вершины. Код ниже.
  • Устанавливается стандартная камера обзора
  • Производится рисование всей сцены уже на экран, при этом в наш шейдер передаются данные о камере источника света (матрица проекции и вида)
    • в пиксельном шейдере мы перемножаем координаты нашего пикселя на матрицу вида и проекции источника света.
    • получаем данные глубины для этой точки
    • если глубина точки, что на экране больше, чем глубина точки в текстуре глубины для данного источника, то получаем, что данный пиксель не освещен этим источником света, т.к. есть какой либо объект препятствующий ему
//Получение глубины для заданной точки
float4 Pos = ...;
float depthValue = Pos.z / Pos.w;
//Проекция текстуры
float2 projectTexCoord;
projectTexCoord.x = (lightViewPos.x / lightViewPos.w) / 2.0f + 0.5f;
projectTexCoord.y = -(lightViewPos.y / lightViewPos.w) / 2.0f + 0.5f;
if (saturate(projectTexCoord.x) == projectTexCoord.x && saturate(projectTexCoord.y) == projectTexCoord.y)
{
//получение глубины от источника света
	float lightDepthValue = DepthTexture[Idx].Sample(ClampSamplerState, projectTexCoord).r;
}
Ну ладно решили эту проблему и бог с ним. Покопавшись в различных архивах, статьях я понял что вся проблема заключается в неточном буфере глубины. Как оказывается данный буфер имеет не линейную формулу, что приводит к большим погрешностям. Но наткнулся на одну очень забавную формулу, которая помогла преобразовать формулу глубины в линейный вид.
Данная формула давала в результате очень хороший вид даже сразу видно, где должны быть тени. И причем достаточно точно. Однако когда я получал значение глубины для остальных источников, меня ждало лишь разочарование. В полученной картинке я не мог увидеть ни какой логики, поэтому пришлось отказаться от данной задумки. Возможно позже найду замену данной технологии.

Итоги

Ну ладно на этом наверное все. Подведу ка я итоги проделанной работы. Что я имею на данном этапе развития:
  • Sky-Box небо
  • Освещение от 8 источников света, с просчетом теней от зонального источника света
  • Рисование сцены в текстуру с последующей пост обработкой
  • Хитрую иерархию сцены
  • Технология рисования большого кол-ва копий одного и того же объекта за один проход
Вроде все.
Всем удачи, и спасибо, что прочитали =)
3
34
12 лет назад
3
directx11
coolstory bro.
Через год получится нечто похожее на движок первой hl )
Ну по себе знаю делать велосипеды без цели довольно интересное занятие, да и еще и опыт повышает и дает обширные знания по языку в целом. Было бы интересно прочитать про тени, но ничего конкретного так и не увидел. Объяснил бы что к чему и как.
0
29
12 лет назад
0
Hellcore, да знания и вправду хорошие по языку получил. Если учесть, что когда садился писать самое начало не мог различить :: от -> =) Что конкретно интересует по этому пункту. Добавлю. А так весь алгоритм построение теней лежит в тех 6 картинках
0
20
12 лет назад
0
1026 фпс! омг это как вообще?
0
29
12 лет назад
0
DotaFSS, просто рендер происходит в любой доступный момент времени, когда окно требует перерисовки
0
25
12 лет назад
0
DotaFSS сегодня, 15:27 [3]
1026 фпс! омг это как вообще?
у современных мониторов в любом случае частота обновления порядка 70.
Про тени - поищи в GPU Gems, уж не знаю в каком. И вообще GPU Gems - лучший источник информации по 3D, правда английском.
0
29
12 лет назад
Отредактирован alexprey
0
XimikS, англ не пугает. Я и так по статьям с англ источникам изучаю все это дело. Частота обновления экрана одно, а частота обновления картинки это другое.
Fps - показывает сколько раз картинка была обработана карточкой
2 комментария удалено
Чтобы оставить комментарий, пожалуйста, войдите на сайт.