XNA для начинающих. Пиксельный шейдер. Смешивание цветов. Часть 2.

XNA для начинающих. Пиксельный шейдер. Смешивание цветов. Часть 2.

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

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0

{


// TODO: add your pixel shader code here.


float snowW = clamp(1 — abs(input.Height/MaxHeight — 0.9)/0.2, 0, 1);

float rockW = clamp(1 — abs(input.Height/MaxHeight — 0.6)/0.3, 0, 1);

float grassW = clamp(1 — abs(input.Height/MaxHeight — 0.3)/0.3, 0, 1);

float waterW = clamp(1 — abs(input.Height/MaxHeight — 0.1)/0.2, 0, 1);


float4 snowColor = float4(1,1,1,1);

float4 rockColor = float4(0.64, 0.16, 0.16, 1);

float4 grassColor = float4(0, 0.5, 0, 1);

float4 waterColor = float4(0, 0, 1, 1);


return snowW * snowColor +

rockW * rockColor +

grassW * grassColor +

waterW * waterColor;

}

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

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

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

Будем получать веса в CreateVertices и сохранять их в компоненты, отвечающую за цвет вершины.


public
class
Game1 : Microsoft.Xna.Framework.Game

{


GraphicsDeviceManager graphics;


SpriteBatch spriteBatch;


Effect effect;


VertexDeclaration vd;


VertexPositionColor[] vertices;


int[] indices;


int N = 120;


float maxHeight;


Texture2D heightMap;


public Game1()

{

graphics = new
GraphicsDeviceManager(this);

Content.RootDirectory = «Content»;

}


///
<summary>


/// Allows the game to perform any initialization it needs to before starting to run.


/// This is where it can query for any required services and load any non-graphic


/// related content. Calling base.Initialize will enumerate through any components


/// and initialize them as well.


///
</summary>


protected
override
void Initialize()

{


// TODO: Add your initialization logic here


base.Initialize();

}


///
<summary>


/// LoadContent will be called once per game and is the place to load


/// all of your content.


///
</summary>


protected
override
void LoadContent()

{


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

spriteBatch = new
SpriteBatch(GraphicsDevice);

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

heightMap = Content.Load<Texture2D>(«heightmap»);

CreateVertices();


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

}


private
void CreateVertices()

{


Color[] heightMapColors = new
Color[heightMap.Width * heightMap.Width];

heightMap.GetData(heightMapColors);

N = heightMap.Width;

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

vertices = new
VertexPositionColor[N * N];


float delta = 1f / (N — 1);


for (int i = 0; i < N; i++)


for (int j = 0; j < N; j++)

{


int index = i * N + j;


float height = heightMapColors[index].R / 255f;

vertices[index].Position = new
Vector3(delta * j, height, -delta * i);

maxHeight = Math.Max(maxHeight, height);

}


for (int i = 0; i < N; i++)


for (int j = 0; j < N; j++)

{


int index = i * N + j;


float snowW = (float)MathHelper.Clamp(1f — Math.Abs(vertices[index].Position.Y / maxHeight — 0.9f) / 0.2f, 0f, 1f);


float rockW = (float)MathHelper.Clamp(1f — Math.Abs(vertices[index].Position.Y / maxHeight — 0.6f) / 0.3f, 0f, 1f);


float grassW = (float)MathHelper.Clamp(1f — Math.Abs(vertices[index].Position.Y / maxHeight — 0.3f) / 0.3f, 0f, 1f);


float waterW = (float)MathHelper.Clamp(1f — Math.Abs(vertices[index].Position.Y / maxHeight — 0.1f) / 0.2f, 0f, 1f);

vertices[index].Color = new
Color(snowW, rockW, grassW, waterW);

}

indices = new
int[(N — 1) * (N — 1) * 6];


int counter = 0;


for (int z = 0; z < N — 1; z++)


for (int x = 0; x < N — 1; x++)

{


int lowerLeft = (z * N + x);


int lowerRight = lowerLeft + 1;


int upperLeft = lowerLeft + N;


int upperRight = upperLeft + 1;

indices[counter++] = lowerLeft;

indices[counter++] = upperLeft;

indices[counter++] = upperRight;

indices[counter++] = lowerLeft;

indices[counter++] = upperRight;

indices[counter++] = lowerRight;

}

}


///
<summary>


/// UnloadContent will be called once per game and is the place to unload


/// all content.


///
</summary>


protected
override
void UnloadContent()

{


// TODO: Unload any non ContentManager content here

}


///
<summary>


/// Allows the game to run logic such as updating the world,


/// checking for collisions, gathering input, and playing audio.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Update(GameTime gameTime)

{


// Allows the game to exit


if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)


this.Exit();


// TODO: Add your update logic here


base.Update(gameTime);

}


