Освещение по Фонгу
Здесь принято делать, как минимум, такие предположения: - Ks = 0 (то есть грань не отражает свет, а только рассеивает) Да-да, здесь нет никакой ошибки. Практически все обычно используемые (в demoпо меньшей мере) методы т.н. "освещения по Фонгу" НЕ учитывают отраженной компоненты освещенности. Здесь будет рассказано о самом, наверное, популярном методе освещения по Фонгу, который сводит освещение к текстурированию по определенной текстуре. Этот метод базируется на таких добавочных предположениях: - L - константа (как бы точечный источник, удаленный бесконечно далеко) - длина единичной нормали к объекту при интерполяции между вершинами грани НЕ меняется
То есть, утверждается, что если в вершинах нормаль к объекту имеет длину 1, то при интерполяции этой нормали между вершинами по какой-то грани мы будем получать в каждой точке нормаль той же самой длины 1. На самом деле это вовсе даже не так, но для несильно разнящихся углов наклона нормалей приблизительно верно.
Так как Ks = 0, а длина N по предположению равна 1 на всей грани, имеем:
intensity = ambient + amp * (N * L).
Рассмотрим упрощенный случай, когда вектор L = (0,0,1). Общий случай можно без особых вычислительных затрат привести к этому упрощенному, как – будет рассказано чуть позже. Так вот, в этом случае
intensity = ambient + amp * (N.x * L.x + N.y * L.y + N.z * L.z) = = ambient + amp * (N.x * 0 + N.y * 0 + N.z * 1) = = ambient + amp * N.z = = ambient + amp * sqrt(1 - (N.x * N.x + N.y * N.y)).
То есть интенсивность выражается через N.x, N.y, а эти величины меняются линейно. N.x и N.y у нас - числа с плавающей запятой от -1 до 1 (т.к. длина вектора равна 1), интерполировать их - занятие медленное, да корень считать раз в пиксел тоже не хочется. Поэтому вместо интерполяции N.x и N.y обычно интерполируют, например, 128*(N.x+1) и 128*(N.y+1), причем уже в целых числах. Тогда все возможные значения таким образом отмасштабировнных N.x, N.y - это 0, 1,..., 255. Поэтому можно заранее посчитать табличку значений intensity для каждой пары отмасштабировнных N.x, N.y. То есть, мы линейно интерполируем 128*(N.x+1) и 128*(N.y+1) (эти значения меняются тоже линейно, раз N.x, N.y меняются линейно) и по ним по таблице определяем интенсивность. Это и есть текстурирование, только в качестве текстуры используется таблица освещенности размером 256x256 (или любым другим), а в качестве координат текстуры u, v для каждой вершины берутся отмасшатбированные координаты нормали в этой вершине. Таблица, согласно всего вышеупомянутого, считается так: //... for (i = 0; i < 256; i++) { for (j = 0; j < 256; j++) { r = pow((i - 128) / 256.0, 2) + // это N.x*N.x pow((j - 128) / 256.0, 2); // это N.y*N.y if (r > 1) r = 1; // длина N меньше 1, поэтому r > 1 быть не может phongTable[i][j] = amp * sqrt(1 - r); } } //... Правда, обычно используют другую - нелинейную - таблицу, видимо, хоть для какой-то компенсации всяких ошибок линеаризации... Результаты выглядят действительно получше. Считается она так: //... for (i = 0; i < 256; i++) for (j = 0; j < 256; j++) phongTable[i][j] = amp * pow(sin(i * PI / 256) * sin(j * PI / 256), 4); //... Для полного комплекта осталось только привести кусочек кода по вычислению координат в этой таблице: //... len = N.x * N.x + N.y * N.y + N.z * N.z; N.x /= len; // на случай, если длина N не равна 1 N.y /= len; N.z /= len; u = (1 + N.x) * 128; // собственно расчет координат v = (1 + N.y) * 128; //... Теперь вернемся к вопросу о том, как привести случай с произвольным вектором освещения к только что рассмотренному, где L = (0,0,1). Здесь все вроде бы просто. Просто применим к нормалям в вершинах любой поворот, совмещающий наш произвольный вектор света с вектором (0,0,1). Скалярное произведение при этом не изменяется, поэтому так делать можно. Ну, а после этого поворота уже имеем только что расписанный упрощенный случай. Этот поворот нормалей в каждой вершине не требует практически никаких затрат по следующей причине. Поворот сам по себе, конечно, достаточно медленная процедура и процессорное время отъедает. Но при движении и вращении камеры и объекта мы все равно должны будем соответственно поворачивать нормали. Так вот, эти два поворота можно совместить в один. Если использовать матрицы, все это делается совсем просто - достаточно перемножить (в нужном порядке!) матрицу собственного поворота объекта, матрицу перехода от произвольной камеры к нашей "стандартной" камере и матрицу перехода от произвольного вектора света к "стандартному" вектору света (0,0,1). Т.е. добавится расчет этой матрицы перехода и одно матричное умножение на объект, а это уже мелочь.
|