Глава 40. Шейдеры GLSL. Использование Без компонентов. Мультитекстурирование. Смешение трёх цветов по базовой карте 


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



ЗНАЕТЕ ЛИ ВЫ?

Глава 40. Шейдеры GLSL. Использование Без компонентов. Мультитекстурирование. Смешение трёх цветов по базовой карте



 

С помощью шейдера на этот раз будем смешивать текстуры в определённой пропорции. Мы воспользуемя одним из стандартных способов.

Для наглядности, один и тот же шейдер мы будем применять сразу к GLCube, GLFreeForm и GLTerrainRenderer. Для работы вам понадобятся четыре картинки с названиями base.jpg, stone.jpg, grass.jpg, snow.jpg и obj модель. Теоретически картинки могут быть любого содержания, но я рекомендую взять текстуру камня для stone.jpg, травы для grass.jpg и снега для snow.jpg.

Я отдельно остановлюсь на 3D модели. Дело в том, что чтобы её затекстурировать, на неё нужно наложить base.jpg. Я опишу необходимые действия при работе в 3DS MAX.

Создать объект

Открыть Material Editor (клавиша «M»)

Внизу разверните свиток «Diffuse Color» и нажмите на кнопку с надписью «None» напротив этого свитка. Появится окно «Material/Map Browser»

Выберете в окне «Material/Map Browser» карту «Bitmap»

Появилось окошко «Select Bitmap Image File», в нём укажите путь к файлу base.jpg.

Назначьте созданный материал объекту.

Теперь поговорим о расчёте изображения и о построении base.jpg. Мы предполагаем, что в каждой точке нашей карты присутствуют все 3 текстуры — земля, трава и снег, но в разных пропорциях. Эти пропорции далее будем записывать как матрицу (Mask.r+Dt, Mask.g+Gt, Mask.b+St), где Dt, Gt, St — цвет земли, травы и снега, представленные числами. Mask — маска смешивания. Для того чтобы указать пропорции Dt, Gt, St в каждой точке, мы и используем базовую карту (BaseTex), в которой RGB компоненты цвета используются в качестве коэффициентов «присутствия материалов». Так, красный цвет обозначает, что тут должен быть камень, зеленый — трава, синий — снег. Соответственно, если компоненты базовой карты представлены, как (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, а GLCamera. Position.Y значение 5.

Поскольку мы используем прямой доступ к OpenGL, то ещё создайте в инспекторе объектов сцены GLDirectOpenGL. Мы будет тестировать шейдер на сразу трёх объектах: на кубе, на FreeForm и на TerrainRenderer, поэтому подключите модули GLObjects, GLVectorFileObjects, GLTerrainRenderer. Также подключить модули форматов файлов: Jpeg и GLFileOBJ.

Зададим в программе глобальные переменные для создания Cube, FreeForm и TerrainRenderer. Они нужны для того, чтобы увидеть действие шейдера.

var

MyCube: TGLCube;

MyFreeForm: TGLFreeForm;

MyTerrainRenderer: TGLTerrainRenderer;

Подключите модуль GLContext, иначе компилятор не будет знать о TGLProgramHandle. Так же нужно подключить модули GLTokens и OpenGL1x.

Теперь объявите в программе две константы типа string для хранения вершинного (_vp) и фрагментного (_fp) шейдеров.

const _vp =

‘void main(void)’+

‘{‘+

‘gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;’+

‘gl_TexCoord[0] = gl_MultiTexCoord0;’+

‘}’;

 

const _fp =

‘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;’+

‘}’;

Также объявите переменную GLSLHandle типа TGLProgramHandle. Она нужна для управления шейдером.

В событии FormCreate нужно создать все используемые в этом примере объекты. Делаем это как обычно — без инспектора объектов:

 

procedure TForm1.FormCreate(Sender: TObject);

begin

MyCube:=TGLCube.Create(nil);

 

GLBitmapHDS1.MaxPoolSize:=8*1024*1024;

