Навигация
· XNA FAQ
· С чего начать
· Конкурсы
· Обратная связь
· XNA Блоги
Сейчас на сайте
· Гостей: 1

· Пользователей: 0

· Всего пользователей: 3,684
· Новый пользователь: headron
Последние фото
Эх, чуть не проспал закрытие.
Эх, чуть не проспал ...
Альбом: XNA Engine

GB
GB
Альбом: XNA Engine

South Park Coon & Friends
South Park Coon & Fr...
Альбом: XNA Games

Блоги
yavshoke
» XboxOne - интерес...
dampirik
» Push уведомления ...
dampirik
» Реклама,статистик...
Chort
» XNA и StartCoroutine
Chort
» Curve Class
dampirik
» Реклама, статисти...
dampirik
» Увеличение скорос...
dampirik
» Реклама, статисти...
general
» Распаковка DxtCom...
general
» Как работать с XN...
Поддержка
microsoft.com
1gb.ru - Дом для вашего сайта
Статистика посещений:

Hardware Instancing
Hardware Instancing – техника, позволяющая выводить множество объектов за один вызов драйвера. Т.е., нам достаточно один раз вызвать DrawIndexedPrimitives(…) и получить на экране не однин, а несколько mech’ей (атака клонов :)), расположенных по разным координатам и повернутых под разными углами.

Но об этом позже. Суть техники заключается в оперировании вершинными буферами GraphicsDevice.Vertices, которые представляют собой класс VertexStreamCollection. 

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

- Упростить экземпляр изначального объекта до разумного минимума. Точка нам не подходит, потому что демонстрация примера будет не столь наглядной по причине ее, точки, малого размера. Квадрат тоже не подойдет, потому что два треугольника это уже лишне. А вот один треугольник в самый раз. Без фанатизма. Это позволит не загромождать наш пример лишними данными.

- Упростить данные о вершинах изначального объекта. Нам не потребуются Normal, TextureCoordinate и т.д. Оставим только Position и Color, получается стандартный тип VertexPositionColor.

- Принять следующее условие: Каждый треугольник должен иметь не только собственное место в пространстве, но и углы поворота. Пусть будет один угол, т.е. поворачивать будем вокруг одной оси. Для выполнения данного условия нам потребуется одна собственная матрица мира на один треугольник.

С учетом вышеперечилтенных требований результат должен быть примерно таким:
articles: HardInstancing.png

Для реализации данной техники нам потребуется следующий эффект:
float4x4 matVP		: VIEWPROGECTION;
 
struct VS_INPUT
{
    float4		Position		: POSITION0;
    float4		Color			: COLOR0;
    float4x4		InstanceWorld		: TEXCOORD0;
};
 
struct VS_OUTPUT
{
    float4		Position		: POSITION0;
    float4		Color			: COLOR0;
};
 
VS_OUTPUT VS(VS_INPUT input)
{
VS_OUTPUT output 	= (VS_OUTPUT)0;
    	output.Position 	= mul(input.Position, 
mul(transpose(input.InstanceWorld), matVP));
    	output.Color 		= input.Color;
    	return output;
}
 
struct PS_INPUT 
{
	float4 Position			: POSITION0;
	float4 Color				: COLOR0;
};
 
float4 PS(PS_INPUT input) 			: COLOR0
{
	return input.Color;
}
 
technique Instancing
{
    pass Pass1
    {
        VertexShader	= compile vs_3_0 VS();
        PixelShader	= compile ps_2_0 PS();
    }
}

 

Здесь вам все должно быть знакомо, за исключением одного момента. Обратите внимание на стуруктуру VS_INPUT, а конкретно на поле: 
{… float4x4 InstanceWorld:TEXCOORD0;} 

Данное поле и будет у нас отвечать за получение матрицы мира для конкретной точки. И, впоследствии, перемножаться в вершинном шейдере с результатом произведения матриц matView и matProjection, matVP. А далее влиять на конечное положение вершины.

Теперь посмотрим на сам класс, реализующий наш пример:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
 
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
 
namespace Instancing
{
    public class Instancing
    {
 
