Использование Вершинных Текстур в играх на платформе XNA (перевод)
Catalin Zima
Теперь нам необходимо вызвать эту функцию и отрисовать систему частиц в методе Draw(). Мы используем additive blending (добавочное смешивание) и отключаем запись в буфер глубины, чтобы у нас не было проблем с отрисовкой частиц. По этой самой причине вся остальная геометрия сцены должна отрисовываться ДО системы частиц, чтобы информация о глубине записалась в буфер глубины и частицы, попадающие за другую геометрию, не рисовались.
Если Вы запустите программу сейчас, то увидите что-то наподобие этого:
Давайте посмотрим, что будет, если добавить воздействие ветра на наши частицы. В шейдере ParticlePhysics.fx нам необходимо добавить несколько параметров, которые будут описывать ветер, а затем используем их в пиксельном шейдере UpdateVelocitiesPS, чтобы изменить скорость частиц.
В классе Game1.cs, в методе SimulateParticles добавьте следующие строчки кода в начало метода:
А сейчас запустите приложение, и когда Вы двигаете левым стиком, “включается” ветер. Если подойти к реализации более глобально, Вы должны симулировать ветер в начальной точке с затуханием и силой ветра, для эффектов типа заклинаний или быстро движущихся объектов. Данные в шейдер можно передавать посредством массивов. К примеру, для определения столкновений, Вы можете использовать окружающие сферы (bounding spheres) и передавать их в шейдер массивами (центр сферы и ее радиус) и уже в нем просчитывать столкновения и соответствующим образом изменять скорость. Пару мыслей я высказал в заключительной части.
Бонус: Проверка столкновений с ландшафтом
Давайте рассмотрим, каким образом можно определить столкновение частиц с ландшафтом и прекращать рисование частиц на поверхности. Мы просто комбинируем два проекта (рисование ландшафта и системы частиц). И, к нашему сожалению, наблюдаем, как частицы пролетают сквозь поверхность ландшафта. Чтобы избавиться от этого артефакта мы будем читать карту смещения для ландшафта в пиксельном шейдере, используемом для обновления данных скоростей и координат. Для статичного ландшафта все довольно просто: когда определяем столкновение, устанавливаем скорость в 0, для динамически изменяемого ландшафта, нам необходимо обновлять координаты в соответствии с морфингом.
Так как в конце прошлой части у нас был динамический ландшафт, мы будем производить модификации именно под эту реализацию ландшафта. Начнем мы с открытия файла шейдера ParticlePhysics.fx и добавления в него самплера для карты смещения.
Далее, Вам потребуется добавить следующий код для UpdateVelocitiesPS и UpdatePositionsPS.
И, наконец, в приложении в классе Game1.cs мы должны дописать строку кода в методе SimulateParticles:
После того, как вы поменяете цвет очистки Color.SkyBlue, финальный результат должен выглядеть примерно следующим образом:
Дейтвия в результате столкновения, конечно, можно изменить. Мы также должны передать карту нормалей в шейдер ParticlePhysics.fx и реализовать более правдоподобную физику при столкновении частиц с ландшафтом, например отскок, скатывание частиц по склонам ландшафта, скапливание частиц в низинах и т.п. Но тут я хотел показать основные принципы реализации столкновений в вершинном шейдере, не углубляясь в нюансы реализации физики.
Этот раздел самый длинный и надеюсь, что он получился не слишком скучным. Мы рассмотрели реализацию хранения информации о частицах в двух текстурах и основные принципы отрисовки частиц при помощи VTF, так же мы добавили простейшую реализацию ветра и столкновений в систему частиц. Что можно было бы улучшить? Более реальное поведение физики, динамику системы частиц (генерация частиц с использованием эмиттеров и аттракторов и т.п.), несколько систем частиц, использующих общие текстуры. Вы можете свободно экспериментировать, и если будет что-нибудь интересное, пожалуйста, напишите мне.
Полный код этого раздела (в том числе столкновения на ландшафте) Вы можете скачать тут: Chapter3.zip
Так как карта трансформации строится на лету, то нет особой разницы какой формы объект находится на сцене и нам нет необходимости писать реализацию под каждый объект. Все что нам необходимо, это отрисовать объекты специализированным шейдером, и карта деформации будет соответствовать любой форме объектов сцены. Для данного урока я выбрал dude из примера Skinned Model sample, так как он уже анимирован и не имеет постоянной формы, так что результат будет лучше.
Перед тем как начнем писать код, нам необходимо подготовить новый проект. Подготовка проекта будет более глобальная по сравнению с предыдущими частями, вы можете либо следовать приведенной ниже инструкции или просто скачать уже подготовленный проект: Chapter4startup.zip.
Создайте новый Windows проект (мы будем использовать SkinnedModel.dll и SkinnedModelPipeline.dll из примера Skinned Model Sample для Windows или соответствующие файлы для XBOX 360).
Catalin Zima
Теперь нам необходимо вызвать эту функцию и отрисовать систему частиц в методе Draw(). Мы используем additive blending (добавочное смешивание) и отключаем запись в буфер глубины, чтобы у нас не было проблем с отрисовкой частиц. По этой самой причине вся остальная геометрия сцены должна отрисовываться ДО системы частиц, чтобы информация о глубине записалась в буфер глубины и частицы, попадающие за другую геометрию, не рисовались.
Использование Вершинных Текстур в играх на платформе XNA (перевод)
Catalin Zima
Теперь нам необходимо вызвать эту функцию и отрисовать систему частиц в методе Draw(). Мы используем additive blending (добавочное смешивание) и отключаем запись в буфер глубины, чтобы у нас не было проблем с отрисовкой частиц. По этой самой причине вся остальная геометрия сцены должна отрисовываться ДО системы частиц, чтобы информация о глубине записалась в буфер глубины и частицы, попадающие за другую геометрию, не рисовались.
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.Black);
SimulateParticles(gameTime);
[...] //тут прочий код рисования. Который у нас есть
// устанавливаем значения параметров шейдера
renderParticleEffect.Parameters["world"].SetValue(Matrix.Identity);
renderParticleEffect.Parameters["view"].SetValue(camera.View);
renderParticleEffect.Parameters["proj"].SetValue(camera.Projection);
renderParticleEffect.Parameters["textureMap"].SetValue(particleTexture);
renderParticleEffect.Parameters["positionMap"].SetValue(positionRT.GetTexture());
renderParticleEffect.CommitChanges();
graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
graphics.GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.One;
graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;
graphics.GraphicsDevice.RenderState.PointSpriteEnable = true;
using (VertexDeclaration decl = new VertexDeclaration(
graphics.GraphicsDevice,
VertexPositionColor.VertexElements))
{
graphics.GraphicsDevice.VertexDeclaration = decl;
renderParticleEffect.Begin();
renderParticleEffect.CurrentTechnique.Passes[0].Begin();
graphics.GraphicsDevice.Vertices[0].SetSource(
particlesVB,
0,
VertexPositionColor.SizeInBytes);
graphics.GraphicsDevice.DrawPrimitives(
PrimitiveType.PointList,
0,
particleCount * particleCount);
renderParticleEffect.CurrentTechnique.Passes[0].End();
renderParticleEffect.End();
}
graphics.GraphicsDevice.RenderState.PointSpriteEnable = false;
graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
}
Если Вы запустите программу сейчас, то увидите что-то наподобие этого:
Давайте посмотрим, что будет, если добавить воздействие ветра на наши частицы. В шейдере ParticlePhysics.fx нам необходимо добавить несколько параметров, которые будут описывать ветер, а затем используем их в пиксельном шейдере UpdateVelocitiesPS, чтобы изменить скорость частиц.
float4 windDirection;
float windStrength;
float4 UpdateVelocitiesPS(in float2 uv : TEXCOORD0) : COLOR
{
[...]
if (pos.w >= maxLife)
{
[...]
}
else
{
//gravity
velocity.y -= 20.0 * elapsedTime;
velocity += windDirection * windStrength * elapsedTime;
}
return velocity;
}
В классе Game1.cs, в методе SimulateParticles добавьте следующие строчки кода в начало метода:
Vector2 leftStick =
GamePad.GetState(
PlayerIndex.One,
GamePadDeadZone.Circular).ThumbSticks.Left;
if (leftStick.Length() > 0.2f)
{
physicsEffect.Parameters["windStrength"].SetValue(
leftStick.Length() * 50);
leftStick.Normalize();
physicsEffect.Parameters["windDirection"].SetValue(
new Vector4(-leftStick.X,
0,
leftStick.Y,
0));
}
else
{
physicsEffect.Parameters["windStrength"].SetValue(0);
}
А сейчас запустите приложение, и когда Вы двигаете левым стиком, “включается” ветер. Если подойти к реализации более глобально, Вы должны симулировать ветер в начальной точке с затуханием и силой ветра, для эффектов типа заклинаний или быстро движущихся объектов. Данные в шейдер можно передавать посредством массивов. К примеру, для определения столкновений, Вы можете использовать окружающие сферы (bounding spheres) и передавать их в шейдер массивами (центр сферы и ее радиус) и уже в нем просчитывать столкновения и соответствующим образом изменять скорость. Пару мыслей я высказал в заключительной части.
Бонус: Проверка столкновений с ландшафтом
Давайте рассмотрим, каким образом можно определить столкновение частиц с ландшафтом и прекращать рисование частиц на поверхности. Мы просто комбинируем два проекта (рисование ландшафта и системы частиц). И, к нашему сожалению, наблюдаем, как частицы пролетают сквозь поверхность ландшафта. Чтобы избавиться от этого артефакта мы будем читать карту смещения для ландшафта в пиксельном шейдере, используемом для обновления данных скоростей и координат. Для статичного ландшафта все довольно просто: когда определяем столкновение, устанавливаем скорость в 0, для динамически изменяемого ландшафта, нам необходимо обновлять координаты в соответствии с морфингом.
Так как в конце прошлой части у нас был динамический ландшафт, мы будем производить модификации именно под эту реализацию ландшафта. Начнем мы с открытия файла шейдера ParticlePhysics.fx и добавления в него самплера для карты смещения.
texture displacementMap;
sampler displacementSampler = sampler_state
{
Texture = <displacementMap>;
MIPFILTER = LINEAR;
MINFILTER = LINEAR;
MAGFILTER = LINEAR;
AddressU = Clamp;
AddressV = Clamp;
};
Далее, Вам потребуется добавить следующий код для UpdateVelocitiesPS и UpdatePositionsPS.
float4 UpdatePositionsPS(in float2 uv : TEXCOORD0) : COLOR
{
float4 pos = tex2D(positionSampler, uv);
if (pos.w >= maxLife)
{
[...]
}
else
{
float2 displacementUV = float2((pos.x / 4 + 128) / 256,
(pos.z / 4 + 128) / 256 );
float height = 3 + tex2D(displacementSampler,
displacementUV).x * 128;
if (pos.y < height)
{
pos.y = height;
}
else
{
// Update particle position
float4 velocity = tex2D(velocitySampler, uv);
pos.xyz += elapsedTime * velocity;
}
pos.w += elapsedTime;
}
return pos;
}
float4 UpdateVelocitiesPS(in float2 uv : TEXCOORD0) : COLOR
{
[...]
if (pos.w >= maxLife)
{
[...]
}
else
{
float2 displacementUV = float2( (pos.x / 4 + 128) / 256,
(pos.z / 4 + 128) / 256 );
float height = 3 + tex2D(displacementSampler,
displacementUV).x * 128;
if (pos.y <= height)
{
velocity = 0;
}
else
{
//гравитация
velocity.y -= 20.0 * elapsedTime;
velocity += windDirection * windStrength * elapsedTime;
}
}
return velocity;
}
Тут много хардкода. Значение 3+ при расчете высоты это просто смещение, чтобы “держать” частицы немного выше поверхности ландшафта. Значение 128, на которое происходит умножение при расчете высоты, совершенно случайно совпадает со значением maxHeight шейдера VTFDisplacement.fx. По идее эти значения надо сделать переменными и задавать из приложения. Теперь разберемся со странной операцией (pos.x/4 + 128)/256. Трансформация пространственных координат в текстурные координаты смещения. 4 – это значение Grid.CellSize, а 256 - Grid.Dimension. Это обратная формула, которая используется для расчета координат в методе GenerateStructures класса Grid.cs. Здесь я выбрал подход хардкода для удобства, но в реальном приложении эти значения должны задаваться через параметры шейдера.
vert.Position = new Vector3((i - dimension / 2.0f) * cellSize,
0,
(j - dimension / 2.0f) * cellSize);
И, наконец, в приложении в классе Game1.cs мы должны дописать строку кода в методе SimulateParticles:
physicsEffect.Parameters["displacementMap"].SetValue(
morphRenderTarget.GetTexture());
После того, как вы поменяете цвет очистки Color.SkyBlue, финальный результат должен выглядеть примерно следующим образом:
Дейтвия в результате столкновения, конечно, можно изменить. Мы также должны передать карту нормалей в шейдер ParticlePhysics.fx и реализовать более правдоподобную физику при столкновении частиц с ландшафтом, например отскок, скатывание частиц по склонам ландшафта, скапливание частиц в низинах и т.п. Но тут я хотел показать основные принципы реализации столкновений в вершинном шейдере, не углубляясь в нюансы реализации физики.
Этот раздел самый длинный и надеюсь, что он получился не слишком скучным. Мы рассмотрели реализацию хранения информации о частицах в двух текстурах и основные принципы отрисовки частиц при помощи VTF, так же мы добавили простейшую реализацию ветра и столкновений в систему частиц. Что можно было бы улучшить? Более реальное поведение физики, динамику системы частиц (генерация частиц с использованием эмиттеров и аттракторов и т.п.), несколько систем частиц, использующих общие текстуры. Вы можете свободно экспериментировать, и если будет что-нибудь интересное, пожалуйста, напишите мне.
Полный код этого раздела (в том числе столкновения на ландшафте) Вы можете скачать тут: Chapter3.zip
I. Шаги на снегу
В этом разделе мы не будем использовать какие-либо новые технологии или подходы, мы будем использовать наработки предыдущих разделов для реализации эффекта “оставление шагов на снегу”. Снег будет сминаться под ногами персонажа и со временем выравниваться (скажем, при заносе следов снегом). Снег будет рисоваться с использованием карты смещения, точно так же как мы рисовали ландшафт. Чтобы добиться эффекта деформации поверхности, мы будем отрисовывать сцену в текстуру, которая будет использоваться в качестве карты смещения. В момент отрисовки в текстуру, виртуальная камера будет снимать сцену снизу, смотря вверх по оси Y. Мы будем использовать специальный шейдер который будет закрашивать пиксели, чем ближе к поверхности “земли”, тем светлее, соответственно, чем дальше тем темнее. Это значит, что белые участки означают полную деформацию сетки, а темные – участки без деформации. Эта карта деформации будет вычитаться из карты смещения, которой мы рисуем снег. Далее, для симуляции заноса следов снегом, мы будем добавлять небольшие рандомные значения к карте смещения, и со временем карта смещения будет “выравниваться”, а за ней и поверхность снега.
Так как карта трансформации строится на лету, то нет особой разницы какой формы объект находится на сцене и нам нет необходимости писать реализацию под каждый объект. Все что нам необходимо, это отрисовать объекты специализированным шейдером, и карта деформации будет соответствовать любой форме объектов сцены. Для данного урока я выбрал dude из примера Skinned Model sample, так как он уже анимирован и не имеет постоянной формы, так что результат будет лучше.
Перед тем как начнем писать код, нам необходимо подготовить новый проект. Подготовка проекта будет более глобальная по сравнению с предыдущими частями, вы можете либо следовать приведенной ниже инструкции или просто скачать уже подготовленный проект: Chapter4startup.zip.
Создайте новый Windows проект (мы будем использовать SkinnedModel.dll и SkinnedModelPipeline.dll из примера Skinned Model Sample для Windows или соответствующие файлы для XBOX 360).
- Добавьте в проект классы Camera.cs, Grid.cs и DudeEntity.cs. DudeEntity.cs – вспомогательный класс, который будет использоваться, чтобы заставить модель ходить по “кругу” через рандомные точки.
- Добавьте в проект, в папку Content (папка обязательно должна иметь имя Content, так как SkinnedModelProcessor заточен именно под это наименование) файлы из архива DudeModel.zip (находится в архиве Resources.zip).
- Скопируйте SkinnedModel.dll и SkinnedModelPipeline.dll в папку проекта.
- Добавьте ссылку на SkinnedModel.dll (Project->Add Reference->Browse).
- В свойствах проекта на вкладке Content Pipeline добавьте ссылку на SkinnedModelPipeline.dll.
- Добавьте в проект модель dude.fbx, переключите Content Processor на SkinnedModelProcessor.
- Скопируйте файл noise.png в папку Content и добавьте в проект.
На данном этапе уже можно начинать писать код. Для начала мы добавим камеру и анимированную модель и заставим ее ходить по нашей сцене. Нам необходимо в класс Game1 добавить несколько переменных, камера инициализируется в конструкторе класса. Затем в методе LoadGraphicsContent мы загружаем модель и инициализируем dudeEntity.
[...]
using SkinnedModel;
using VTFTutorial;
[...]
Camera camera;
Model dudeModel;
DudeEntity dudeEntity;
public Game1()
{
[...]
camera = new Camera(this);
Components.Add(camera);
}
[...]
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
dudeModel = content.Load<Model>("Content\\dude");
dudeEntity = new DudeEntity();
dudeEntity.Initialize(dudeModel.Tag as SkinningData);
}
}
В методе Update обновляем данные модели и в методе Draw рисуем модель стандартным кодом отрисовки, с использованием матриц костей модели.
protected override void Update(GameTime gameTime)
{
[...]
dudeEntity.Update(gameTime);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
Matrix[] bones = dudeEntity.AnimationPlayer.GetSkinTransforms();
foreach (ModelMesh mesh in dudeModel.Meshes)
{
foreach (Effect effect in mesh.Effects)
{
effect.Parameters["Bones"].SetValue( bones );
effect.Parameters["View"].SetValue( camera.View );
effect.Parameters["Projection"].SetValue( camera.Projection );
}
mesh.Draw();
}
base.Draw(gameTime);
}
Если запустите программу, то Вы должны увидеть, как персонаж ходит по сцене.
Если Вы запустите программу сейчас, то увидите что-то наподобие этого:
Давайте посмотрим, что будет, если добавить воздействие ветра на наши частицы. В шейдере ParticlePhysics.fx нам необходимо добавить несколько параметров, которые будут описывать ветер, а затем используем их в пиксельном шейдере UpdateVelocitiesPS, чтобы изменить скорость частиц.
float4 windDirection;
float windStrength;
float4 UpdateVelocitiesPS(in float2 uv : TEXCOORD0) : COLOR
{
[...]
if (pos.w >= maxLife)
{
[...]
}
else
{
//gravity
velocity.y -= 20.0 * elapsedTime;
velocity += windDirection * windStrength * elapsedTime;
}
return velocity;
}
В классе Game1.cs, в методе SimulateParticles добавьте следующие строчки кода в начало метода:
Vector2 leftStick =
GamePad.GetState(
PlayerIndex.One,
GamePadDeadZone.Circular).ThumbSticks.Left;
if (leftStick.Length() > 0.2f)
{
physicsEffect.Parameters["windStrength"].SetValue(
leftStick.Length() * 50);
leftStick.Normalize();
physicsEffect.Parameters["windDirection"].SetValue(
new Vector4(-leftStick.X,
0,
leftStick.Y,
0));
}
else
{
physicsEffect.Parameters["windStrength"].SetValue(0);
}
А сейчас запустите приложение, и когда Вы двигаете левым стиком, “включается” ветер. Если подойти к реализации более глобально, Вы должны симулировать ветер в начальной точке с затуханием и силой ветра, для эффектов типа заклинаний или быстро движущихся объектов. Данные в шейдер можно передавать посредством массивов. К примеру, для определения столкновений, Вы можете использовать окружающие сферы (bounding spheres) и передавать их в шейдер массивами (центр сферы и ее радиус) и уже в нем просчитывать столкновения и соответствующим образом изменять скорость. Пару мыслей я высказал в заключительной части.
Бонус: Проверка столкновений с ландшафтом
Давайте рассмотрим, каким образом можно определить столкновение частиц с ландшафтом и прекращать рисование частиц на поверхности. Мы просто комбинируем два проекта (рисование ландшафта и системы частиц). И, к нашему сожалению, наблюдаем, как частицы пролетают сквозь поверхность ландшафта. Чтобы избавиться от этого артефакта мы будем читать карту смещения для ландшафта в пиксельном шейдере, используемом для обновления данных скоростей и координат. Для статичного ландшафта все довольно просто: когда определяем столкновение, устанавливаем скорость в 0, для динамически изменяемого ландшафта, нам необходимо обновлять координаты в соответствии с морфингом.
Так как в конце прошлой части у нас был динамический ландшафт, мы будем производить модификации именно под эту реализацию ландшафта. Начнем мы с открытия файла шейдера ParticlePhysics.fx и добавления в него самплера для карты смещения.
texture displacementMap;
sampler displacementSampler = sampler_state
{
Texture = <displacementMap>;
MIPFILTER = LINEAR;
MINFILTER = LINEAR;
MAGFILTER = LINEAR;
AddressU = Clamp;
AddressV = Clamp;
};
Далее, Вам потребуется добавить следующий код для UpdateVelocitiesPS и UpdatePositionsPS.
float4 UpdatePositionsPS(in float2 uv : TEXCOORD0) : COLOR
{
float4 pos = tex2D(positionSampler, uv);
if (pos.w >= maxLife)
{
[...]
}
else
{
float2 displacementUV = float2((pos.x / 4 + 128) / 256,
(pos.z / 4 + 128) / 256 );
float height = 3 + tex2D(displacementSampler,
displacementUV).x * 128;
if (pos.y < height)
{
pos.y = height;
}
else
{
// Update particle position
float4 velocity = tex2D(velocitySampler, uv);
pos.xyz += elapsedTime * velocity;
}
pos.w += elapsedTime;
}
return pos;
}
float4 UpdateVelocitiesPS(in float2 uv : TEXCOORD0) : COLOR
{
[...]
if (pos.w >= maxLife)
{
[...]
}
else
{
float2 displacementUV = float2( (pos.x / 4 + 128) / 256,
(pos.z / 4 + 128) / 256 );
float height = 3 + tex2D(displacementSampler,
displacementUV).x * 128;
if (pos.y <= height)
{
velocity = 0;
}
else
{
//гравитация
velocity.y -= 20.0 * elapsedTime;
velocity += windDirection * windStrength * elapsedTime;
}
}
return velocity;
}
Тут много хардкода. Значение 3+ при расчете высоты это просто смещение, чтобы “держать” частицы немного выше поверхности ландшафта. Значение 128, на которое происходит умножение при расчете высоты, совершенно случайно совпадает со значением maxHeight шейдера VTFDisplacement.fx. По идее эти значения надо сделать переменными и задавать из приложения. Теперь разберемся со странной операцией (pos.x/4 + 128)/256. Трансформация пространственных координат в текстурные координаты смещения. 4 – это значение Grid.CellSize, а 256 - Grid.Dimension. Это обратная формула, которая используется для расчета координат в методе GenerateStructures класса Grid.cs. Здесь я выбрал подход хардкода для удобства, но в реальном приложении эти значения должны задаваться через параметры шейдера.
vert.Position = new Vector3((i - dimension / 2.0f) * cellSize,
0,
(j - dimension / 2.0f) * cellSize);
И, наконец, в приложении в классе Game1.cs мы должны дописать строку кода в методе SimulateParticles:
physicsEffect.Parameters["displacementMap"].SetValue(
morphRenderTarget.GetTexture());
После того, как вы поменяете цвет очистки Color.SkyBlue, финальный результат должен выглядеть примерно следующим образом:
Дейтвия в результате столкновения, конечно, можно изменить. Мы также должны передать карту нормалей в шейдер ParticlePhysics.fx и реализовать более правдоподобную физику при столкновении частиц с ландшафтом, например отскок, скатывание частиц по склонам ландшафта, скапливание частиц в низинах и т.п. Но тут я хотел показать основные принципы реализации столкновений в вершинном шейдере, не углубляясь в нюансы реализации физики.
Этот раздел самый длинный и надеюсь, что он получился не слишком скучным. Мы рассмотрели реализацию хранения информации о частицах в двух текстурах и основные принципы отрисовки частиц при помощи VTF, так же мы добавили простейшую реализацию ветра и столкновений в систему частиц. Что можно было бы улучшить? Более реальное поведение физики, динамику системы частиц (генерация частиц с использованием эмиттеров и аттракторов и т.п.), несколько систем частиц, использующих общие текстуры. Вы можете свободно экспериментировать, и если будет что-нибудь интересное, пожалуйста, напишите мне.
Полный код этого раздела (в том числе столкновения на ландшафте) Вы можете скачать тут: Chapter3.zip
I. Шаги на снегу
В этом разделе мы не будем использовать какие-либо новые технологии или подходы, мы будем использовать наработки предыдущих разделов для реализации эффекта “оставление шагов на снегу”. Снег будет сминаться под ногами персонажа и со временем выравниваться (скажем, при заносе следов снегом). Снег будет рисоваться с использованием карты смещения, точно так же как мы рисовали ландшафт. Чтобы добиться эффекта деформации поверхности, мы будем отрисовывать сцену в текстуру, которая будет использоваться в качестве карты смещения. В момент отрисовки в текстуру, виртуальная камера будет снимать сцену снизу, смотря вверх по оси Y. Мы будем использовать специальный шейдер который будет закрашивать пиксели, чем ближе к поверхности “земли”, тем светлее, соответственно, чем дальше тем темнее. Это значит, что белые участки означают полную деформацию сетки, а темные – участки без деформации. Эта карта деформации будет вычитаться из карты смещения, которой мы рисуем снег. Далее, для симуляции заноса следов снегом, мы будем добавлять небольшие рандомные значения к карте смещения, и со временем карта смещения будет “выравниваться”, а за ней и поверхность снега.Так как карта трансформации строится на лету, то нет особой разницы какой формы объект находится на сцене и нам нет необходимости писать реализацию под каждый объект. Все что нам необходимо, это отрисовать объекты специализированным шейдером, и карта деформации будет соответствовать любой форме объектов сцены. Для данного урока я выбрал dude из примера Skinned Model sample, так как он уже анимирован и не имеет постоянной формы, так что результат будет лучше.
Перед тем как начнем писать код, нам необходимо подготовить новый проект. Подготовка проекта будет более глобальная по сравнению с предыдущими частями, вы можете либо следовать приведенной ниже инструкции или просто скачать уже подготовленный проект: Chapter4startup.zip.
Создайте новый Windows проект (мы будем использовать SkinnedModel.dll и SkinnedModelPipeline.dll из примера Skinned Model Sample для Windows или соответствующие файлы для XBOX 360).
- Добавьте в проект классы Camera.cs, Grid.cs и DudeEntity.cs. DudeEntity.cs – вспомогательный класс, который будет использоваться, чтобы заставить модель ходить по “кругу” через рандомные точки.
- Добавьте в проект, в папку Content (папка обязательно должна иметь имя Content, так как SkinnedModelProcessor заточен именно под это наименование) файлы из архива DudeModel.zip (находится в архиве Resources.zip).
- Скопируйте SkinnedModel.dll и SkinnedModelPipeline.dll в папку проекта.
- Добавьте ссылку на SkinnedModel.dll (Project->Add Reference->Browse).
- В свойствах проекта на вкладке Content Pipeline добавьте ссылку на SkinnedModelPipeline.dll.
- Добавьте в проект модель dude.fbx, переключите Content Processor на SkinnedModelProcessor.
- Скопируйте файл noise.png в папку Content и добавьте в проект.
[...]
using SkinnedModel;
using VTFTutorial;
[...]
Camera camera;
Model dudeModel;
DudeEntity dudeEntity;
public Game1()
{
[...]
camera = new Camera(this);
Components.Add(camera);
}
[...]
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
dudeModel = content.Load<Model>("Content\\dude");
dudeEntity = new DudeEntity();
dudeEntity.Initialize(dudeModel.Tag as SkinningData);
}
}В методе Update обновляем данные модели и в методе Draw рисуем модель стандартным кодом отрисовки, с использованием матриц костей модели.
protected override void Update(GameTime gameTime)
{
[...]
dudeEntity.Update(gameTime);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
Matrix[] bones = dudeEntity.AnimationPlayer.GetSkinTransforms();
foreach (ModelMesh mesh in dudeModel.Meshes)
{
foreach (Effect effect in mesh.Effects)
{
effect.Parameters["Bones"].SetValue( bones );
effect.Parameters["View"].SetValue( camera.View );
effect.Parameters["Projection"].SetValue( camera.Projection );
}
mesh.Draw();
}
base.Draw(gameTime);
}Если запустите программу, то Вы должны увидеть, как персонаж ходит по сцене.
Комментариев нет:
Отправить комментарий