///
<summary>


/// This is called when the game should draw itself.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.Black);


// TODO: Add your drawing code here


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


Matrix View = Matrix.CreateLookAt(new
Vector3(0, 1.5f, 3), Vector3.Zero, Vector3.Up);


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

GraphicsDevice.VertexDeclaration = vd;

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

effect.Parameters[«MaxHeight»].SetValue(maxHeight);

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

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

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

effect.Begin();

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

GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, vertices, 0, N * N, indices, 0, indices.Length / 3);

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

effect.End();


base.Draw(gameTime);

}

}

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

float4x4 World;

float4x4 View;

float4x4 Projection;

float MaxHeight;

// TODO: add effect parameters here.

struct VertexShaderInput

{


float4 Position : POSITION0;

float4 Color : COLOR0;


// TODO: add input channels such as texture


// coordinates and vertex colors here.

};

struct VertexShaderOutput

{


float4 Position : POSITION0;


float4 Color : COLOR0;


float Height : TEXCOORD0;


// 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);

output.Height = input.Position.y;

output.Color = input.Color;


// TODO: add your vertex shader code here.


return output;

}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0

{


// TODO: add your pixel shader code here.


float snowW = input.Color.r;

float rockW = input.Color.g;

float grassW = input.Color.b;

float waterW = input.Color.a;


float4 snowColor = float4(1,1,1,1);

float4 rockColor = float4(0.64, 0.16, 0.16, 1);

float4 grassColor = float4(0, 0.5, 0, 1);

float4 waterColor = float4(0, 0, 1, 1);


return snowW * snowColor +

rockW * rockColor +

grassW * grassColor +

waterW * waterColor;

}

technique Landscape

{


pass Pass1

{


// TODO: set renderstates here.


VertexShader = compile
vs_1_1 VertexShaderFunction();


PixelShader = compile
ps_2_0 PixelShaderFunction();

}

}

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

float4x4 World;

float4x4 View;

float4x4 Projection;

float MaxHeight;

float4 snowColor = float4(1,1,1,1);

float4 rockColor = float4(0.64, 0.16, 0.16, 1);

float4 grassColor = float4(0, 0.5, 0, 1);

float4 waterColor = float4(0, 0, 1, 1);

// TODO: add effect parameters here.

struct VertexShaderInput

{


float4 Position : POSITION0;

float4 Color : COLOR0;


// TODO: add input channels such as texture


// coordinates and vertex colors here.

};

struct VertexShaderOutput

{


float4 Position : POSITION0;


float4 Color : COLOR0;


float Height : TEXCOORD0;


// 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);

output.Height = input.Position.y;

output.Color = input.Color;


// TODO: add your vertex shader code here.


return output;

}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0

{


// TODO: add your pixel shader code here.


float snowW = input.Color.r;

float rockW = input.Color.g;

float grassW = input.Color.b;

float waterW = input.Color.a;


return snowW * snowColor +

rockW * rockColor +

grassW * grassColor +

waterW * waterColor;

}

technique Landscape

{


pass Pass1

{


// TODO: set renderstates here.


VertexShader = compile
vs_1_1 VertexShaderFunction();


PixelShader = compile
ps_2_0 PixelShaderFunction();

}

}

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

Точные вычисления весов, нет эффекта блочности

Эффект блочности

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

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

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

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s