        public bool                             bReady = false;
 
        private VertexPositionColor[]           vertex;
        private int[]                           index;
        private VertexBuffer                    vertexBuffer;
        private IndexBuffer                     indexBuffer;
 
        private VertexDeclaration               vertexDeclarationInstancing;
 
        private Effect                          effect;
 
        private Matrix[]                        matrix;
        private const int                       sizeofMatrix = sizeof(float) * 16;
        private int                             instanceDataSize;
 
        /// <summary>
        /// Инициализация техники 
        /// </summary>
        /// <param name="inGraphics">GraphicsDeviceManager</param>
        /// <param name="inEffect"></param>
        public Instancing(GraphicsDeviceManager inGraphics, Effect inEffect)
        {
            //Создаем свой VertexDeclaration 
            VertexElement[] elements = GetVertexElementVertexPositionColorMatrix();
            vertexDeclarationInstancing = new VertexDeclaration(inGraphics.GraphicsDevice, elements);
 
            // Назначаем эфект
            effect = inEffect;
 
            // Строим треугольник и создаем вершинный и индексный буфер
            vertex = new VertexPositionColor[3];
            index = new int[3];
 
            vertex[0] = new VertexPositionColor(new Vector3(0f, 1f, 0f), Color.Yellow);
            vertex[1] = new VertexPositionColor(new Vector3(0.5f, 0f, 0f), Color.Green);
            vertex[2] = new VertexPositionColor(new Vector3(-0.5f, 0f, 0f), Color.Red);
 
            index[0] = 0;
            index[1] = 1;
            index[2] = 2;
 
            vertexBuffer = new VertexBuffer(
                                inGraphics.GraphicsDevice,
                                VertexPositionColor.SizeInBytes * vertex.Length,
                                BufferUsage.None);
            vertexBuffer.SetData<VertexPositionColor>(vertex);
 
            indexBuffer = new IndexBuffer(
                                inGraphics.GraphicsDevice,
                                sizeof(int) * index.Length,
                                BufferUsage.None, IndexElementSize.ThirtyTwoBits);
            indexBuffer.SetData<int>(index);
 
            // Инициируем массив матриц и его размер в байтах
            matrix = new Matrix[10000];
            instanceDataSize = sizeofMatrix * matrix.Length;
            Vector3 pos = new Vector3(0f, 0f, 0f);
            int n = 0;
            Vector3 vx = new Vector3(1f, 0f, 0f);
            Vector3 vz = new Vector3(0f, 0f, -1f);
            for (int z = 0; z < 100; z++)
            {
                pos.X = 0f;
                for (int x = 0; x < 100; x++) 
                {
                    if (n < matrix.Length)
                    {
                        matrix[n] = Matrix.CreateTranslation(pos);
                        n++;
                        pos += vx;
                    }
                }
                pos += vz;
            }
            bReady = true;
        }
 
        /// <summary>
        /// Обновление матриц состояния
        /// </summary>
        /// <param name="gameTime">Время для синхронизации</param>
        public void Update(GameTime gameTime)
        {
            float ang = 0.005f;
            Matrix matrRot = Matrix.CreateRotationZ(ang * (float)gameTime.ElapsedGameTime.TotalMilliseconds);
            for (int i = 0; i < matrix.Length; i++)
            {
                matrix[i] = matrRot * matrix[i];
            }
        }
 
