- •Учебник
- •Оглавление
- •Введение
- •Глава 1
- •I. Установка
- •II. Удаление
- •Глава 2. Первый проект
- •Глава 3. Создание сцены в режиме design time
- •Создание объектов:
- •Глава 4. Примитивные объекты. Вкладка Basic geometry.
- •Глава 5. Ориентация, координаты.
- •Глава 6. Движение.
- •Глава 7. Как узнать, сколько сейчас fps?
- •Глава 8. Полноэкранный режим.
- •Глава 9. Работа с материалами.
- •I. Прямое обращение к .Material
- •Глава 10. Использование 3d моделей.
- •I. Форматы моделей
- •Глава 11. Прокси объекты.
- •Глава 12. Создание земной поверхности с glTerrainRenderer.
- •Глава 13. Создание земной поверхности с glFreeForm.
- •Глава 14. Итоги работы с glTerainrender и glFreeForm.
- •Глава 15. Создание земной поверхности с glHeightField.
- •Глава 15. Создание неба.
- •Глава 16. GlAtmosphere.
- •Глава 17. Спецэффекты.
- •I. Огонь
- •II. Молния
- •III. Дождь
- •IV. Снег
- •VI. Блики от источников света
- •Глава 18. Растительность.
- •I. Деревья и кустарники
- •II. Трава
- •Глава 19. Работа со шрифтами и вывод надписей.
- •I. Вывод текста через компонент glWindowsBitmapFont
- •II. Проблема с glWindowsBitmapFont в Windows Vista
- •Глава 20. Выделение объекта мышкой.
- •Глава 21. Создание сцены в режиме runtime.
- •Глава 22. Тени.
- •Глава 23. Туман.
- •Глава 24. Vbo или расширение arb_vertex_buffer_object в OpenGl.
- •Глава 25. Fbo или расширение ext_framebuffer_object в OpenGl.
- •Глава 26. Рисование на канве.
- •Глава 27. Использование чистого OpenGl.
- •Глава 28. Звуки.
- •I. Проигрывание звуков с помощью bass
- •II. Проигрывание звуков с помощью fmod
- •III. Проигрывание звуков с помощью OpenAl
- •Глава 29. Игровое меню.
- •Глава 30. Примитивная физика dce.
- •Глава 31. Физика ode.
- •II. Основы библиотеки ode(: tglodeManager и tglodeJointList) на примере создания подобия боулинга.
- •Глава 32. Физика Newton.
- •Глава 33. Ручная проверка коллизий.
- •V. Ручная проверка коллизии
- •Глава 34. Шейдеры. Терменология.
- •Глава 35. Шейдеры. История и компоненты.
- •Глава 36. Шейдеры glsl. Введение.
- •Глава 37. Шейдеры glsl. Использование без компонентов. Самый примитивный шейдер.
- •Глава 38. Шейдеры glsl. Немного о спецификации. Типы данных и переменные
- •Стандартные функции
- •Поддерживаемые компоненты векторов
- •Глава 40. Шейдеры glsl. Текстурирование.
- •Глава 41. Шейдеры glsl. Использование без компонентов. Мультитекстурирование. Смешение трёх цветов по базовой карте.
- •Глава 42. Шейдеры glsl. Использование через компонент glslShader. Часть первая – Первые шаги.
- •Глава 43. Шейдеры glsl. Использование через компонент glslShader. Часть вторая – знакомимся со светом. Передача параметров в шейдер.
- •Глава 44. Шейдеры glsl. Использование через компонент glslShader. Часть третья – текстуры.
- •Глава 45. RenderMonkey.
- •Глава 46. Оптимизация программы.
- •Глава 47. Практика. Создание мира
- •Приложение
- •I. Типы векторов и матриц
- •II. Работа с векторами
- •III. Работа с матрицами
- •IV. Методы объектов glScene
- •Предметный указатель по компонентам glScene
Глава 41. Шейдеры glsl. Использование без компонентов. Мультитекстурирование. Смешение трёх цветов по базовой карте.
С помощью шейдера на этот раз будем смешивать текстуры в определённой пропорции. Мы воспользуемя одним из стандартных способов.
Для наглядности один и тот же шейдер мы будем применять сразу к GLSphere, GLFreeForm и GLTerrainRenderer. Для работы вам понадобятся четыре картинки с названиями base.bmp, stone.jpg, grass.jpg, snow.jpg и .3ds модель. Теоритически картинки могут быть любого содержания, но я рекомендую взять текстуру камня для stone.jpg, травы для grass.jpg и снега для snow.jpg.
Я отдельно остановлюсь на 3D модели. Дело в том, что чтобы её затекстурировать, на неё нужно наложить base.bmp. Я опишу необходимые действия при работе в 3ds max.
1. Создать объект
2. Открыть Material Editor (клавиша “m”)
3. Внизу разверните свиток “Diffuse Color” и нажмите на кнопку с надписью “None” напротив этого свитка. Появится окно “Material/Map Browser”
4. Выберете в окне “Material/Map Browser” карту “Bitmap”
5. Появилось окошко “Select Bitmap Image File”. В нём укажите путь к файлу base.bmp.
6. Назначте материал, который мы создали, объекту.
Всё.
Теперь поговорим о расчёте изображения и о построении base.bmp. Мы предполагаем, что в каждой точке нашей карты присутствуют все 3 текстуры - земля, трава и снег, но в разных пропорциях. Эти пропорции далее будем записывать как матрицу (Mask.r+Dt,Mask.g+Gt,Mask.b+St), где Dt,Gt,St цвет земли, травы и снега, представленные числами. Mask – маска смешивания. Для того чтобы указать пропорции Dt,Gt,St в каждой точке, мы и используем базовую карту (BaseTex), в которой компоненты цвета используются в качестве коэффициентов "присутствия материалов". Так красный цвет обозначает, что тут должен быть камень, зеленый - трава, синий - снег. Соответственно, если компоненты базовой карты представлены, как (0,1,0) , это означает, что в этом месте 100% должна быть трава и только трава. Аналогично и для (1,0,0) - мы рисуем только камень. В случае же, если присутствуют несколько компонентов одновременно, к примеру (0.5,0.5,0), то мы должны их смешать в равной пропорции, так 50% земли и 50% зелени, ну и 0% снега. Так как у нас компоненты цвета представлены в виде вектора нормированного к единице, то такое смешивание производится достаточно просто - текстура камня * 0,5 (красный компонент) + текстура травы * 0,5 (зеленый компонент) + текстура снега * 0 (синий компонент). Это и будет нашим результирующим цветом.
Аналогично и в случае присутствия всех трех компонент, к примеру (0.7, 0.2, 0.1), в этом случае земля, трава и снег смешаются в соотношении 70% земли + 20% травы + 10% снега.
При таком смешивании возможна одна проблема - если точка будет белого цвета, а белый цвет, как мы знаем, задается смешиванием всех трех компонент в одинаковой пропорции, то есть (1,1,1), то в сумме мы получим цвет с утроенной яркостью, а это видеокарта отобразить не может. Потому мы искусственно домножаем цвет на какой-то коэффициент, меньше единицы, чтобы не было переполнения. В коде это выглядит так: (mask.r*Dt + mask.g*Gt + mask.b*St) * 0.7. В нашем примере я использовал коэффициент 0.7. Почему 0.7, а не 0.3, что было бы логичнее? Потому что текстуры травы, земли и снега имеют свою собственную яркость, так яркость травы не поднимается выше 0.4, земли - выше 0,5, а снега - выше 0.7. При таком способе мы в сумме будем иметь максимум примерно 0.4+0.5+0.7=1,6 яркости, умножив на 0.7, получим примерно 1.1 яркости, что уже "укладывается" в допустимый диапазон. Небольшое превышение допустимой яркости (не более единицы) приводит к тому, что все изображение становится светлее, потому +0.1 это вполне допустимо.
Возвращаемся в среду разработки. Поместите на форму GLSceneViewer и GLScene1(инспектор объектов сцены), в котором нужно создать камеру (и включите в свойство Camera объекта GLSceneViewer значение GLCamera1). Так же поместите на форму GLBitmapHDS (вкладка GLScene Terrain). Присвойте свойству GLCamera.Position.Z значение 7.
Поскольку мы используем прямой доступ к OpenGL, то ещё создайте в инспекторе объектов сцены GLDirectOpenGL. В него поместите GLSphere, GLFreeForm и GLTerrainRenderer. Они нужны для того, чтобы увидеть действие шейдера.
Теперь откройте окошко Editing GLSphere.TextureEx (object.Material.TextureEx). И в нём каждому из объектов (GLSphere, GLFreeForm, GLTerrainRenderer) создайте по 4 текстуры. У каждой из двенадцати текстур свойство Texture.Disabled присвойте равным False. Далее в текстуру с именем Tex[0] у каждого объекта загрузите base.bmp. В текстуру Tex[1] загрузите stone.jpg. В Tex[2] используйте grass.jpg. А в Tex[3], соответственно, загрузите snow.jpg.
И подключите модуль GLContext, иначе компилятор не будет знать о TGLProgramHandle. Так же нужно подключить модуль OpenGL1x.
Теперь объявите в программе две константы типа string для хранения вершинного (_vp) и фрагментного (_fp) шейдера.
const _vp:string=
'void main(void){'+
‘gl_Position = ftransform();'+
'gl_TexCoord[0] = gl_MultiTexCoord0;'+
'}';
_fp:string=
'uniform sampler2D BaseTex;'+
'uniform sampler2D stoneTex;'+
'uniform sampler2D GrassTex;'+
'uniform sampler2D SnowTex;'+
'void main (void){'+
'vec4 mask = texture2D(BaseTex, gl_TexCoord[0].xy);'+
'vec4 Dt = texture2D(stoneTex, gl_TexCoord[0].xy*4);'+
'vec4 Gt = texture2D(GrassTex, gl_TexCoord[0].xy*4);'+
'vec4 St = texture2D(SnowTex, gl_TexCoord[0].xy*4);'+
'gl_FragColor = (mask.r*Dt + mask.g*Gt + mask.b*St) * 0.7;'+
'}';
Объявите переменную glsl типа TGLProgramHandle. Она нужна для для управления шейдером.
GLSLHandle:TGLProgramHandle;
И объевите ещё одну переменную для того, чтобы узнать прошёл ли запуск успешно.
InitDGL:boolean;
Теперь в событие OnRender объекта GLDirectOpenGL добавим код для инициализации шейдера и его обработки.
if not InitDGL = True then begin
if not(GL_ARB_shader_objects and GL_ARB_vertex_program and GL_ARB_vertex_shader and GL_ARB_fragment_shader)then begin
ShowMessage(' Ваша видеокарта не поддерживает GLSL шейдеры!');
Halt;
end;
GLSLHandle:=TGLProgramHandle.CreateAndAllocate; //Создаём и распределяем дескриптор
GLSLHandle.AddShader(TGLVertexShaderHandle, _vp); //Добавляем вершинный шейдер
GLSLHandle.AddShader(TGLFragmentShaderHandle, _fp);// Добавляем фрагментный шейдер
{два оператора ниже – проверка, запущен ли шейдер}
if not GLSLHandle.LinkProgram then raise Exception.Create(GLSLHandle.InfoLog); //Пытаемся слинковать программу
if not GLSLHandle.ValidateProgram then raise Exception.Create(GLSLHandle.InfoLog); //Проверяем правильность программы
CheckOpenGLError;
InitDGL:=True;
end;
if InitDGL then
with GLSLHandle do begin
UseProgramObject;
Uniform1i['BaseTex']:=0;
Uniform1i['stoneTex']:=1;
Uniform1i['GrassTex']:=2;
Uniform1i['SnowTex']:=3;
GLSphere.Render(rci);
GLFreeForm.Render(rci);
GLTerrainRenderer.Render(rci);
EndUseProgramObject; {Нужно, что бы эффекты шейдера не срабатывали на все объекты сцены}
end;
Теперь в FormCreate нужно создавать ландшафт и загружать в GLFreeForm любую модель (не забудьте перед этим подключить соответствующий расширению модели модуль). И помните, что обычно модель очень большая и её надо масштабировать.
GLBitmapHDS1.MaxPoolSize:=8*1024*1024;
GLTerrainRenderer.HeightDataSource:=GLBitmapHDS1;
GLTerrainRenderer.PitchAngle:= 90;
GLTerrainRenderer.Scale.SetVector(1,1,0.02);
GLFreeForm.LoadFromFile('map.3ds');
GLFreeForm.Scale.SetVector(0.1,0.1,1)
И если компилятор будет ругаться на строчку “var rci: TRenderContextInfo);”, подключите модуль GLRenderContextInfo.
_______________________________________________________________________
Всё! Запускайте проект и смотрите, что получилось.
А теперь самое интересное, будем разбирать шейдер.
Начнём с вершинного (вертексного)
void main(void)
{gl_Position = ftransform();
gl_TexCoord[0] = gl_MultiTexCoord0;}
Тут все просто. Прежде, чем мы сможем работать с вершинами нам необходимо их трансформировать с учётом глобальной (мировой) матрицы. Вот это и выполняется командой gl_Position = ftransform().
Далее мы должны передать во фрагментный шейдер текстурные координаты текущей вершины (так как сам фрагментный шейдер не может получить эту информацию) вот для этого и служит команда gl_TexCoord[0] = gl_MultiTexCoord0;
Теперь возьмёмся за фрагментный (пиксельный) шейдер
uniform sampler2D BaseTex;
uniform sampler2D stoneTex;
uniform sampler2D GrassTex;
uniform sampler2D SnowTex;
void main (void){
vec4 mask = texture2D(BaseTex, gl_TexCoord[0].xy);
vec4 Dt = texture2D(stoneTex, gl_TexCoord[0].xy*4);
vec4 Gt = texture2D(GrassTex, gl_TexCoord[0].xy*4);
vec4 St = texture2D(SnowTex, gl_TexCoord[0].xy*4);
gl_FragColor = (mask.r*Dt + mask.g*Gt + mask.b*St) * 0.7;
}
Uniform - это константа, которую мы передаем из нашей программы в шейдер.
sampler2D - то что мы передали нужно интерпретировать как двумерную текстурную карту. В нашем примере для мультитекстурирования используется одна базовая карта (BaseTex), которая по сути говорит, какой материл в каком месте ставить и собственно 3 различных текстуры для 3-х разных материалов.
А теперь отдельно рассмотрим часть фрагментного шейдера:
…
void main (void){
vec4 c = texture2D(BaseTex, gl_TexCoord[0].xy);
vec4 Dt = texture2D(DirtTex, gl_TexCoord[0].xy*4);
vec4 Gt = texture2D(GrassTex, gl_TexCoord[0].xy*4);
vec4 St = texture2D(SnowTex, gl_TexCoord[0].xy*4);
…
Тип данных vec4 - это вектор, который хранит информацию о 4-х компонентах цвета - RGB+Alpha
В данном фрагменте мы получаем цвет точки из нашей текстуры. Координаты точки берутся из gl_TexCoord[0].xy. Поскольку при таком подходе текстуры получаются очень сильно растянутыми, мы увеличиваем их число, домножив координаты на какой-нибудь коэффициент. В нашем случае – 4.
Ну и завершает этот процесс расчет нового цвета:
…
gl_FragColor = (mask.r*Dt + mask.g*Gt + mask.b*St) * 0.7;
}
…
gl_FragColor - возвращает значение цвета, рассчитанного в шейдере. Это обязательное действие.