Рендеринг меха в XNA

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

http://www.ziggyware.com/readarticle.php?article_id=194 – оригинал статьи

ссылки, указанные в самой статье:

http://www.ziggyware.com/ziggywareimages/catalin/Camera.cs — Camera.cs

http://www.emanueleferonato.com/ — скачайте изображения для добавления их в Content project

http://www.ziggyware.com/downloads.php?cat_id=4&download_id=124

http://www.ziggyware.com/ziggywareimages/catalin/dino.zip — Dino.zip

http://www.ziggyware.com/downloads.php?cat_id=4&download_id=125

Рендеринг Меха

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

Рендеринг оболочками

Основная идея выполнения оболочками изображена на рисунке.

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

  • Информация о позициях пучков волос. Выполняя каждый сектор, мы должны знать, какие пиксели будут видимы (принадлежащий пучку волос), а какие пиксели будут прозрачны. Мы будем хранить эту информацию в текстуре. Непрозрачный пиксель в этой текстуре будет пучком волос. Создавая эту текстуру, мы беспорядочно поместим непрозрачные пиксели туда. Число пикселей, которые мы поместим, будет зависеть от значения, которое укажет плотность волос.
  • Позиция секторов. Для простой демонстрации на плоском горизонтальном рисунке позиции секторов просто росли бы по оси вверх. Однако мы хотим сделать это легко применимым на любой модели, таким образом, лучший способ сделать это — переместить каждый сектор, основанный на нормали основного полигона. Фактически, чтобы создать секторы, мы только выполним каждый полигон несколько раз, и при каждом рендеринге, мы переместим полигоны с маленьким расстоянием, основанным на их нормалях.
  • Цвет каждого пучка волос. Первоначально, мы будем просто читать цвет от карты плотности, но впоследствии, мы будем читать цвет от отдельной карты, чтобы достигнуть более хорошего результата.

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

Создание карты меха

Как замечено ранее, карта меха будет использоваться, чтобы выполнить каждый сектор меха. Она будет содержать прозрачные пиксели — вне областей меха, и непрозрачные пиксели для пучков волос. Мы, вычисляем число этих пикселей, используя плотность, а затем беспорядочно размещаем их по поверхности текстуры. Законченная функция, которая делает это, представленна ниже

/// <summary> 
/// This functions prepares a texture to be used for fur rendering 
/// </summary> 
/// <param name="furTexture">This will contain the final texture</param> 
/// <param name="density">Hair density in [0..1] range </param> 
private void FillFurTexture(Texture2D furTexture, float density) 
{ 
 //read the width and height of the texture 
 int width = furTexture.Width; 
 int height = furTexture.Height; 
 int totalPixels = width * height; 

 //an array to hold our pixels 
 Color[] colors; 
 colors = new Color[totalPixels]; 

 //random number generator 
 Random rand = new Random(); 

 //initialize all pixels to transparent black 
 for (int i = 0; i < totalPixels; i++) 
 colors[i] = Color.TransparentBlack; 

 //compute the number of opaque pixels = nr of hair strands 
 int nrStrands = (int)(density * totalPixels); 

 //fill texture with opaque pixels 
 for (int i = 0; i < nrStrands; i++) 
 { 
 int x, y; 
 //random position on the texture 
 x = rand.Next(height); 
 y = rand.Next(width); 
 //put color (which has an alpha value of 255, i.e. opaque) 
 colors[x * width + y] = Color.Gold; 
 } 

 //set the pixels on the texture. 
 furTexture.SetData<Color>(colors); 
} 

После текстуролизации меха мы сначала попытаемся выполнить мех на единственном полигоне. Для простоты мы будем просто использовать массив вершины, который будет оттягивать мех, используя DrawUserPrimitives. Позже в обучающей программе, мы будем использовать Образцовый класс (Model class).

Генерация геометрии

Функция, которая генерирует геометрию, создает два треугольника, находящиеся на координатной плоскости с-Z осью, как нормаль, и соответствующих координатах текстуры.

VertexPositionNormalTexture[] vertices; 