        /// <summary>
        /// Вывод
        /// </summary>
        /// <param name="inGraphics">GraphicsDeviceManager</param>
        public void Draw(GraphicsDeviceManager inGraphics, Matrix matrixViewProjection)
        {
            // настримваем вывод
            inGraphics.GraphicsDevice.VertexDeclaration             = vertexDeclarationInstancing;
            inGraphics.GraphicsDevice.Indices                       = indexBuffer;
            inGraphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
 
            // инициируем динамический вершинный буфер - поток с данными о матрицах
            DynamicVertexBuffer instanceDataStream = new DynamicVertexBuffer(
                inGraphics.GraphicsDevice, 
                instanceDataSize, 
                BufferUsage.WriteOnly);
            instanceDataStream.SetData(matrix, 0, matrix.Length, SetDataOptions.Discard);
 
            // назначаем вершинные буферы нашего GraphicsDevice
            VertexStreamCollection vertices = inGraphics.GraphicsDevice.Vertices;
            vertices[0].SetSource                   (vertexBuffer, 0, VertexPositionColor.SizeInBytes);
            vertices[0].SetFrequencyOfIndexData     (matrix.Length);
            vertices[1].SetSource                   (instanceDataStream, 0, sizeofMatrix);
            vertices[1].SetFrequencyOfInstanceData  (1);
 
            // настраиваем effect
            effect.Parameters["matVP"].SetValue(matrixViewProjection);
            effect.CurrentTechnique = effect.Techniques["Instancing"];
 
            // стандартный вывод
            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                inGraphics.GraphicsDevice.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList,
                    0, 0,
                    vertex.Length,
                    0,
                    index.Length / 3);
                pass.End();
            }
            effect.End();
 
            // освобождаем поток с данными о матрицах
            instanceDataStream.Dispose();
            // очищаем вершинные буферы нашего GraphicsDevice
            vertices[0].SetSource(null, 0, 0);
            vertices[1].SetSource(null, 0, 0);
        }
 
        /// <summary>
        /// Получение описания вершин формата VertexPositionColorMatrix
        /// </summary>
        /// <returns>список описания вершин</returns>
        private VertexElement[] GetVertexElementVertexPositionColorMatrix()
        {
            // инициируем новый список описания вершин
            // 0 - Position         0-  = 2 элемента
            // 1 - Color            1/
            // 2 - Matrix line0     0\
            // 3 - Matrix line1     1 \ = 4 элемента
            // 4 - Matrix line2     2 /
            // 5 - Matrix line3     3/
            VertexElement[] elements = new VertexElement[2 + 4];
 
            // используем промежуточный список описания вершин для 
            // описания данных под матрицы
            VertexElement[] elementsMatrix = new VertexElement[4];
            short stream = 1;       // индекс потока
            short offset = 0;       // смешение
            byte usageIndex = 0;    // индекс начальной семантики в шейдере (: TEXCOORD0;) в данном случае 
            // описываем данные о 4х строках матрицы (4строки из Vector4)
            for (int i = 0; i < 4; i++)
            {
                elementsMatrix[i] = new VertexElement(stream, offset, VertexElementFormat.Vector4
		VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, usageIndex);
                usageIndex++;
                offset += (short)Marshal.SizeOf(new Vector4());
            }
            // копируем в новый список описания вершин данные описания вершин треугольника
            VertexPositionColor.VertexElements.CopyTo(elements, 0);
            // копируем в новый список описания вершин данные о описания матрицы к треугольнику
            elementsMatrix.CopyTo(elements, 2);
            // возвращаем результат
            return elements;
        }
    }
 
    /*
    // для информации
    public struct VertexPositionColor
    {
 
        Vector3 Position;
        Color   Color;
 
        public static readonly VertexElement[] VertexElements;
 
        static VertexPositionColored()
        {
            VertexElements = new VertexElement[2];
            short stream    = 0;
            short offset    = 0;
            byte usageIndex = 0;
            VertexElements[0] = new VertexElement(0, offset, 
                                            VertexElementFormat.Vector3, 
                                            VertexElementMethod.Default, 
                                            VertexElementUsage.Position, 0);
            offset += (short)Marshal.SizeOf(new Vector3());
            VertexElements[1] = new VertexElement(0, offset, 
                                            VertexElementFormat.Color, 
                                            VertexElementMethod.Default, 
                                            VertexElementUsage.Color, 0);
            offset += (short)Marshal.SizeOf(new Color());
        }
 
        public VertexPositionColored(Vector3 inPosition, Color inColor)
        {
            this.Position   = inPosition;
            this.Color      = inColor;
        }
 
        public static int SizeInBytes 
        { 
            get 
            {
                int length = Marshal.SizeOf(new Vector3()) + Marshal.SizeOf(new Color());
                return length;
            } 
        }
    }
    */
}

 

