netlib.narod.ru | < Назад | Оглавление | Далее > |
Метод Terrain::genTexture обращается к функции Terrain::lightTerrain, которая, как говорит ее имя, выполняет освещение ландшафта для увеличения реализма сцены. Поскольку мы уже вычислили цвета текстуры ландшафта, нам осталось вычислить только коэффициент затенения, который делает отдельные участки темнее или светлее в зависимости от их расположения относительно источника света. В данном разделе мы исследуем такую технику. Вы можете недоумевать, почему мы занялись освещением ландшафта, а не позволили Direct3D все сделать за нас. У самостоятельного выполнения вычислений есть три преимущества:
Мы экономим память, поскольку нам не надо хранить нормали вершин.
Так как ландшафты статичны и мы не будем перемещать источники света, можно заранее рассчитать освещение, освободив то время, которое Direct3D тратил бы на расчет освещения ландшафта в реальном времени.
Мы попрактикуемся в математике, познакомимся с базовыми концепциями освещения и поработаем с функциями Direct3D.
Техника освещения, которую мы будем использовать для вычисления оттенков ландшафта является одной из самых простых и известна как рассеянное освещение (diffuse lighting). Мы используем параллельный источник света, который описываем путем указания направления на источник света, являющегося противоположным тому направлению, в котором падают испускаемые источником лучи. Например, если мы хотим чтобы лучи света падали с неба вертикально вниз в направлении lightRaysDirection = (0, –1, 0), то направлением на источник света будет указывающий в противоположном направлении вектор directionToLight = (0, 1, 0). Обратите внимание, что мы используем единичные векторы.
Затем для каждого квадрата ландшафта мы вычисляем угол между вектором освещения и нормалью к поверхности квадрата .
На рис. 13.7 видно, что чем больше угол, тем больше поверхность квадрата отворачивается от источника света и тем меньше света попадает на поверхность. Соответственно, чем меньше угол, тем больше поверхность квадрата повернута к источнику света и тем больше света она получает. Кроме того, обратите внимание, что если угол между вектором освещения и нормалью поверхности больше 90 градусов, поверхность вообще не освещена.
Рис. 13.7. Угол между вектором освещения и нормалью поверхности определяет сколько света получает поверхность. На рисунке (a) угол меньше 90 градусов. На рисунке (б) угол больше 90 градусов. Заметьте, что во втором случае поверхность не получает света потому что лучи света (испускаемые в направлении противоположном вектору ) попадают на обратную сторону поверхности |
Используя угловые отношения между вектором освещения и нормалю поверхности можно вычислить коэффициент затенения, находящийся в диапазоне [0, 1], который определяет сколько света получает поверхность. Большие углы представляются близкими к нулю значениями коэффициента. Когда цвет умножается на близкий к нулю коэффициент затенения, он становится темнее, а это именно то, что нам надо. С другой стороны, малые углы представляются близкими к единице значениями коэффициента, и умножение на этот коэффициент практически не меняет яркость цвета.
Направление на источник света нам дано в виде нормализованного вектора . Чтобы вычислить угол между и нормалью квадрата нам сперва необходимо получить . Это тривиальная задача, решаемая с помощью векторного произведения. Но сначала мы должны получить два ненулевых и непараллельных вектора, лежащих в той же плоскости, что и квадрат. На рис. 13.8 эти два вектора обозначены u и v:
Рис. 13.8. Вычисление двух векторов, находящихся в одной плоскости с квадратом |
Получив u и v, нормаль квадрата N вычисляем по формуле N = u × v. Конечно же следует нормализовать вектор N:
Чтобы найти угол между и вспомним, что скалярное произведение двух единичных векторов в трехмерном пространстве равно косинусу угла между ними:
Значения коэффициента s будут находиться в интервале [–1, 1]. Значения s из диапазона [–1, 0) соответствуют углам между и большим 90 градусов, а в этом случае, как показано на рис. 13.7 поверхность не получает света. Поэтому любые значения из диапазона [–1, 0) мы заменяем на 0:
float cosine = D3DXVec3Dot(&n, directionToLight); if(cosine < 0.0f) cosine = 0.0f;
Теперь, когда значения s для углов больших 90 градусов отброшены, s становится нашим коэффициентом затенения с интервалом значений [0, 1], поскольку когда угол между и увеличивается от 0 до 90 градусов значение s изменяется от 1 до 0. Это именно та необходимая нам функциональность о которой говорилось в разделе 13.4.1.
Коэффициент затенения для отдельного квадрата вычисляет метод Terrain::computeShade. В качестве параметров он получает номера строки и столбца, идентифицирующие квадрат ландшафта и вектор, задающий направление на параллельный источник света.
float Terrain::computeShade(int cellRow, int cellCol, D3DXVECTOR3* directionToLight) { // Получаем высоты трех вершин квадрата float heightA = getHeightmapEntry(cellRow, cellCol); float heightB = getHeightmapEntry(cellRow, cellCol+1); float heightC = getHeightmapEntry(cellRow+1, cellCol); // Строим два вектора квадрата D3DXVECTOR3 u(_cellSpacing, heightB - heightA, 0.0f); D3DXVECTOR3 v(0.0f, heightC - heightA, -_cellSpacing); // Находим нормаль, выполнив векторное умножение // двух векторов квадрата D3DXVECTOR3 n; D3DXVec3Cross(&n, &u, &v); D3DXVec3Normalize(&n, &n); float cosine = D3DXVec3Dot(&n, directionToLight); if(cosine < 0.0f) cosine = 0.0f; return cosine; }
Теперь, когда мы узнали как выполнить затенение отдельного квадрата, можно выполнить затенение всех квадратов ландшафта. Мы просто перебираем в цикле все квадраты ландшафта, вычисляем для каждого из них коэффициент затенения и умножаем цвет соответствующего квадрату текселя на полученный коэффициент. В результате квадраты, получающие мало света станут темнее. Ниже приведен фрагмент кода, являющийся главной частью метода Terrain::lightTerrain:
DWORD* imageData = (DWORD*)lockedRect.pBits; for(int i = 0; i < textureDesc.Height; i++) { for(int j = 0; j < textureDesc.Width; j++) { int index = i * lockedRect.Pitch / 4 + j; // Получаем текущий цвет ячейки D3DXCOLOR c(imageData[index]); // Затеняем текущую ячейку c *= computeShade(i, j, lightDirection);; // Сохраняем затененный цвет imageData[index] = (D3DCOLOR)c; } }
netlib.narod.ru | < Назад | Оглавление | Далее > |