15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Глава 10 Свет и тень


В тех приложениях, с которыми нам приходилось иметь дело в предыдущих главах, освещение состояло из фонового рассеянного света низкой интенсивности и направленного света для получения бликов. В этой главе мы рассмотрим все разновидности источников света, которые могут встретиться в наших приложениях, познакомимся с их свойствами и узнаем, с какими накладными расходами сопряжено использование каждого из них. Мы также увидим, каким образом можно имитировать тени в макете. Основное приложение этой главы находится в каталоге Lights.

Цветовые модели

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

 Цвет и материал поверхности, находящейся непосредственно под пикселем.

 Угол наклона поверхности по отношению к каждому источнику света в макете.

 Цвет, интенсивность и расположение каждого источника света.

 Возможности физического устройства отображения (дисплея).

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

Существует несколько способов упрощения этой задачи. Например, можно игнорировать цветовые компоненты света и считать их источниками белого цвета различной интенсивности. Это значительно упрощает расчет цветов и ускоряет производительность без особого вреда для качества. Или возьмем другой пример - мы можем упростить зависимость между свойствами материала и его цветом. Для этого зеркальные и диффузные отражающие свойства материала комбинируются, и по ним определяется индекс в цветовой таблице, содержащей весь диапазон цветов объекта, от чисто зеркального до чисто диффузного. Возможно, эти примеры покажутся излишне упрощенными, однако они показывают, что небольшая доля изобретательности может значительно улучшить производительность работы, хотя и за счет качества изображения. Поскольку чаще всего основной проблемой является именно производительность, с небольшой потерей качества вполне можно примириться.

Приведенные выше примеры показывают, что решить проблему цвета можно несколькими способами. Наверное, разработчикам Direct3D следовало позволить нам, пользователям, самим выбирать методику оптимизации для каждого конкретного случая. Тем не менее они этого не сделали. Нам предлагаются всего два режима: монохромный и RGB. Монохромный режим назван так из-за того, что в нем используются только монохромные (белые) источники света. Мы можем менять интенсивность источника света, но не его цвет. Это заметно упрощает процесс обсчета цветовых оттенков. Кроме того, цвета материалов в монохром-Цветовые модели Щ 235


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

В RGB-режиме для расчета цвета пикселей используются значительно более сложные вычисления. RGB-режим полностью поддерживает работу с цветными источниками света и свойствами материала и позволяет получить наилучшее возможное качество изображения. Поскольку в этой главе мы будем играть с цветными источниками света, нам придется настроить механизм визуализации на работу в RGB-режиме. Наверняка вы подумали: <Все, о производительности придется забыть!> Не волнуйтесь, дело обстоит не так плохо.

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

 24-битный дисплей способен отобразить практически любой цвет, сгенерированный механизмом визуализации в виде тройки RGB-компонент. На каждый пиксель необходимо переслать 3 байта данных; в отдельных случаях этот фактор может сказаться на производительности.

 16-битпь1Й дисплей может отобразить от 32,767 до 65,535 цветов, в зависимости от настройки механизма визуализации. Для каждого пикселя требуется всего 2 байта данных, однако при их генерации приходится выполнить с RGB-компонентами несколько операций сдвига, что также требует некоторых расходов времени.

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

Короче говоря, ситуация выглядит следующим образом. При 24-битном дисплее вы получаете потрясающие картинки. При 16-битном дисплее вы получаете хорошее качество с неплохой производительностью, 8-битный дисплей позволяет добиться неплохого качества при хорошей производительности. Основная проблема 8-битных дисплеев заключается в том, что для воспроизведения огромного диапазона цветов RGB-модели при 256-цветной палитре приходится пользоваться методикой смешения цветов (dithering), снижающей общее качество изображения. В монохромном режиме на 8-битном дисплее можно ограничить количество цветов, используемых объектами, и получать все необходимые цвета непосредственно из палитры. При этом вы получаете качественное изображение при хорошей производительности. Чтобы решить, какой режим лучше всего подходит вам, следует немного поэкспериментировать.

Тип цветовой модели задается при создании трехмерного окна. Ниже приведен соответствующий фрагмент кода:

236 :i^a:i::-' Гпяпя 1П Г!пет u TftHh