Я постарался комментитовать все, что только можно закомментировать. Так же оставил в конце (как пример для понимания) закомментированную структуру VertexPositionColor. На что здесь можно обратить дополнительное внимание? Выделю несколько ключевых моментов:

- Инициализация vertexDeclarationInstancing и соответствующий ему метод GetVertexElementVertexPositionColorMatrix. Не хочу повторяться с пояснением кода, потому как достаточно подробно данный вопрос расписан в XNA SDK в разделе «How To: Create and Use a Custom Vertex».

- Инициализация массива матриц, каждая из которых соответствует своему треугольнику и является для него матрицей мира.

- Инициализация динамического вершинного буфера DynamicVertexBuffer и включение его в вершинный буфер графического девайса.

Вот так должен выглядеть (в данном случае – статический) результат работы данного примера:
articles: HardInstancing01.png

Также Вы можете скачать прилагаемый к статье пример приложения и покрутить его как Вашей душе угодно.

Тимофеев Дмитрий (general)

Комментарии
#1 | beaver 07.04.2009 08:12:43
Гуд.
Теперь ждем треугольники, танцующие вальс Grin
#2 | vanka 07.04.2009 17:28:25
Классно.
Только у меня пара вопросов:
С чем именно связано использование 2 и 3 шейдеров?
На сколько примерно увеличилось быстродействие?
#3 | general 07.04.2009 18:34:08
тогда у меня более чем два ответа Smile:

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

2) тестировал на домашнем компе: i7 проц (2.66 на ядро), gf280gtx.
на 100000 треугольников 79 fps во всех случаях:
хоть
VertexShader = compile vs_3_0 VS();
PixelShader = compile ps_3_0 PS();
хоть
VertexShader = compile vs_2_0 VS();
PixelShader = compile ps_2_0 PS();
хоть
VertexShader = compile vs_1_0 VS();
PixelShader = compile ps_1_0 PS();

может я что то упустил?

3) использование разных техник в примере, следствие предыдущих экспериментов

4) а вот когда отключил в классе Instancing метод Update!
и тем самым запретил пересчет матриц. то fps в этом случае подпрыгнули до 209!
#4 | Chort 07.04.2009 18:38:05
Хардварный инстансинг супортится только 3 шейдерами.
Быстродействие примерно в 2-3 раза или (N - 1) * (callTime + setTime), где N - количество объектов, callTime - время вызова отрисовки, setTime - время установки параметра для конкретного объекта
#5 | general 07.04.2009 18:43:55
про поддержку только 3ми вершинными шейдерами я знаю.
может компилер выставляет нужные версии техник самостоятельно. хотя вот данные из .xnb файла эффекта

