XNA для начинающих. Использование шейдеров в XNA Framework

XNA для начинающих. Использование шейдеров в XNA Framework

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

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

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

Нашей задачей будет отображение квадрата на экране монитора.

Создадим новый проект, и добавим в него переменные:


VertexPositionColor[] verts;


int[] indices;


VertexDeclaration vd;


BasicEffect basicEffect;

Я использую VertexPositionColor поскольку для начала я не буду использовать текстуры.

Также мне понадобится новый метод CreateGeometry, в котором я создам геометрию (то есть опишу все вершины) для квадрата:

void CreateGeometry()

{

basicEffect = new
BasicEffect(GraphicsDevice, null);

vd = new
VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);

verts = new
VertexPositionColor[4];

verts[0].Position = new
Vector3(-0.5f, -0.5f, 0);

verts[0].Color = Color.Red;

verts[1].Position = new
Vector3(-0.5f, 0.5f, 0);

verts[1].Color = Color.Green;

verts[2].Position = new
Vector3(0.5f, 0.5f, 0);

verts[2].Color = Color.Blue;

verts[3].Position = new
Vector3(0.5f, -0.5f, 0);

verts[3].Color = Color.White;

indices = new
int[] { 0, 1, 2, 2, 3, 0 };

}

Этот метод я буду вызывать в Initialize класса Game1, там же я инициализирую basicEffect.


basicEffect = new
BasicEffect(GraphicsDevice, null);

Теперь в методы Draw я напишу код, который будет рисовать квадрат на экране монитора:


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.CornflowerBlue);


// TODO: Add your drawing code here


Matrix World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(-0.5f, 0.5f, 0);


Matrix View = Matrix.CreateLookAt(new
Vector3(0, 0, 2), Vector3.Zero, Vector3.Up);


Matrix Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 5f);

GraphicsDevice.VertexDeclaration = vd;

basicEffect.VertexColorEnabled = true;

basicEffect.World = World;

basicEffect.View = View;

basicEffect.Projection = Projection;

basicEffect.Begin();

basicEffect.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, verts, 0, 4, indices, 0, 2);

basicEffect.CurrentTechnique.Passes[0].End();

basicEffect.End();


base.Draw(gameTime);

}

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

Если сейчас запустить приложение, то мы увидим следующую картину:

Теперь, при помощи BasicEffect нарисуем текстурированный квадрат, для этого нужно добавить текстуру в проект (в папку Content) и создать нужные нам переменные.


Texture2D texture;


protected
override
void LoadContent()

{


// Create a new SpriteBatch, which can be used to draw textures.

spriteBatch = new
SpriteBatch(GraphicsDevice);


// TODO: use this.Content to load your game content here


texture = Content.Load<Texture2D>(«gold»);

}

Также мне понадобятся новые вешины, которые будут также содержать информацию о текстурных координатах. Индескы можно оставить прежние.


VertexPositionTexture[] verts2;


VertexDeclaration vd2;


void CreateGeometry()

{

vd = new
VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);

verts = new
VertexPositionColor[4];

verts[0].Position = new
Vector3(-0.5f, -0.5f, 0);

verts[0].Color = Color.Red;

verts[1].Position = new
Vector3(-0.5f, 0.5f, 0);

verts[1].Color = Color.Green;

verts[2].Position = new
Vector3(0.5f, 0.5f, 0);

verts[2].Color = Color.Blue;

verts[3].Position = new
Vector3(0.5f, -0.5f, 0);

verts[3].Color = Color.White;


vd2 = new
VertexDeclaration(GraphicsDevice, VertexPositionTexture.VertexElements);

verts2 = new
VertexPositionTexture[4];

verts2[0].Position = new
Vector3(-0.5f, -0.5f, 0);

verts2[0].TextureCoordinate = new
Vector2(0, 1);

verts2[1].Position = new
Vector3(-0.5f, 0.5f, 0);

