Глава 43. Шейдеры GLSL. Использование через компонент glslshader. Часть третья — текстуры 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Глава 43. Шейдеры GLSL. Использование через компонент glslshader. Часть третья — текстуры



 

Карпенюк Алексей (karpenyuk@bk.ru)

(публикация стилизована)

В предыдущих разделах мы с вами научились подключать шейдер, передавать ему различные параметры и даже научились освещать объект без использования источников света. Теперь самое время разобраться с текстурами, для этого немного изменим наш проект.

Для начала заменим гриб на что-то более удобное для объяснения материала — я выбрал куб, на каждую из сторон которого наложена своя текстура.

Итак, открываем наш старый проект и начинаем вносить изменения — первым делом вместо гриба загружаем куб и настраиваем камеру на новый объект (положение, масштаб, центровка). Это можно сделать как в инспекторе объектов, так и в onCreate формы. Мы рассмотрим второй вариант, так как это гарантирует, что ничего не будет пропущено:

procedure TForm1.FormCreate(Sender: TObject);

begin

with GLFreeForm1 do begin

ObjectStyle:=[osDirectDraw];

Material.LibMaterialName:=‘‘;

Material.MaterialLibrary:=nil;

UseMeshMaterials:=true;

MaterialLibrary:=GLMaterialLibrary1;

AutoCentering:=[macUseBaryCenter];

Scale.AsVector:=vectormake(0.5,0.5,0.5);

LoadFromFile(‘cube.3ds’);

end;

GLCamera1.Position.AsVector:=VectorMake(0,5,12);

GLSLShader1.LoadShaderPrograms(‘Shader.Vert’, ‘Shader.Frag’);

GLSLShader1.Enabled:= true;

end;

Как вы помните, на прошлых уроках мы вручную задавали материал для гриба, теперь нам это уже не нужно. Для того, чтобы разрешить GLFreeForm1 использовать материал объекта, мы установили соответствующий флаг UseMeshMaterials:=true, следующим шагом нам нужно указать, в какую библиотеку материалов будут загружены наши текстуры: MaterialLibrary:=GLMaterialLibrary1.

Чтоб увидеть, как выглядит на этом этапе наш куб, нам понадобится добавить на сцену источник света. Чтобы ближайшая к нам грань была всегда освещена, перетащим его в GLCamera1, тем самым отпадет необходимость отдельно задавать его координаты (как вы помните, за освещение у нас в конце будет отвечать шейдер). Так же не забываем что для использования текстур в формате Jpeg необходим модуль Jpeg. Если вы все правильно сделали, то на экране должен появиться такой вот куб:

Можете заставить его вращаться, для этого поместите на форму компонент GLCadencer, укажите что он связан с нашей сценой (Scene = GLScene1) и в его событии onProgress добавьте такие строки:

GLFreeForm1.Turn(deltatime*10);

GLSceneViewer1.Invalidate;

Это заставит его вращаться вокруг оси Y.

Как видите — каждой его грани присвоен свой материал, всего у этого куба 6 материалов, по 1 на каждую грань, далее мы научимся применять шейдер к любому из материалов объекта.

Тут есть одна особенность — если в момент загрузки в библиотеке материалов будет присутствовать материал с таким же именем как и у объекта, то он и будет взят из библиотеки материалов, иначе он будет создан и загружен автоматически. Узнать имена материалов используемых объектом можно в любом 3D редакторе, или же сделать проще — после первой загрузки объекта вместе с материалами вывести список имен материалов в данной библиотеке, сделать это можно к примеру так:

var

s: string;

i: integer;

for i:=0 to GLMaterialLibrary1.Materials.Count-1 do

s:=s+GLMaterialLibrary1.Materials.Items[i].Name+#13#10;

ShowMessage(s);

В нашем случае материалы называются Mat 01, Mat 02,…., Mat 06.

