Спекуляр - это имитация отражения источника света от поверхности, имеющей некоторую шероховатость. Если поверхность абсолютно гладкая - то для точечного источника света мы бы получили яркие точки отражений на тех местах, где угол падения равен углу отражения, то есть угол между нормалью и вектором на свет равен углу между нормалью и вектором на камеру, и эти вектора лежат в одной плоскости.
Чтобы проверить это соответствие, можно использовать кучу разных способов, пробуем первый:
Если нормализовать вектора на свет и на камеру, а потом их сложить, то полученный вектор как раз будет биссектрисой угла свет-объект-камера, то есть угол падения равен углу отражения, и всё это в одной плоскости. Остаётся проверять, на сколько этот суммарный вектор совпадает с нормалью, для этого суммарный вектор нормализуем. Два нормализованных вектора совпадают, если их скалярное произведение равно единице, чем сильнее отличаются вектора - тем меньше скалярное произведение, у перпендикулярных векторов скалярное произведение равно нулю, у противоположных - минус единице. Значения в выходных регистрах oD0 и oD1 предназначены для света, поэтому автоматически ограничиваются диапазоном 0..1, то есть мы можем не беспокоиться об отрицательных значениях - они заменятся нулём. При таком расчёте получится отражение от очень шероховатой поверхности, где яркость падает до нуля, когда угол равен Pi/2. Чтобы сделать поверхность более гладкой - нужно брать диапазон значений, более близкий к единице, например 0.75..1. Если просто ограничить величину этими значениями - свет на краях блика будет резко исчезать, что неестественно. Нужно чтобы яркость к краям стремилась к нулю. Очевидно, что чтобы привести диапазон 0.75..1 к 0..1, нужно величину умножить на 4 и вычесть 3, можно умножить на любое число >1 и вычесть на единицу меньшее, чем больше число - тем более гладкой получается поверхность, более отчётливым блик. Но не нужно забывать, что все эти расчёты мы производим в вертексном шейдере, значения вычисляются только для вершин треугольников и интерполируются внутри него, блик меньше треугольника мы всё равно не получим, при слишком большом значении мы увидим искажения, станут заметны отдельные треугольники в геометрии. Чтобы делать на низкополигональной геометрии чёткие, почти зеркальные блики - нужно расчёты переносить в пиксельный шейдер, что сильно увеличит нагрузку на видеокарту. Но об этом потом... может быть.
Итак, с теорией ясно, практика:
Добавляем в код на бейсике переменную для спекуляра:
- Код: Выделить всё
Public Specular As D3DVECTOR
Даём ей некоторое значение:
- Код: Выделить всё
Specular = Vec3(0.8, 0.8, 0.7)
Передаём Specular в шейдер, как мы передавали Diffuse и Ambient:
- Код: Выделить всё
Dev.SetVertexShaderConstantF 7, VarPtr(Specular), 1
Так же нам теперь требуется направление на камеру, но, в отличие от направления на бесконечно удалённый источник света, направление на камеру для каждого вертекса будет разным, поэтому передаём в шейдер не направление, а позицию камеры, направление будем вычислять в шейдере. Так же, как и для направления на свет, пересчитываем позицию в локальную систему координат модели - умножаем на ту же инвертированную мировую матрицу модели, только сам тип умножения для позиций применяется не тот, что для направлений:
- Код: Выделить всё
Vec3TransformCoord v3, CamPos, Mtrx
Dev.SetVertexShaderConstantF 8, VarPtr(v3), 1
С кодом на бейсике всё, остальное - в шейдерах. В вертексном:
sub r1, c8, v0 - вычли из позиции камеры позицию вертекса, то есть нашли направление на камеру.
nrm r2, r1.xyz - нормализовали его.
add r1, c4, r2 - сложили с направлением на свет.
nrm r2, r1.xyz - нормализовали, получили вектор для сравнения с нормалью.
dp3 r1, r2, v1 - сравниваем.
mad r1, r1, c9.x, c9.y - умножаем на c9.x (=4) и прибавляем c9.y (=-3), то есть вычитаем 3. Добавили гладкость. Я константу c9 задал непосредственно в шейдере просто для примера, её можно передавать в шейдер так же, как и все остальные константы.
mul oD1, r1, c7 - домножили на цвет спекуляра и отправили в выходной регистр oD1, это значение после интерполяции попадёт в пиксельный шейдер во входной регистр v1.
В пиксельном шейдере добавили всего две строки кода:
mul r1, r0.a, v1 - домножили цвет спекуляра на его яркость для данной точки, взятую из карты (Specular Map), находящейся в альфаканале текстуры. (Обрати внимание, в текстуре есть альфаканал, хотя прозрачность мы не используем).
add r0, r0, r1 - прибавили к вычисленному цвету пикселя цвет спекуляра перед отправкой в бэкбуфер.
Вроде всё, остальное не отличается от предыдущего примера. Жду вопросы.
Проект:
https://yadi.sk/d/P7j1blXcb4pbZ