GLBitmapHDS1.Picture.LoadFromFile(‘TerrainTex.jpg’);

MyTerrainRenderer:=TGLTerrainRenderer.Create(nil);

MyTerrainRenderer.HeightDataSource:=GLBitmapHDS1;

MyTerrainRenderer.PitchAngle:= 90;

MyTerrainRenderer.Scale.SetVector(1,1,0.02);

 

MyFreeForm:=TGLFreeForm.Create(nil);

MyFreeForm.LoadFromFile(‘map.obj’);

MyFreeForm.Scale.SetVector(0.5,0.5,0.5);

end;

Думаю, для читателя здесь нет никаких незнакомых строк. Сначала мы создали MyCube; потом загрузили карту ландшафта в GLBitmapHDS1, после чего создали MyTerrainRenderer и передали ему эту карту; после этого действия мы создали MyFreeForm, загрузив после этого в неё модель и не забыв её отмасштабировать (если ваша модель отмасштабирована уже в редакторе, стоит убрать эти строчки).

Теперь допишите в конец OnCreate следующий код:

with MyCube do

begin

Material.TextureEx.Add;

Material.TextureEx.Items[0].Texture.Image.LoadFromFile(‘base.jpg’);

Material.TextureEx.Items[0].Texture.Disabled:=False;

Material.TextureEx.Add;

Material.TextureEx.Items[1].Texture.Image.LoadFromFile(‘snow.jpg’);

Material.TextureEx.Items[1].Texture.Disabled:=False;

Material.TextureEx.Add;

Material.TextureEx.Items[2].Texture.Image.LoadFromFile(‘dirt.jpg’);

Material.TextureEx.Items[2].Texture.Disabled:=False;

Material.TextureEx.Add;

Material.TextureEx.Items[3].Texture.Image.LoadFromFile(‘grass.jpg’);

Material.TextureEx.Items[3].Texture.Disabled:=False;

end;

Так мы создали в свойстве Material.TextureEx четыре текстуры для объекта MyCube. Эти текстуры использовать мы будем не только для него, но и для двух остальных объектов. Смысл в том, что текстуры травы, камня, снега и распределяющая текстура будут одинаковыми и для MyCube, и для MyTerrainRenderer, и для MyFreeForm.

Необходимо создать два события OnRender у GLDirectOpenGL: одно — GLDirectOpenGLInit — для инициализации шейдеров и передачи неизменяемых uniform, второе — GLDirectOpenGLRender — для применения шейдеров. После создания тела процедур, назначьте GLDirectOpenGL.OnRender:= GLDirectOpenGLInit в design time или поместив эту строчку в OnCreate формы. OnRender приведите к следующему виду:

procedure TForm1.GLDirectOpenGLInit(Sender: TObject;

var rci: TRenderContextInfo);

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;

 

with GLSLHandle do

begin

UseProgramObject;

Uniform1i[‘BaseTex’]:=0;

Uniform1i[‘stoneTex’]:=1;

Uniform1i[‘GrassTex’]:=2;

Uniform1i[‘SnowTex’]:=3;

EndUseProgramObject;

end;

 

GLDirectOpenGL.OnRender:=GLDirectOpenGLRender;

GLDirectOpenGL.BuildList(rci);

end;

 

Ответственный момент — прописываем процедуру для применения шейдера.

procedure TForm1.GLDirectOpenGLRender(Sender: TObject;

var rci: TRenderContextInfo);

begin

with GLSLHandle do

begin

UseProgramObject;

MyCube.Render(rci);

MyTerrainRenderer.Render(rci);

MyFreeForm.Render(rci);

EndUseProgramObject;

end;

end;

 

Вот и всё. Готовый пример берите здесь: http://narod.ru/...

P.S. Если компилятор будет ругаться на строчку «var rci: TRenderContextInfo)», подключите модуль GLRenderContextInfo.

 

А теперь самое интересное: будем разбирать шейдер. Начнём с вершинного (вертексного).

void main(void)

{

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

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

 



Поделиться:


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

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