...ps_1_1 Microsoft (R) D3DX9 Shader Compiler 9.15.779...
...vs_1_1 Microsoft (R) D3DX9 Shader Compiler 9.15.779...
#6 | general 07.04.2009 20:02:02
господа, призываю вас ставить оценки. данный фактор на прямую влияет на желание автора писать в дальнейшем новые статьи!
#7 | Bandit 08.04.2009 12:23:41
Мне бы хотелось бы услишать более подробные коментарии по методу Draw().
Показываю, как я это вижу:
GeSHi: C#
  1.  
  2. public void Draw(GraphicsDeviceManager inGraphics, Matrix matrixViewProjection)
  3. {
  4. // настримваем вывод
  5. inGraphics.GraphicsDevice.VertexDeclaration = vertexDeclarationInstancing;
  6. inGraphics.GraphicsDevice.Indices = indexBuffer;
  7. inGraphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
  8.  
  9. // инициируем динамический вершинный буфер - поток с данными о матрицах
  10. //Bandit: Каждый раз при вызове метода мы выделяем и ...
  11. DynamicVertexBuffer instanceDataStream = new DynamicVertexBuffer(
  12. inGraphics.GraphicsDevice,
  13. instanceDataSize,
  14. BufferUsage.WriteOnly);
  15. //Bandit: ... и забиваем видеопамять данными, при этом
  16. //перезаписываем память от начала до конца не
  17. //смотря на уже содержащиеся в памяти данные, а так
  18. //как память мы только, что выделили, то она не содержит ничего, что бы можно было бы перезаписать.
  19. //Смысл в ключе SetDataOptions.Discard теряется.
  20. //Только создадим новый пул. ...
  21. instanceDataStream.SetData(matrix, 0, matrix.Length, SetDataOptions.Discard);
  22.  
  23. // назначаем вершинные буферы нашего
  24. //Bandit:Каждый раз плодим лишнюю ссылку
  25. GraphicsDevice
  26. VertexStreamCollection vertices = inGraphics.GraphicsDevice.Vertices;
  27. vertices[0].SetSource (vertexBuffer, 0, VertexPositionColor.SizeInBytes);
  28. vertices[0].SetFrequencyOfIndexData (matrix.Length);
  29. vertices[1].SetSource (instanceDataStream, 0, sizeofMatrix);
  30. vertices[1].SetFrequencyOfInstanceData (1);
  31.  
  32. // настраиваем effect
  33. effect.Parameters["matVP"].SetValue(matrixViewProjection);
  34. effect.CurrentTechnique = effect.Techniques["Instancing"];
  35.  
  36. // стандартный вывод
  37. effect.Begin();
  38. foreach (EffectPass pass in effect.CurrentTechnique.Passes)
  39. {
  40. pass.Begin();
  41. inGraphics.GraphicsDevice.DrawIndexedPrimitives(
  42. PrimitiveType.TriangleList,
  43. 0, 0,
  44. vertex.Length,
  45. 0,
  46. index.Length / 3);
  47. pass.End();
  48. }
  49. effect.End();
  50.  
  51. // освобождаем поток с данными о матрицах
  52. //Bandit: ... Говорим мусорщику, что он может очистить вершинный буфер.
  53. // Получается, что каждый раз во время отрисовки мы используем новый участок видео памяти?
  54. instanceDataStream.Dispose();
  55. // очищаем вершинные буферы нашего GraphicsDevice
  56. vertices[0].SetSource(null, 0, 0);
  57. vertices[1].SetSource(null, 0, 0);
  58. }
  59.  
  60.  
Добавлено за 0.014 секунд, используя GeSHi 1.0.8.2


зы. Ну и на встроенной ведяхе кроме черного экрана я ничего неувидел ...
#8 | general 08.04.2009 23:05:41
Bandit я в тебе и не сомнивался :) Ты все правильно сказал.

Более того, я рекомендую начинающим товарищам, у которых запустился мой пример и они разобрались, почитать твои комментарии. Которые относятся к области оптимизации. И правильного написания кода с точки зрения CLR в целом и сборщика мусора в частности! Ребята действительно обратите внимание!

В примере я старался не показать какой я умный и как могу замудрить код, а как наиболее просто прийти к использованию данной техники (сам рассмотрел много примеров и не всеми доволен). И строил пример при помощи следующих принципов:
- Соблюсти стерильные условия. Т.е принцип выделил память / почистил за собой. Это помогает избегать лишних оговорок, пояснений и углубления в сторону оптимизации (не профильное в данном случае направление).
- По возможности не выносить за пределы Draw объявления переменных. Тем самым сосредотачивая внимание читателя в области одного метода.
- Выделить применение базовых классов. На примере
GeSHi: C#
  1. VertexStreamCollection vertices = inGraphics.GraphicsDevice.Vertices;
Добавлено за 0.004 секунд, используя GeSHi 1.0.8.2

Можно было и не делать вовсе, а работать на прямую с .GraphicsDevice.Vertices[n]. Но я выделил смысл. Всегда в таких случаях вспоминается Казьма Прутков со своим «Зри в корень» (исключительно емкая фраза!). Так удобнее и нагляднее новичку разбираться с новым для себя материалом и сформировать базовые знания (второе хобби психология).