private void GenerateGeometry() 
{ 
 vertices = new VertexPositionNormalTexture[6]; 
 vertices[0] = new VertexPositionNormalTexture( 
 new Vector3(-10,0,0), 
 -Vector3.UnitZ, 
 new Vector2(0,0)); 
 vertices[1] = new VertexPositionNormalTexture( 
 new Vector3(10,20,0), 
 -Vector3.UnitZ, 
 new Vector2(1,1)); 
 vertices[2] = new VertexPositionNormalTexture( 
 new Vector3(-10, 20, 0), 
 -Vector3.UnitZ, 
 new Vector2(0, 1)); 

 vertices[3] = vertices[0]; 
 vertices[4] = new VertexPositionNormalTexture( 
 new Vector3(10, 0, 0), 
 -Vector3.UnitZ, 
 new Vector2(1, 0)); 
 vertices[5] = vertices[1]; 
} 

Рендеринг оболочек

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

Теперь, когда у Вас есть текстура меха и геометрия на следующем шаге мы должны написать шейдер, который обработает рендеринг меха. Чтобы сделать это, создайте новый шейдер в Контент (Информационного наполнения) проекте. Для шейдера, нам нужны несколько параметров. Ощущение жизни, представление и проектирование матрицы известны, но мы также нуждаемся в параметре, который управляет длиной волос, давайте называть это MaxHairLength (максимальная длина волос), и тем, который говорит нам, какой горизонтальный сектор (то есть уровень) в настоящее время обрабатывается. Чтобы достигнуть небольшого количества независимости от фактических чисел уровней, которые фактически используются, мы будем полагать, что параметр CurrentLayer принадлежит диапазону [0..1], 0 значений говорит, что текущий уровень является самым близким реальной поверхности, и 1 — что это является самым дальним от него. Промежуточные уровни отобразятся, чтобы усреднять значения. Они умножены на максимальную длину волос, и это приведет к смещению уровня, обрабатываемого в соответствующий момент. Наконец, мы должны читать данные с текстуры меха, таким образом, нам нужны параметры и сэмплер для него.

float4x4 World; 
float4x4 View; 
float4x4 Projection; 

float CurrentLayer; //value between 0 and 1 
float MaxHairLength; //maximum hair length 

texture FurTexture; 
sampler FurSampler = sampler_state 
{ 
 Texture = (FurTexture); 
 MinFilter = Point; 
 MagFilter = Point; 
 MipFilter = Point; 
 AddressU = Wrap; 
 AddressV = Wrap; 
}; 

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

struct VertexShaderInput 
{ 
 float3 Position : POSITION0; 
 float3 Normal : NORMAL0; 
 float2 TexCoord : TEXCOORD0; 
}; 

struct VertexShaderOutput 
{ 
 float4 Position : POSITION0; 
 float2 TexCoord : TEXCOORD0; 
}; 

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

float3 pos = input.Position + input.Normal * MaxHairLength * CurrentLayer; 

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

VertexShaderOutput FurVertexShader(VertexShaderInput input) 
{ 
 VertexShaderOutput output; 
 float3 pos; 
 pos = input.Position + input.Normal * MaxHairLength * CurrentLayer; 

 float4 worldPosition = mul(float4(pos,1), World); 
 float4 viewPosition = mul(worldPosition, View); 
 output.Position = mul(viewPosition, Projection); 

 output.TexCoord = input.TexCoord; 
 return output; 
} 

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

float4 FurPixelShader(VertexShaderOutput input) : COLOR0 
{ 
 return tex2D(FurSampler, input.TexCoord); 
} 

technique Fur 
{ 
 pass Pass1 
 { 
 AlphaBlendEnable = true; 
 SrcBlend = SRCALPHA; 
 DestBlend = INVSRCALPHA; 
 CullMode = None; 

 VertexShader = compile vs_2_0 FurVertexShader(); 
 PixelShader = compile ps_2_0 FurPixelShader(); 
 } 
} 

Соединение воедино: рисунок и геометрия

Для более чистого кода, создадим функцию, которая отвечает за геометрию, определённую ранее. Эта функция должна установливает объявление вершины на устройстве, и подать сигнал в GraphicsDevice. DrawUserPrimitives.

private void DrawGeometry() 
{ 
 using (VertexDeclaration vdecl = new VertexDeclaration( 
 GraphicsDevice, 
 VertexPositionNormalTexture.VertexElements)) 
 { 
 GraphicsDevice.VertexDeclaration = vdecl; 
 GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>( 
 PrimitiveType.TriangleList, 
 vertices, 0, 2); 
 } 
} 