verts2[1].TextureCoordinate = new
Vector2(0, 0);

verts2[2].Position = new
Vector3(0.5f, 0.5f, 0);

verts2[2].TextureCoordinate = new
Vector2(1, 0);

verts2[3].Position = new
Vector3(0.5f, -0.5f, 0);

verts2[3].TextureCoordinate = new
Vector2(1, 1);

indices = new
int[] { 0, 1, 2, 2, 3, 0 };

}

Теперь нам осталось только изменить метод Draw следующим образом:


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.CornflowerBlue);


// TODO: Add your drawing code here


Matrix World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(-0.5f, 0.5f, 0);


Matrix View = Matrix.CreateLookAt(new
Vector3(0, 0, 2), Vector3.Zero, Vector3.Up);


Matrix Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 5f);

GraphicsDevice.VertexDeclaration = vd;

basicEffect.VertexColorEnabled = true;

basicEffect.World = World;

basicEffect.View = View;

basicEffect.Projection = Projection;

basicEffect.Begin();

basicEffect.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, verts, 0, 4, indices, 0, 2);

basicEffect.CurrentTechnique.Passes[0].End();

basicEffect.End();


World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(0.5f, 0.5f, 0);

GraphicsDevice.VertexDeclaration = vd2;

basicEffect.VertexColorEnabled = false;

basicEffect.World = World;

basicEffect.Texture = texture;

basicEffect.TextureEnabled = true;

basicEffect.Begin();

basicEffect.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, verts2, 0, 4, indices, 0, 2);

basicEffect.CurrentTechnique.Passes[0].End();

basicEffect.End();

basicEffect.TextureEnabled = false;


base.Draw(gameTime);

}

Тут я пересоздал матрицу World для того, чтобы новый квадрат рисовался в правом верхнем углу, а также написал код, который рисует текстурированный квадрат. Обратите также внимание на то, что необходимо менять значение параметров TextureEnabled и VertexColorEnabled у basicEffect для корректной отрисовки примитивов.

Если сейчас запустить приложение, то мы увидим следующую картинку:

Теперь пришло время создавать свой собственный шейдер, добавим в проект (в папку Content) новый файл с эффектом и назовем его, скажем, myEffect.fx и начнем редактировать файл.

float4x4 World;

float4x4 View;

float4x4 Projection;

// TODO: add effect parameters here.

struct VertexShaderInput

{


float4 Position : POSITION0;


// TODO: add input channels such as texture


// coordinates and vertex colors here.

};

struct VertexShaderOutput

{


float4 Position : POSITION0;


// TODO: add vertex shader outputs such as colors and texture


// coordinates here. These values will automatically be interpolated


// over the triangle, and provided as input to your pixel shader.

};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)

{

VertexShaderOutput output;


float4 worldPosition = mul(input.Position, World);


float4 viewPosition = mul(worldPosition, View);

output.Position = mul(viewPosition, Projection);


// TODO: add your vertex shader code here.


return output;

}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0

{


// TODO: add your pixel shader code here.


return
float4(1, 0, 0, 1);

}

technique Technique1

{


pass Pass1

{


// TODO: set renderstates here.


VertexShader = compile
vs_1_1 VertexShaderFunction();


PixelShader = compile
ps_1_1 PixelShaderFunction();

}

}

Сейчас вершинный шейдер не передает никакой информации о цвете пикселя, следовательно, в пиксельном шейдере невозможно определить цвет. Модифицируем код шейдера так, чтобы он передавал информацию о цвете из вершинного шейдера в пиксельный. Для этого нам понадобится изменить структуры, соответствующие входным и выходным параметрам вершинного шейдера. После этого нужно обязательно заполнить информацию о новых элементах выходной структуры в вершинном шейдере. Для того, чтобы заполнить информацию о цвете в выходной структуре вершинного шейдера я просто передам туда значение из входной структуры.

struct VertexShaderInput