int CMainFrame::OnCreate(LPCREATESTRUCT IpCreateStruct)

{

// Создать трехмерное окно

if (!m_wnd3d.Create(this, IDC_3DWND, D3DCOLOR_RGB)) { return -1;

t

Если при вызове функции C3dWnd::Create цветовая модель не указана, по умолчанию принимается значение D3DCOLOR_MONO.

Пожалуй, мы задержались на этой теме несколько дольше, чем я рассчитывал вначале, однако мне хотелось пояснить, почему мы переходим на RGB-модель вместо монохромной, которая до настоящего времени вполне нас устраивала.

Выбор типа освещения

Механизм визуализации DirectSD поддерживает пять разных типов освещения:

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

Тип освещения Положение Направление Точечный источник Предельная дальность Другие параметры
Рассеянный нет нет нет нет нет
Направленный нет да нет нет нет
Параллельно-точечный да нет да нет нет
Точечный да нет да да нет
Зональный да да да да да


Из таблицы видно, что источники света обладают различными свойствами. Для каждого источника света можно задать RGB-значение, которое позволяет управлять как цветом, так и интенсивностью света. Давайте рассмотрим каждый тип освещения и его влияние на макет.

Рассеянный свет

Рассеянный свет обсчитывается проще всего. Он обеспечивает равномерный уровень освещения в макете, при котором все грани объектов освещаются одинаково. На рис. 10-1 изображен пример макета, освещенного белым рассеянным светом.

237

Выбор типа освещения


132.jpg

Рис. 10-1. Макет, освещенный только рассеянным светом

ПРИМЕЧАНИЕ

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

Вопреки первому впечатлению, эти джентльмены вовсе не собираются грабить банк. <Эффект чулка> вызван сочетанием плоского освещения и смешением цветов, необходимым для отображения объекта на моем 256-цветном дисплее. От одного рассеянного света проку мало, однако в сочетании с другими типами освещения он помогает устранить из макета излишне темные области.

Направленный свет

Направленный свет по простоте обсчета уступает разве что рассеянному. Он освещает макет параллельными лучами из источника, находящегося на бесконечно большом расстоянии от макета. Его ориентация задается с помощью вектора направления. Задавать положение источника направленного света бесполезно. На рис. 10-2 изображен макет, освещенный рассеянным светом малой интенсивности в сочетании с направленным светом.

Как видите, макет выглядит значительно лучше, чем с одним лишь рассеянным светом. Освещение на рис. 10-2 очень похоже на встречающееся в естественных условиях. Стрелка показывает, откуда падает направленный свет. На рис. 10-3 изображен тот же самый макет, но без рассеянного света.

Рисунок 10-3 выглядит слишком контрастным, на нем имеется много темных мест, которые мешают рассмотреть некоторые части объектов.

238

Глава 10. Свет и тень


133.jpg

Рис. 10-2. Направленный свет используется для получения бликов

134.jpg

Рис. 10-3. Макет освещен только направленным светом

Параллельно-точечный свет

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

239

Выбоо типа освешения


135.jpg

Рис. 10-4. Параллельно-точечное освещение

Как видно из рисунка, источник света (изображенный в виде маленького кубика) освещает головы таким образом, что свет кажется исходящим из точки, в которой находится кубик. По рис. 10-4 довольно сложно определить глубину источника света по отношению к головам, но если посмотреть на текст программы (мы сделаем это позднее, на стр. 242), то можно убедиться, что источник света находится немного позади от них.

Точечный свет

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

Я советую немного поэкспериментировать с приложением Lights, чтобы понять отличия этого типа освещения от предыдущего. Впрочем, повышение качества обходится довольно дорого, и по моему мнению, точечное освещение не имеет особых преимуществ перед параллельно-точечным.

Зональный свет

Обсчет зонального освещения оказывается самым сложным, однако оно заметно повышает качество изображения, как можно видеть из рис. 10-6 (кроме того, посмотрите на цветную версию рисунка на вкладке).

На освещенность макета влияет как положение, так и направление зонального источника света. Кроме того, угол светового конуса изменяется по двум параметрам. Считается, что световой конус состоит из центрального конуса с полной интенсивностью и внешнего конуса с уменьшенной интенсивностью. Вы можете задавать величину угла при вершине обоих конусов. Зональный источник света идеально подходит для создания макета тюремного двора с прожекторами или лесного костра.

240

Глава 10. Свет и тень


136.jpg

Рис. 10-5. Точечное освещение

137.jpg

Рис. 10-6. Зональное освещение

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

241

Выбор типа освещения


Предельная дальность и затухание

Точечные и зональные источники света обладают еще двумя параметрами, влияющими на освещение макета. Предельная дальность определяет расстояние, на котором действует свет. По умолчанию предельная дальность равна 256 единицам модели - этого хватит для освещения всех объектов, которые мы до настоящего момента включали в макеты. Присвоение данному параметру малых значений приводит к довольно интересным эффектам. Кроме того, при этом повышается производительность работы механизма визуализации, поскольку при расчете освещенности объектов, расстояние до которых превышает предельную дальность источника света, можно не учитывать данный источник.

Другой параметр, величину которого также можно регулировать, - затухание. Он влияет на убывание интенсивности света с расстоянием. В квадратное уравнение, определяющее интенсивность света, входят три параметра. В документации по DirectSD они названы постоянной, линейной и квадратичной составляющими. По умолчанию им присваиваются значения 1, 0 и 0 соответственно - интенсивность света от такого источника не убывает с расстоянием. Уравнение, определяющее величину затухания, выглядит следующим образом:

а = с + Id + qd2,

где а - величина затухания; с - постоянная составляющая; 1 - линейная составляющая; d - расстояние от источника света, a q - квадратичная составляющая. Для того чтобы изменить затухание источника света, можно воспользоваться функцией C3dlight::SetAttenuation.

Хватит разговоров - давайте программировать

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

Поскольку программная реализация источников света выглядит довольно просто и не сильно отличается для различных типов освещения, я приведу код только для создания параллельно-точечного источника света на рис. 10-4.

void CMainFrame::OnEditParlight() {

C3dParPtLight* pLight = new CSdParPtLight;

pLight->Create (1, 1, 1);

m_pCurLight = pLight;

// Создать фигуру, изображающую источник света C3dShape* psi = new C3dShape;

psl->CreateCube (0.2) ;

// Присоединить источник света в качестве потомка фигуры, 242 аШУ Глава 10. Свет и тень


// чтобы его можно было выбрать psl->AddChild(pLight) ;

// Присоединить источник света к макету m_pScene->AddChild(psl) ;

m pScene->m_ShapeList. Append (psi) ;

m pScene->m_ShapeList. Append (pLight) ;

// Поместить источник света на видном месте psl->SetPosition(0, 1, -2);

psl->SetName ("Parallel Point Light");

MakeCurrent(psi) ;

}

Большая часть этого фрагмента не имеет никакого отношения к созданию самого источника света, но мы все равно рассмотрим его. В начальных строках функции определяется источник света - в данном случае объект CSdParPtLight. Чтобы показать расположение источника света в макете, я создал фигуру и присоединил к ней источник в качестве потомка, чтобы они перемещались вместе. Классы фигуры и источника света являются производными от CSdFrame. Это позволяет включить их в список фигур макета, чтобы они были удалены во время уничтожения всего макета. Остается лишь задать положение источника света в макете. Для направленного источника света следовало бы задать направление, а для зонального - как направление, так и положение.

Классы C++ для работы с источниками света конкретного типа выглядят очень просто. Они являются производными от класса C3dLight, в котором и создается источник:

BOOL C3dLight::Create(D3DRMLIGHTTYPE type,

double r, double g, double b) {

// Создать фрейм, содержащий источник света if (!C3dFrame::Create(NOLL)) return FALSE;

// Создать объект-источник света ASSERT(m_pILight == NULL);

if (!the3dEngine.CreateLight(type, r, g, b, &m pILight)) {

return FALSE;

} ASSERT(m_pILight);

// Присоединить источник света к фрейму ASSERT (m_J3l Frame) ;

m_hr = m_pIFrame->AddLight (m_pILight) ;

if (FAILED(m_hr)) return FALSE;

return TRUE;

}

Хватит оазговооов - давайте поогоаммиоовать "lEU 243


Источник света присоединяется к фрейму для того, чтобы мы смогли задать его положение. Источник света, создаваемый механизмом визуализации, не обладает собственным положением или ориентацией, пока он не будет закреплен за каким-нибудь фреймом. Наследование классом C3dLight свойств класса C3dFrame облегчает работу с объектами в макетах.

Цветные источники света

Я довольно долго искал какой-нибудь пример того, как цветное освещение улучшает вид макета, однако после долгих размышлений мне удалось изобрести лишь несколько простейших приложении, не имеющих никакого практического значения. Затем в один прекрасный день я увидел в чьем-то кабинете пару красно-зеленых стереоскопических очков, и это натолкнуло меня на мысль. Предлагаю вашему вниманию программу для просмотра стереоскопических изображении. Приложение находится в каталоге Stereo. Для работы с ним следует надеть стереоскопические очки. На рис. 10-7 показано, как будет выглядеть окно приложения, если вы вдруг снимете очки.

138.jpg

Рис. 10-7. Нестереоскопическое изображение стереоскопического объекта в зеленых тонах

Читателей, которым приходилось рассматривать стереокартинки на упаковках с кукурузными хлопьями, может удивить отсутствие на рис. 10-7 знакомых красно-зеленых перекрывающихся изображений. Дело в том, что моя программа воспроизводит макет в красном освещении, после чего слегка передвигает камеру и перерисовывает его в зеленом свете. Таким образом, экран может принадлежать либо зеленой, либо красной половинке рабочего цикла. Чтобы добиться полноценного стереоэффекта, запустите приложение Stereo и наденьте стереоскопические очки. При этом желательно выключить свет и остаться в темноте.

94Д ^IStW' Гпап-а 1П Г^ват тош!-


Не знаю, можно ли считать это практическим примером работы с цветным освещением, однако писать программу было довольно интересно. Смысл основной части этого приложения - переместить камеру, задать новое освещение и воспроизвести макет на экране. Давайте выделим несколько минут и посмотрим, как все это делается. Однако перед тем, как заниматься программой, взгляните на рис. 10-8, который поясняет принцип ее работы.

139.jpg

1310.jpg

Рис. 10-8. Получение стереоизображения

Сначала макет освещается красным светом, а камера переносится в точку А, в которой воспроизводится изображение для левого глаза. Затем макет освещается зеленым светом, а камера переносится в точку В, где воспроизводится изображение для правого глаза. Если просматривать изображение с красным фильтром на левом глазу и зеленым - на правом, то левый глаз будет видеть изображение из точки А, а правый - из точки В. Ниже приведен фрагмент кода пассивного цикла приложения, в котором все это происходит:

BOOL CMainFrame::Update() {

m_bPhase = !m bPhase;

double d = 1.0; // Расхождение

double cz = 10; // Положение камеры по оси z

C3dVector vo;

if (m_pCur3hape) (

I iRRTHhIP UrrrrHJUUIUIA ГРОТЯ "ЧКЙ:-; 9А^


m_pCurShape->GetPosition (vo) ;

} else {

vo = C3dVector(0, 0, 0) ;

} C3dVector cv;

if (m_pDirLight && m_p3cene) { if "(itiJaPhase) {

m_pDirLight->SetColor (1, 0, 0) ;

cv = C3dVector(-d, 0, -cz') ;

} else {

m_pDirLight->SetColor(0, 1, 0);

cv = C3dVector(d, 0, -cz);

} m_pScene->SetCameraPosition (cv) ;

C3dVector vl = vo - cv;

m_pScene->SetCameraDirection (vl) ;

}

// Обновить трехмерное окно if (m_wnd3d.Update(1)) { ЬМоге = TRUE;

}

return ЬМоге;

i

Вектор текущего положения объекта (vo) используется для вычисления вектора направления камеры (vl), чтобы векторы для левого и правого видов сходились в центре объекта.

Подобный подход к реализации стереоскопического изображения обладает тремя недостатками:

 Поскольку левый и правый виды выводятся по отдельности, изображение заметно мерцает.

 Стереоскопическое изображение невозможно захватить (сфотографировать);

хотя макет остается прежним, приложение должно постоянно перерисовывать его.

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

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

246 ЙУ Глава 10. Свет и тень


Тени

В самом начале книги я упомянул о том, что механизм визуализации Direct3D не поддерживает работу с тенями. Тем не менее в отдельных случаях тень все же можно имитировать, создавая визуальный объект нужной формы и размещая его в нужном месте макета. На рис. 10-9 изображен пример из приложения Lights (выполните команды File Ѓ New и Edit Ѓ Insert Shape, нажмите кнопку ОК на вкладке Sphere и выполните команду Edit Ѓ Shadow).

1311.jpg

Рис. 10-9. Имитация тени

Для создания тени следует спроектировать объект на плоскость и указать положение источника света в качестве параметра. Чтобы тень появилась в макете, необходимо присоединить ее к некоторому фрейму. Присоединение тени к фрейму затеняющего объекта обеспечивает перемещение тени вместе с объектом. Кроме того, при движении источника света тень также будет перемещаться. Плоскость, на которую отбрасывается тень, задается с помощью точки и вектора нормали. Приведенный ниже фрагмент создает сферу на рис. 10-9 и присоединяет к ней тень:

void CMainFrame::OnEditShadow() (

C3dShape* pCast = new C3dShape;

pCast->CreateSphere(0.3) ;

m_pScene->AddChild(pCast) ;

m_pScene->m_ShapeList .Append (pCast) ;

pCast->SetPosition(0, 1, 0) ;

MakeCurrent(pCast) ;

// Создать тень, слегка приподнятую над плоскостью

Тени

247


CSctVector pt(0, -1.9, 0); // Точка плоскости

C3dVector normal(0, 1, 0); // Нормаль к плоскости

pCast->Create3hadow(pLight, pt, normal);

i

Функция C3dShape::CreateShadow создает визуальный элемент тени по нескольким аргументам: текущему источнику света, точке плоскости, на которую отбрасывается тень, нормали к этой плоскости:

BOOL CSdShape::CreateShadow(C3dLight* pLight,

D3DVECTOR& vPt,

D3DVECTOR& vN) {

IDirect3DRMVisual* pIVisual = NULL;

m_hr =

the3dEngine.Getlnterface()->CreateShadow(GetVisual(), pLight->GetLight () , vPt.x, vPt.y, vPt.z, vN.x, vN.y, vN.z, SpIVisual);

ASSERT(SUCCEEDED(m_hr)) ;

// Присоединить тень к фрейму ASSERT(m_pIFrame) ;

m_hr = m_pIFrame->AddVisual (pIVisual) ;

return SUCCEEDED(m_hr);

)

Поскольку тень является таким же визуальным объектом, как и любая другая фигура макета, следует соблюдать осторожность при задании плоскости, на которую она должна проектироваться. Убедитесь, что эта плоскость слегка приподнята над поверхностью, на которую должна отбрасываться тень. Если эта плоскость совпадет с гранью объекта, пиксели перемешаются и результат будет непредсказуемым. Тень, слегка приподнятая над поверхностью, выглядит вполне нормально и четко.

Обратите внимание - тень может проектироваться только на одну плоскость. Вы не сможете загнуть ее за угол или перенести на смежную грань куба. Кроме того, проекционная плоскость считается бесконечной. В демонстрации, изображенной на рис. 10-9, перемещение источника света или фигуры выводит тень за пределы поверхности, на которую она проектируется. Кроме того, можно протащить фигуру сквозь поверхность вниз, а тень при этом останется наверху.

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

9АЯ -flaSi^' ГПОП-Э 1П Г^аат 1Л TQLJL


1312.jpg

Рис. 10-10. Приложение Globe с тенью

Итоги

Различные типы освещения могут значительно улучшить внешний вид макета. Поскольку для некоторых источников света требуются более сложные расчеты, необходимо разумно выбирать тип освещения в тех случаях, когда важна высокая скорость работы приложения. Цветные источники света применяются для создания специальных эффектов, но не забывайте о том, что работать с ними можно только в RGB-модели. Наконец, тени также повышают реализм изображения, но они ограничены всего одной плоскостью.

Далее...