Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебник по GLScene.doc
Скачиваний:
255
Добавлен:
16.12.2018
Размер:
7.18 Mб
Скачать

Глава 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 - возвращает значение цвета, рассчитанного в шейдере. Это обязательное действие.