{


float4 Position : POSITION0;

float4 Color : COLOR0;

};

struct VertexShaderOutput

{


float4 Position : POSITION0;

float4 Color : COLOR0;

};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)

{

VertexShaderOutput output;


float4 worldPosition = mul(input.Position, World);


float4 viewPosition = mul(worldPosition, View);

output.Position = mul(viewPosition, Projection);

output.Color = input.Color;


return output;

}

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

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0

{


return input.Color;

}

Теперь осталось применить новый шейдер для рисования нашего квадрата. Вернемся к Game1 и там добавим новую переменную типа Effect (этот класс отвечает за работу с шейдерами в XNA) и инициализируем ее, как и для всех остальных видов ресурсов это делается при помощи ContentManager:


Effect effect;


protected
override
void LoadContent()

{


// Create a new SpriteBatch, which can be used to draw textures.

spriteBatch = new
SpriteBatch(GraphicsDevice);


// TODO: use this.Content to load your game content here

texture = Content.Load<Texture2D>(«gold»);


effect = Content.Load<Effect>(«myEffect»);

}

В методе Draw напишем код, который отображает квадрат с использованием нового шейдера в нижнем левом углу.


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.CornflowerBlue);


// TODO: Add your drawing code here


Matrix World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(-0.5f, 0.5f, 0);


Matrix View = Matrix.CreateLookAt(new
Vector3(0, 0, 2), Vector3.Zero, Vector3.Up);


Matrix Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 5f);

GraphicsDevice.VertexDeclaration = vd;

basicEffect.VertexColorEnabled = true;

basicEffect.World = World;

basicEffect.View = View;

basicEffect.Projection = Projection;

basicEffect.Begin();

basicEffect.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, verts, 0, 4, indices, 0, 2);

basicEffect.CurrentTechnique.Passes[0].End();

basicEffect.End();

World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(0.5f, 0.5f, 0);

GraphicsDevice.VertexDeclaration = vd2;

basicEffect.VertexColorEnabled = false;

basicEffect.World = World;

basicEffect.Texture = texture;

basicEffect.TextureEnabled = true;

basicEffect.Begin();

basicEffect.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, verts2, 0, 4, indices, 0, 2);

basicEffect.CurrentTechnique.Passes[0].End();

basicEffect.End();

basicEffect.TextureEnabled = false;


World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(-0.5f, -0.5f, 0);

GraphicsDevice.VertexDeclaration = vd;

effect.Parameters[«World»].SetValue(World);

effect.Parameters[«View»].SetValue(View);

effect.Parameters[«Projection»].SetValue(Projection);

effect.CurrentTechnique = effect.Techniques[«Technique1»];

effect.Begin();

effect.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, verts, 0, 4, indices, 0, 2);

effect.CurrentTechnique.Passes[0].End();

effect.End();


base.Draw(gameTime);

}

Обратите внимание на то, что для класса Effect не определены свойства такие как имели в BasicEffect, например World или View. Для задания параметров эффекта нужно использовать свойство Parameters, которое является коллекцией параметров эффекта. Для обращения к конкретному параметру нужно использовать строковые идентификаторы, соответствующие названию параметра. Как Вы помните, сейчас в шейдере используется три переменные – World, View, Projection. Именно их значения мы и установили в методе Draw при помощи вызова метода SetValue.

Также нужно установить технику, которую необходимо использовать

effect.CurrentTechnique = effect.Techniques[«Technique1»];

Рисование примитивов осуществляется алалогично тому, как это делалось для BasicEffect.

Запустив приложение, мы увидим новый квадрат, который нарисован уже с помощию нашего собственного шейдера!

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

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

Для того чтобы определять цвет каждого пикселя нам понадобятся текстурные координаты в пиксельном шейдере. Сейчас у нас не учитываются текстурные координаты во входной и выходной структуре данных вершинного шейдера. Добавим их самостоятельно.

struct VertexShaderInput

