Рендеринг меха в XNA Часть 2

Усовершенствования

Усовершенствование #1: Поддельная Преграда Межмеха

Одна проблема, которая является видимой, это если мы должны наложить целый кусок меха с тем же самым цветом.

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

 //based on layer depth, choose the amount of shading. 
 //we lerp between two values to avoid having the 
 //base of the fur pure black. 
 float shadow = lerp(0.4,1,CurrentLayer); 
 furColor *= shadow; 

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

Усовершенствование #2: Изменение Высоты

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

Во-первых, мы должны изменить функцию FillFurTexture (). Мы назначим на каждый пкчок волос число, которое представит максимальные слои включения, которые будут видны. Для нашего примера мы будем делить пучки на число уровней. У нас есть 1000 пучков и 10 уровней, первые 100 пучков (1000/10), только будет одним уровнем высоко, вторые 100 пучков ловко подходят ко второму уровню, и так далее, пока последняя сотня пучков не достигнет своей вершины. Это дает линейное распределение высот. В коде это смотрится так. Мы сначала вычислим, сколько пучков достигает каждого уровня. Потом, вычисляя позицию каждого пучка, мы выясним, какой группе это принадлежит, то есть, кто является максимальным уровнем из пучков. Тогда мы нормализуем это значение, деля его к общему количеству уровней в диапазоне [0.. 1]. Потом, мы устанавливаем это как значение на красный канал пикселя.

private void FillFurTexture(Texture2D furTexture, float density) 
{ 
 [...] //other computations 
 //compute the number of opaque pixels = nr of hair strands 
 int nrStrands = (int)(density * totalPixels); 

 //compute the number of strands that stop at each layer 
 int strandsPerLayer = nrStrands / nrOfLayers; 

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

 //compute max layer 
 int max_layer = i / strandsPerLayer; 
 //normalize into [0..1] range 
 float max_layer_n = (float)max_layer / (float)nrOfLayers; 

 //put color (which has an alpha value of 255, i.e. opaque) 
 //max_layer_n needs to be multiplied by 255 to 
 //achieve a color in [0..255] range 
 colors[x * width + y] = new Color( 
 (byte)(max_layer_n * 255), 0, 0, 255); 
 } 
 //set the pixels on the texture. 
 furTexture.SetData<Color>(colors); 
} 

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

float furVisibility =(CurrentLayer > furData.r) ? 0 : furData.a; 
furColor.a = (CurrentLayer == 0) ? 1 : furVisibility; 

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

Еще одно усовершенствование, которое может быть сделано, связанно с высотой позиций волос, должно сделать распределение высоты — не линейным. Чтобы сделать это, добавьте только строку, которая изменяет значение max_layer_n. Есть несколько способов изменить это, приводящие к различным результатам.

max_layer_n = (float)Math.Sin(max_layer_n); 

max_layer_n = (float)Math.Pow(max_layer_n,5); 

max_layer_n = (float)Math.Sqrt(max_layer_n); 

if ((max_layer_n > 0.05f) && (max_layer_n < 0.95f)) 
 max_layer_n = 0.5f; 

Для небольшого количества интересных эффектов Вы можете также использовать X и Y позиции, чтобы вычислить высоту.

max_layer_n = 0.2f + 0.8f * 
 ((float)Math.Sin((double)x / height * 20) / 2.0f + 0.5f); 

Vector2 dist = new Vector2((float)x / height - 0.5f, 
 (float)y / width - 0.5f); 
max_layer_n = 0.4f + 0.6f * 
 ((float)Math.Cos(dist.Length() * 50) / 2.0f + 0.5f); 

Вы можете экспериментировать с различными способами, изменять значение max_layer_n и выбрать то, что подходит.

Усовершенствование #3: Деформация

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

//movement vectors 
Vector3 gravity = new Vector3(0, -1.0f, 0); 
Vector3 forceDirection = Vector3.Zero; 
//final displacement for hair 
Vector3 displacement; 

Нам также нужен параметр для смещения в шейдере.

float3 Displacement; 