Итак, мы хотим применить к одному из материалов наш шейдер, например заменить текстуру на одной из граней куба. Для этого нам потребуются, во-первых, сама текстура, во-вторых, шейдер который сможет это сделать. С текстурой просто — возьмем текстуру из комплекта GLScene — GLScene.bmp. Добавим ее загрузку в событие формы onCreate:

GLMaterialLibrary1.AddTextureMaterial(‘glscene’,’glscene.bmp’);

Объект готов, текстура готова, сцена готова — самое время отредактировать шейдер.

Открываем в блокноте шейдер и дописываем туда выделенные строки. Вершинный шейдер:

 

varying vec2 Texcoord;

varying vec3 Normal;

void main(void)

{

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

Normal = gl_Normal;

Texcoord = gl_MultiTexCoord0.xy;

}

Фрагментный шейдер:

varying vec3 Normal;

varying vec2 Texcoord;

uniform vec4 vec_light;

uniform float ambient_intensity;

uniform sampler2D MainTexture;

void main(void)

{

vec4 TextureColor = texture2D(MainTexture, Texcoord);

gl_FragColor = ambient_intensity*dot(Normal,vec_light)*TextureColor;

}

Функция gl_MultiTexCoord0.xy возвращает текстурные координаты для данной точки объекта, вызываться эта функция может только из вершинного шейдера. Если нам необходимо использовать эти координаты в фрагментном шейдере, мы должны их туда передать посредством глобальной переменной, которая объявляется как varying, причем объявить ее нужно в обоих шейдерах. Так как текстура у нас двумерная, то и для передачи текстурных координат достаточно двумерного вектора, объявленного через vec2. Следующее изменение у нас касается фрагментного шейдера — там у нас появился новый тип данных — uniform sampler2D, так в шейдере обозначаются текстурные карты. Как вы уже заметили — мы эту текстуру объявили как uniform параметр, следовательно, нам ее прийдется передавать в шейдер из нашей программы. Ну и последняя новая команда для нас это функция texture2D, которая возвращает нам цвет из точки текстуры с заданными координатами. Так как в качестве цвета у нас теперь будет текстура, то за ненадобностью мы удалим строку «uniform vec4 ambient_color;», так же удалим ambient_color из функции вычисления цвета, заменив ее на TextureColor.

С шейдером закончили. Теперь самое интересное — как применить этот шейдер к текстуре одной из граней и как в него передать наш логотип — для этого нам опять придется внести некоторые изменения в onCreate формы, так как на данном этапе наши текстуры граней куба все еще подгружаются автоматически. После загрузки объекта командой LoadFromFile(‘cube.3ds’); переназначим один из материалов, для этого напишем такую вот строчку:

GLMaterialLibrary1.LibMaterialByName(‘Mat 03’).Shader:=GLSLShader1;

Эта строка найдет в библиотеке материалов уже загруженный материал Mat 03 и применит к нему наш шейдер GLSLShader1.

Следующим этапом будет передать данную текстуру в шейдер, поэтому идём в обработчик события GLSLShader1Apply и там дописываем такую строку:

Param[‘MainTexture’].AsTexture2D[0]:=

GLMaterialLibrary1.LibMaterialByName(‘glscene’).Material.Texture;

При этом не забываем удалить уже не нужную строку:

Param[‘ambient_color’].AsVector4f:= VectorMake(0.2, 0.2, 0.2, 1);

На этом наша работа закончена, смело запускаем программу и наблюдаем результат. Как мы и хотели, на одной из граней куба появился логотип GLScene. Аналогичным образом мы можем применить данную текстуру к любой грани куба.

Теперь мы с вами рассмотрим, как можно эту текстуру растянуть или же наоборот — размножить по гране куба, для этого нам вновь придется открыть шейдер. Откройте файл фрагментного шейдера shader.frag и найдите там строчку:

vec4 TextureColor = texture2D(MainTexture, Texcoord);