{


float4 Position : POSITION0;

float4 TexCoords : TEXCOORD0;

};

struct VertexShaderOutput

{


float4 Position : POSITION0;

float4 TexCoords : TEXCOORD0;

};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)

{

VertexShaderOutput output;


float4 worldPosition = mul(input.Position, World);


float4 viewPosition = mul(worldPosition, View);

output.Position = mul(viewPosition, Projection);

output.TexCoords = input.TexCoords;


return output;

}

Теперь мы сможет получить интерполированные текстурные координаты в пиксельном шейдере, но для того, чтобы получать цвет их текстуры по этим координатам, нам нужно будет создать сэмплер и передать в шейдер саму текстуру. Обратите внимание на строку Texture=<Tex>; при описании сэмплера, она указывает на то, какую такстуру будет использовать сэмплер.

Texture Tex;

sampler mySampler = sampler_state

{

Texture = <Tex>;

};

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0

{


return
tex2D(mySampler, input.TexCoords);

}

Шейдер готов и можно приступать к его использованию в Game1, для этого добавим все необходимое.

Effect effect2;


protected
override
void LoadContent()

{


// Create a new SpriteBatch, which can be used to draw textures.

spriteBatch = new
SpriteBatch(GraphicsDevice);


// TODO: use this.Content to load your game content here

texture = Content.Load<Texture2D>(«gold»);

effect = Content.Load<Effect>(«myEffect»);

effect2 = Content.Load<Effect>(«myEffect2»);

}


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.CornflowerBlue);


// TODO: Add your drawing code here


Matrix World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(-0.5f, 0.5f, 0);


Matrix View = Matrix.CreateLookAt(new
Vector3(0, 0, 2), Vector3.Zero, Vector3.Up);


Matrix Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 5f);

GraphicsDevice.VertexDeclaration = vd;

basicEffect.VertexColorEnabled = true;

basicEffect.World = World;

basicEffect.View = View;

basicEffect.Projection = Projection;

basicEffect.Begin();

basicEffect.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, verts, 0, 4, indices, 0, 2);

basicEffect.CurrentTechnique.Passes[0].End();

basicEffect.End();

World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(0.5f, 0.5f, 0);

GraphicsDevice.VertexDeclaration = vd2;

basicEffect.VertexColorEnabled = false;

basicEffect.World = World;

basicEffect.Texture = texture;

basicEffect.TextureEnabled = true;

basicEffect.Begin();

basicEffect.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, verts2, 0, 4, indices, 0, 2);

basicEffect.CurrentTechnique.Passes[0].End();

basicEffect.End();

basicEffect.TextureEnabled = false;

World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(-0.5f, -0.5f, 0);

GraphicsDevice.VertexDeclaration = vd;

effect.Parameters[«World»].SetValue(World);

effect.Parameters[«View»].SetValue(View);

effect.Parameters[«Projection»].SetValue(Projection);

effect.CurrentTechnique = effect.Techniques[«Technique1»];

effect.Begin();

effect.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, verts, 0, 4, indices, 0, 2);

effect.CurrentTechnique.Passes[0].End();

effect.End();

World = Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(0.5f, -0.5f, 0);

GraphicsDevice.VertexDeclaration = vd2;

effect2.Parameters[«World»].SetValue(World);

effect2.Parameters[«View»].SetValue(View);

effect2.Parameters[«Projection»].SetValue(Projection);

effect2.Parameters[«Tex»].SetValue(texture);

effect2.CurrentTechnique = effect2.Techniques[«Technique1»];

effect2.Begin();

effect2.CurrentTechnique.Passes[0].Begin();

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, verts2, 0, 4, indices, 0, 2);

effect2.CurrentTechnique.Passes[0].End();

effect2.End();


base.Draw(gameTime);

}

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

Результатом наших действий будет следующая картинка:

Реклама
Запись опубликована в рубрике Uncategorized. Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s