В вершинном шейдере, на сей раз, мы будем использовать этот параметр, чтобы изменить позицию вершины меха в пространстве. Мы не хотим просить то же самое смещение обо всех уровнях, потому что целый мех двигался бы. Мы умножим смещение с коэффициентом в пределах от от 0 до 1, основанный на обрабатываемом уровне. Если мы только полагали бы, что коэффициент CurrentLayer, смещения будет выглядеть нереалистичным. В реальной жизни пучок волос обуздан. Когда записано в ППЗУ перемоткой или затрагивал силой тяжести, потому что у различных частей пучка есть различная адаптационная способность и строковая напряженность. Чтобы моделировать это, мы будем использовать третью мощность CurrentLayer и получим нелинейное смещение. Возводим это в степень, способом, которым выращенное смещение кажется более естественным и дает лучшие результаты. Код смотрите ниже.

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

 float4 worldPosition = mul(float4(pos,1), World); 

 //make the displacement non linear, to make it look more like fur 
 float displacementFactor = pow(CurrentLayer, 3); 
 //apply the displacement 
 worldPosition.xyz +=Displacement*displacementFactor ; 

 [...] //rest of the shader 
 return output; 
} 

Назад к коду рисунка, добавьте следующие две строки в функцию Draw (). Они вычисляют конечное значение смещения, суммируя силу тяжести и другую силу (которая пока остается нулем). После этого значение установлено на параметре эффекта с тем же самым названием.

displacement = gravity + forceDirection; 
furEffect.Parameters["Displacement"].SetValue(displacement); 

Результат — волосы, затронутые силой тяжести.

Теперь добавьте новую строку в функцию Draw(), модифицируйте forceDirection, и смотрите.

forceDirection.X = (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds) * 0.5f; 

Вы можете больше экспериментировать, изменяя forceDirection или векторы силы тяжести, или мое применение к модели (furEffect. Параметры [«World»]), и увидеть, как ведет себя мех.

Применение Меха на Модели

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

Добавьте модель динозавра в Вашу папку Информационного наполнения, и удостоверьтесь, что связанные текстуры (dino.png и eyes.png) в той же самой папке, как модель.

Теперь мы должны добавить переменную, чтобы закрепить модель, и инициализировать ее внутри LoadContent ().

//dino model 
Model dino; 

protected override void LoadContent() 
{ 
 dino = Content.Load<Model>("dino"); 
 [...] 
} 

Нам будут нужны две функции. Одна из них просто тянет геометрию модели. Она берет, ту, которая должна быть оттянута, к которые будут использоваться эффекты, которые будет на устройстве. Это устанавливает значения World матрицы на эффекте, и из параметра
Texture, поэтому, текстура может красить мех модели, будет то же самое, как и набор текстур на модели.

private void DrawModelGeometry(Model model, Matrix[] bones, Effect effect) 
{ 
 foreach (ModelMesh mesh in model.Meshes) 
 { 
 effect.Parameters["World"].SetValue( 
 bones[mesh.ParentBone.Index]); 
 //set World Matrix 
 foreach (ModelMeshPart meshpart in mesh.MeshParts) 
 { 
 effect.Parameters["Texture"].SetValue( 
 ((BasicEffect)meshpart.Effect).Texture); 
 //set Texture 
 effect.CommitChanges(); //commit changes 
 graphics.GraphicsDevice.VertexDeclaration = 
 meshpart.VertexDeclaration; 

 graphics.GraphicsDevice.Vertices[0].SetSource( 
 mesh.VertexBuffer, 
 meshpart.StreamOffset, 
 meshpart.VertexStride); 

 graphics.GraphicsDevice.Indices = mesh.IndexBuffer; 
 //draw the geometry 
 graphics.GraphicsDevice.DrawIndexedPrimitives( 
 PrimitiveType.TriangleList, 
 meshpart.BaseVertex, 0, 
 meshpart.NumVertices, 
 meshpart.StartIndex, 
 meshpart.PrimitiveCount); 
 } 
 } 
} 

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

private void DrawFurModel(Model model) 
{ 
 Matrix[] bones = new Matrix[model.Bones.Count]; 
 model.CopyAbsoluteBoneTransformsTo(bones); 

 furEffect.Parameters["Displacement"].SetValue(displacement); 
 furEffect.Parameters["MaxHairLength"].SetValue(maxHairLength); 
 furEffect.Parameters["FurTexture"].SetValue(furTexture); 

 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(); 
 //draw geometry of current layer 
 DrawModelGeometry(model, bones, furEffect); 
 furEffect.CurrentTechnique.Passes[0].End(); 
 } 
 furEffect.End(); 
} 

После тонкой настройки небольшого количества значений, как размерность текстуры меха, силы и векторов силы тяжести, простой зов в DrawFurModel (dino); в функции Draw() приведет к изображению, изображённому ниже.

Добавляя простое освещение N*L в шейдере меха (иллюстрированный в сопроводительном исходном тексте), качество улучшено.

Заключения

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

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s