Эта строка как раз и отвечает за текстурирование объекта, используя текущие текстурные координаты (Texcoord), полученные из вершинного шейдера. Особенность этих координат в том, что они нормированы к интервалу [0..1], при этом 0 — это левый/нижний угол, 1 — правый/верхний угол картинки. Узнать какой точке текстуры будет соответствовать текстурная координата очень просто — для этого достаточно домножить эту координату на размер текстуры. К примеру, если у нас размер текстуры 256х256 пикселей, то текстурная координата (0.4, 0.6) будет соответствовать точке с координатами (0.4*256,0.6*256). Если точка попадает между пикселями, как в нашем примере (0.4*256=102.4, 0.6*256=153.6), то шейдер возьмет цвет из ближайшей точки, в нашем случае таковой будет точка с координатой x = 102, y = 154.

Вторая особенность текстурных координат заключается в том, что если они превышают единицу, то целая часть просто отбрасывается, т.е. точки с координатами x = 0.5, x = 1.5 и x = 12.5 и даже x = 134567.5 будет указывать на одну и туже точку на текстуре. Используя это свойство, мы с вами попробуем изменить параметры наложения текстуры. Давайте подумаем — что произойдет с текстурой если мы умножим текстурные координаты на какой-то коэффициент, к примеру на 2? Это будет вам «домашним заданием», а пока мы заставим шейдер сделать это вместо нас и посмотрим, что из этого получится — исправим в шейдере предыдущую строчку:

vec4 TextureColor = texture2D(MainTexture, Texcoord*2);

Теперь запускаем программу и смотрим, что у нас получилось:

   

Как видите, по высоте и ширине текстура у нас повторилась 2 раза. Аналогичная ситуация будет, когда вы зададите коэффициент меньше единицы, с той только разницей, что теперь текстура будет растягиваться (часть текстуры при этом будет обрезаться, так как выходит за пределы объекта).

Теперь мы научимся анимировать эту текстуру, заставив ее прокручиваться в каком-то направлении. Для этого нам вновь придется отредактировать шейдер — добавьте в фрагментный шейдер еще одну строчку — uniform float Shift; и добавьте это сдвиг к текстурной координате:

vec4 TextureColor = texture2D(MainTexture, Texcoord*2+Shift);

В результате чего наш фрагментный шейдер теперь будет выглядеть так:

varying vec3 Normal; varying vec2 Texcoord; uniform vec4 vec_light;

uniform float ambient_intensity;

uniform sampler2D MainTexture;

uniform float Shift;

void main(void){

vec4 TextureColor = texture2D(MainTexture, Texcoord*2+Shift);

gl_FragColor = ambient_intensity*dot(Normal,vec_light)*TextureColor;

}

Чтобы текстура двигалась, мы просто добавляем некоторое смещение к координатам текстуры x и y, саму же величину смещения мы будем передавать из программы. На этом изменения в шейдере завершены, и мы переходим к изменениям в основной программе — перейдите к обработчику события GLSLShader1Apply и добавьте там такую строку:

Param[‘Shift’].AsFloat:=GLCadencer1.CurrentTime*0.5;

Все, запускайте программу и наблюдайте, как наша текстура плавно ползет по грани куба, коэффициент 0,5 задает скорость этого движения.

Ну и напоследок несколько уточнений — если вы хотите, чтобы для каждой текстуры был свой шейдер, просто поместите на форму еще нужное число компонентов TGLSLShader и свяжите нужные материалы с нужными шейдерами, как было показано ранее. Ну и естественно, для каждого шейдера вы должны написать свой обработчик onApply, в котором вы будете передавать ему параметры.

В предыдущем примере я использовал только одну текстуру для одной грани. Но можно таким же образом передать в шейдер до 8 различных текстур, что позволит вам комбинировать их внутри шейдера по своему усмотрению.



Поделиться:


Последнее изменение этой страницы: 2016-04-19; просмотров: 394; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.138.122.4 (0.013 с.)