Про видео карты.
Я не считаю себя спецом по встроенным видюхам. Но могу сказать, что если в документации написано, что поддерживаются 3-тьи шейдеры, то это не значит, что поддерживаются полностью со всеми наворотами. Более того. Некоторые фишки могут просто игнорироваться. На сколько я осведомлен, 3D разработчики всего мира в первую очередь ориентируются на NVidia. Так же работают и изначально тестят именно на картах этой фирмы. Т.к. NVidia самая честная в плане реализации заявленных параметров и поддержке стандартов. Ну а потом уже разными способами добиваются работоспособности на Radeon. Про встроенные видюхи молчу. Там вообще все по разному.

Мой пример на различных NVidia работает. На других картах возможности проверить нет.

На само деле я ждал, что будут в первую очередь другие вопросы!
1. Почему перед применением в щейдере матрица транспонируется?
GeSHi: HLSL
  1. output.Position = mul(input.Position, mul(transpose(input.InstanceWorld), matVP))
Добавлено за 0.006 секунд, используя GeSHi 1.0.8.2

2. Что нужно сделать, и как перестроить код приложения и шейдера, если я хочу чтобы во входных данных вершинного шейдера семантика TEXCOORD0 соответствовала реальным текстурным координатам?
Просто тут я сам споткнулся в начале.
#9 | Jam 09.04.2009 12:38:58
Действительно вопросы интересныеSmile
Зачем транспонируется матрица?
Что нужно сделать, для того, чтобы TEXCOORD0 соответствовала текстурным координатам?
Что делать если текстурных координат будет много(например еще одни для лайтмапов или для текстур детализации)?
А что делать, если помимо текстурных координат в шейдер передается информация о нормалях, например?
Насколько я понимаю, нужно просто изменить usageIndex с учетом размера данных, которые описаны в VertexDeclaration, правильно?
#10 | general 09.04.2009 21:10:43
на выходных, если на работу не выгонят как на прошлых, напишу. сложного там ни чего нет.
Добавить комментарий
Пожалуйста, залогиньтесь для добавления комментария.
Рейтинги
Рейтинг доступен только для пользователей.

Пожалуйста, залогиньтесь или зарегистрируйтесь для голосования.

Отлично! Отлично! 88% [7 Голоса]
Очень хорошо Очень хорошо 13% [1 Голос]
Хорошо Хорошо 0% [Нет голосов]
Удовлетворительно Удовлетворительно 0% [Нет голосов]
Плохо Плохо 0% [Нет голосов]
Авторизация
Логин

Пароль



Вы не зарегистрированы?
Нажмите здесь для регистрации.

Забыли пароль?
Запросите новый здесь.
Мини-чат
Вы должны авторизироваться, чтобы добавить сообщение.

27.08.2014
Я умею немного на asp.net + html и css

22.08.2014
на ASP mvc 3 есть пару проектов. Могу помочь, если нужно. Обидно, если закроется Frown

21.08.2014
я тоже ноль

21.08.2014
Я в вебе только с php занимался да и то на уровне чтоб работало.

21.08.2014
Я в вебе полный ноль…

21.08.2014
Переводить его надо, хоть на ту же азуру. И двиг менять на что-то современное. Если есть веб-разрабы - можем скооперироваться. Один делать не буду.

21.08.2014
не знаю всех нюансов по оплате и все хорошее когда нибудь заканчивается

21.08.2014
А что случилось?

21.08.2014
похоже сайт будет работать до 28го числа

09.08.2014
Апи пока не видел. Но есть приложение в магазине Live Lock Screen BETA, так что думаю скоро будет

08.08.2014
Я про API для Update1. На нем работает это

08.08.2014
А что именно нужно? Чтото и сейчас открыто http://msdn.micro.
...105).aspx

06.08.2014
Кто-нибудь слышал об открытии доступа к Lock Screen Api?

31.07.2014
VPDExpress на базе MVS 2012, ни в какую не ловит исключения. Даже если их сам создаешь. И всех так?

25.07.2014
С днем системного администратора причастных к этой профессии! По случаю - тортик от жены

RSS каналы сайта
XNA - Новости
XNA - Статьи
XNA - Форум
XNA - Галерея
XNA - Файлы
Время загрузки: 0,40 секунд 8,709,497 уникальных посетителей