Теперь мы должны написать некоторый код в функции Draw, код, который фактически тянет наш объект и уровни меха. Перед этим мы должны добавить некоторых участников, и инициализировать их в LoadContent () функции. Также добавьте файл Camera.cs к Вашему проекту, и инициализируйте камеру в функции Initialize игры.

[...] 
using XNASimpleCamera; //namespace of Camera.cs 
[...] 

public class Game1 : Microsoft.Xna.Framework.Game 
{ 
 [...] 

 //simple camera for use in the game 
 Camera camera; 
 //texture containing fur data 
 Texture2D furTexture; 
 //effect for fur shaders 
 Effect furEffect; 
 //number of layers of fur 
 int nrOfLayers = 40; 
 //total length of the hair 
 float maxHairLength = 2.0f; 
 //density of hair 
 float density = 0.2f; 

 protected override void Initialize() 
 { 
 //initialize the camera 
 camera = new Camera(this); 
 Components.Add(camera); 
 base.Initialize(); 
 } 

 protected override void LoadContent() 
 { 
 [...] 
 //generate the geometry 
 GenerateGeometry(); 
 //load the effect 
 furEffect = Content.Load<Effect>("FurEffect"); 
 //create the texture 
 furTexture = new Texture2D(GraphicsDevice, 
 256, 256, 1, 
 TextureUsage.None, 
 SurfaceFormat.Color); 
 //fill the texture 
 FillFurTexture(furTexture, density); 
 } 
} 

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

После этого, мы тянем режим времени nrOfLayers, и устанавливать параметры шейдера
CurrentLayer перед каждым рисунком.

protected override void Draw(GameTime gameTime) 
{ 
 graphics.GraphicsDevice.Clear(Color.CornflowerBlue); 

 furEffect.Parameters["World"].SetValue( 
 Matrix.CreateTranslation(0,-10,0)); 
 furEffect.Parameters["View"].SetValue(camera.View); 
 furEffect.Parameters["Projection"].SetValue(camera.Projection); 
 furEffect.Parameters["MaxHairLength"].SetValue(maxHairLength); 
 furEffect.Parameters["FurTexture"].SetValue(furTexture); 

 furEffect.Begin(); 
 for (int i = 0; i < nrOfLayers; i++) 
 { 
 furEffect.Parameters["CurrentLayer"].SetValue( 
 (float)i/nrOfLayers); 
 furEffect.CommitChanges(); 
 furEffect.CurrentTechnique.Passes[0].Begin(); 
 DrawGeometry(); 
 furEffect.CurrentTechnique.Passes[0].End(); 

 } 
 furEffect.End(); 
} 

Выполнив, у вас должно получится:

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

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

Теперь добавьте участника к Игровому классу, чтобы закрепить эту текстуру, и загрузить её в функцию LoadContent ().

Texture2D furColorTexture; 
protected override void LoadContent() 
{ 
 [...] 
 furColorTexture = Content.Load<Texture2D>("bigtiger"); 
} 

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

Чтобы сделать слой базы непрозрачным, мы сравним переменную CurrentLayer с 0. Если это 0, мы устанавливаем альфу результат 1 (непрозрачный), иначе, мы устанавливаем его в чтение значения с текстуры.

texture Texture; 
sampler FurColorSampler = sampler_state 
{ 
 Texture = (Texture); 
 MinFilter = Linear; 
 MagFilter = Linear; 
 MipFilter = Linear; 
 AddressU = Wrap; 
 AddressV = Wrap; 
}; 
[...] 
float4 FurPixelShader(VertexShaderOutput input) : COLOR0 
{ 
 float4 furData = tex2D(FurSampler, input.TexCoord); 
 float4 furColor = tex2D(FurColorSampler, input.TexCoord); 

 //make opaque if lowest layer, otherwise use alpha channel of furData 
 furColor.a = (CurrentLayer == 0) ? 1 : furData.a; 
 return furColor; 
} 

Тогда добавьте следующую строку в Вашей функции Draw().

furEffect.Parameters["Texture"].SetValue(furColorTexture); 

Результат ниже

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s