Форум программистов, компьютерный форум, киберфорум
Storm23
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  

Задумал я сделать игрушку. День 3.

Запись от Storm23 размещена 28.03.2016 в 02:57
Показов 4525 Комментарии 2

День 3. Космос.

Предыдущие дни здесь.

Итак, вдохновившись PowderToy, захотелось мне сделать что-то больше, красивое и эпичное. И конечно оно должно быть в 3d. Двумерные игры мне не нравятся. А тут еще на хабре прочел заметку про то как парень писал космосим. И выбор был сделан. Пишем космический шутер.

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

Итак, только хардкор, только олд-скул. Никаких DirectX, никаких шейдеров, никаких Unity и XNA.

В общем, далее будет типичный crazy programming and wheel invention. В лучших традициях велосипедостроения будем делать нечто вроде 3d движка, но под GDI+, без шейдеров, без использования возможностей видеокарты, на чистом C#, без сторонних библиотек. И посмотрим, что получится.

А да, и еще текстуры, готовые модели и звуки - мы тоже использовать не будем. Кстати посмотрим еще и на размер программы в результате.

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

Начнем с физики. Я не буду подробно останавливаться на физическом движке, который я реализовал. Скажу лишь что он почти такой же как в PowderToy, только трехмерный. И еще, столкновения частиц - основная фишка PowderToy - мне не пригодилась. Вот бывает и так :[]

Физика реализована в классе Sandbox, который содержит список частиц, класс Particle. Они довольно скучны внутри, я не буду пока подробно останавливаться. Скажу лишь, что я применил интегрирование Верле, о котором можно более подробно почитать здесь. Это немного повышает быстродействие и дает возможность обрабатывать столкновения.

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

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

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

Почему я использую частицы, а не полигоны, как в стандартных движках? Ну потому что под полигоны заточены видеокарты и они отрисовывают их быстро. Но я их не использую. А моделировать и рисовать частицы - проще и быстрее. Кроме того, меньше мороки при просчете z-буфера, заслонения объектов друг-другом и так далее. И да, мы же исходим из PowderToy, а там был песочек.

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

Конечно у партиклов есть и недостатки. Их полно. В первую очередь - малая информативность, отсутствие детализации объектов. Но, надеюсь, мы с этим поборемся.

И еще, выбор партикловой модели довольно хорошо ложится на тематику космоса. Здесь нет теней (по крайней мере, у звезд - так точно). И малые объекты находятся на таком большом расстоянии, что они и должны выглядеть как точка.

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

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

Особенно в определенные сложные игровые моменты, когда появляется много частиц - физика и рендер могут лагнуть. Но GUI, мышка и экран будут по прежнему работать очень быстро. Игрок просто заметит, что частицы на экране будут двигаться медленнее, но дергаться кадры не будут.

Теперь я немного остановлюсь на математической модели игры в плане графики. Я не буду использовать стандартные методы, основанные на матрицах. Почему? Ну вот так захотелось. Crazy development потому что.

Ну на самом деле конечно не просто так я не использую матрицы. Почему матрицы нам почти не пригодятся - станет ясно чуть позже.

Итак. У нас есть трехмерный мир частиц-точек. А нам нужно нарисовать их на двумерном экране. В обычной ситуации я бы просто сделал проективную матрицу и умножил на нее все координаты точек. Но вместо этого, я буду переводить точки в сферическую систему координат, с центром в точке, где находится игрок.

Ниже приведен рисунок с расположением осей:

Нажмите на изображение для увеличения
Название: 0_16.png
Просмотров: 971
Размер:	225.8 Кб
ID:	3705

(прошу прощения за качество рисунков, но мне так быстрее и проще - рисовать их от руки)

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

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

Если бы мы делали честную проекцию сферы на плоскость экрана, нам бы потребовался синус зенитного угла. Но поскольку мы смотрим только на экватор, то вместо синуса можно взять просто сам зенитный угол, пользуясь тем, что Sin(x)~x при малых x (замечу, что я изначально считаю не зенитный угол, а PI/2 минус зенитный угол, то есть отсчитываю его от горизонта, так удобнее).

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

Рисунок ниже показывает идею:

Нажмите на изображение для увеличения
Название: 0_17.png
Просмотров: 1145
Размер:	340.8 Кб
ID:	3706

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

При этом, игроку показывается не весь битмап, а лишь небольшая его часть размером 600x400. Это и есть та область, куда смотрит игрок и которая отображается на экране. Окно просмотра может передвигаться по битмапу, в зависимости от движения мышкой. Таким образом игрок просматривает пространство вокруг себя и немного выше и немного ниже себя.

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

Тут еще нужно остановиться немного на игровой механике, которую я буду использовать. Обычно в космических играх корабль может двигаться и вращаться в любых направлениях. Я так делать не буду. Мой игрок сможет передвигаться только в двумерной плоскости. Смотреть и стрелять он может в любом направлении, и вверх и вниз. Но двигаться - только вперед/назад, вправо/влево. Это дает несколько преимуществ. Первое - это проще. Проще делать карту, проще делать остальную механику, проще делать дизайн уровней. Второе - управление кораблем будет намного проще и привычнее, чем управление кораблем в полноценном 3D. Фактически передвижение - стандартное для обычных шутеров: клавиши WS - вперед/назад и AD - стрейф влево/вправо. Ну а кроме того, у меня от постоянного кручения начинает болеть голова :D

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

Теперь переходим к самому интересному - к картинкам. После всех манипуляций, описанный выше. Я смог получить примерно следующее:

Нажмите на изображение для увеличения
Название: 0_18.png
Просмотров: 792
Размер:	45.7 Кб
ID:	3707

Неплохо, но не впечатляет. Совсем.

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

В результате получаем следующее:

Нажмите на изображение для увеличения
Название: 0_6.png
Просмотров: 882
Размер:	313.8 Кб
ID:	3702

Намного лучше, да?

Размытие дает нам следующее. Первое - виден след от движения частиц, создается иллюзия движения с хвостом, типа комет. Второе - создается гало вокруг крупных объектов типа звезд. Третье - собственно размытие позволяет видеть само тело звезд, а не видеть только отдельные партиклы.

Еще несколько картинок:

Нажмите на изображение для увеличения
Название: 0_7.png
Просмотров: 1011
Размер:	387.9 Кб
ID:	3703 Нажмите на изображение для увеличения
Название: 0_14.jpg
Просмотров: 856
Размер:	47.2 Кб
ID:	3709 Нажмите на изображение для увеличения
Название: 0_15.jpg
Просмотров: 837
Размер:	54.2 Кб
ID:	3710 Нажмите на изображение для увеличения
Название: 0_12.jpg
Просмотров: 1174
Размер:	43.4 Кб
ID:	3711

Итак, рисуется красиво. Эпичненько. Симпатично. Это плюс.

Но не все так гладко. Самое плохое в таком подходе то, что битмап никогда не очищается. Пока игрок находится на месте, а только крутит головой - все хорошо. Но когда он начинает сам быстро двигаться мимо других объектов, их местоположение на битмапе сдвигается. А поскольку битмап не очищается, то на их старом месте еще довольно долго видно характерное пятно.

Нажмите на изображение для увеличения
Название: 0_19.png
Просмотров: 900
Размер:	242.6 Кб
ID:	3708

Это неприятно. И мы будем с этим бороться. Во-первых звезды у нас будут далеко от игрока. Если тело далеко - эффект не заметен. А близко подлетать к звездам игроку не дадим. Да это и в реальности невозможно сделать - там огромная температура. С мелкими и близкими объектами - сложнее. Их относительное движение всегда велико. С кораблями противника я более менее знаю что делать. С планетами - пока нет. Будем думать.

Теперь о хорошем. О производительности. Основной лаг системы - в операции размытия битмапа. Ведь он имеет довольно большой размер 3600x800. А это почти 3 млн пикселов. Причем, не забываем, что изображение имеет 4 цветовых канала, то есть получается массив размером 11 мегабайт. Поэтому стояла первоочередная задача как можно быстрее его размывать и желательно с хорошим качеством.

Сначала я приведу код, который довольно полезен, хотя я его и не применяю в данном случае.
Вот здесь описан быстрый метод гауссовского размытия изображений, но на неизвестном мне языке. А здесь девушка пыталась перевести его на C#. Но ее код был местами неоптимален и с ошибками. Я исправил ошибки и оптимизировал его. Выкладываю результат, может кому пригодится:

GaussianBlur
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
    public static class GaussianBlur
    {
        /// <summary>
        /// Gaussian blur.
        /// Complexity O(n) (where n = amount of pixels * channels)
        /// w - width of image (is not multiplied on bpp)
        /// h - height of image
        /// sigma - sigma of Gaussian
        /// bpp - bytes per pixel (i.e. bpp=4 for ARGB)
        /// </summary>
        public static void GaussBlur_4(byte[] originalImage, byte[] blurImage, int w, int h, double sigma, int bpp = 1) 
        {
            var bxs = GetBoxesForGauss(sigma, 3);
            BoxBlur_4(originalImage, blurImage, w, h, (int)((bxs[0] - 1) / 2f), bpp);
            BoxBlur_4(originalImage, blurImage, w, h, (int)((bxs[1] - 1) / 2f), bpp);
            BoxBlur_4(originalImage, blurImage, w, h, (int)((bxs[2] - 1) / 2f), bpp);
        }
 
        /// <summary>
        /// Box blur.
        /// Complexity O(n) (where n = amount of pixels * channels)
        /// w - width of image (is not multiplied on bpp)
        /// h - height of image
        /// r - radius of blur
        /// bpp - bytes per pixel (i.e. bpp=4 for ARGB)
        /// </summary>
        public static void BoxBlur_4(byte[] originalImage, byte[] blurImage, int w, int h, int r, int bpp = 1)
        {
            Array.Copy(originalImage, blurImage, originalImage.Length);
            BoxBlurH_4(blurImage, originalImage, w, h, r, bpp);
            BoxBlurT_4(originalImage, blurImage, w, h, r, bpp);
        }
 
        //Returns array of box-radiuses for Gauss convolution
        static double[] GetBoxesForGauss(double sigma, int n)  // standard deviation, number of boxes
        {
            var wIdeal = Math.Sqrt((12 * sigma * sigma / n) + 1);  // Ideal averaging filter width 
            var wl = (int)Math.Floor(wIdeal);
            if (wl % 2 == 0) wl--;
            var wu = wl + 2;
 
            var mIdeal = (12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4);
            var m = Math.Round(mIdeal);
            // var sigmaActual = Math.sqrt( (m*wl*wl + (n-m)*wu*wu - n)/12 );
 
            var sizes = new double[n];
            for (var i = 0; i < n; i++)
                sizes[i] = i < m ? wl : wu;
            return sizes;
        }
 
        //Horizontal convolution with accumulator
        static void BoxBlurH_4(byte[] originalImage, byte[] blurImage, int w, int h, int r, int bpp)
        {
            int to = w - r;
 
            double iarr = 1d / (r + r + 1);
            for (int iBpp = 0; iBpp < bpp; iBpp++ )
            for (int i = 0; i < h; i++)
            {
                int ti = i * w * bpp + iBpp, li = ti, ri = ti + r * bpp;
                byte fv = originalImage[ti], lv = originalImage[ti + w - bpp];
                double val = (r + 1) * fv;
 
                for (int j = 0; j < r; j++) val += originalImage[ti + j*bpp];
                for (int j = 0; j <= r; j++)
                {
                    val += originalImage[ri] - fv;
                    blurImage[ti] = (byte)Math.Round(val * iarr);
                    ri += bpp;
                    ti += bpp;
                }
 
                for (int j = r + 1; j < to; j++)
                {
                    val += originalImage[ri] - originalImage[li];
                    blurImage[ti] = (byte)Math.Round(val * iarr);
                    ri += bpp;
                    li += bpp;
                    ti += bpp;
                }
 
                for (int j = w - r; j < w; j++)
                {
                    val += lv - originalImage[li];
                    blurImage[ti] = (byte)Math.Round(val * iarr);
                    li += bpp;
                    ti += bpp;
                }
            }
        }
 
        //Vertical(or total) convolution with accumulator
        static void BoxBlurT_4(byte[] originalImage, byte[] blurImage, int w, int h, int r, int bpp)
        {
            w = w * bpp;
 
            double iarr = 1d / (r + r + 1);
            for (int i = 0; i < w; i++)
            {
                int ti = i, li = ti, ri = ti + r * w;
                byte fv = originalImage[ti], lv = originalImage[ti + w * (h - 1)];
                double val = (r + 1) * fv;
 
                for (int j = 0; j < r; j++) val += originalImage[ti + j * w];
                for (int j = 0; j <= r; j++)
                {
                    val += originalImage[ri] - fv;
                    blurImage[ti] = (byte)Math.Round(val * iarr); 
                    ri += w; ti += w;
                }
 
                int to = h - r;
 
                for (int j = r + 1; j < to; j++)
                {
                    val += originalImage[ri] - originalImage[li];
                    blurImage[ti] = (byte)Math.Round(val * iarr); 
                    li += w; ri += w; ti += w;
                }
 
                for (int j = h - r; j < h; j++)
                {
                    val += lv - originalImage[li]; 
                    blurImage[ti] = (byte)Math.Round(val * iarr); 
                    li += w; ti += w;
                }
            }
        }
    }

GaussianBlurUnsafe
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
    public static class GaussianBlurUnsafe
    {
        /// <summary>
        /// Gaussian blur.
        /// Complexity O(n) (where n = amount of pixels * channels)
        /// w - width of image (is not multiplied on bpp)
        /// h - height of image
        /// sigma - sigma of Gaussian
        /// bpp - bytes per pixel (i.e. bpp=4 for ARGB)
        /// </summary>
        public static void GaussBlur_4(byte[] originalImage, byte[] blurImage, int w, int h, double sigma, int bpp = 1) 
        {
            var bxs = GetBoxesForGauss(sigma, 3);
            BoxBlur_4(originalImage, blurImage, w, h, (int)((bxs[0] - 1) / 2f), bpp);
            BoxBlur_4(originalImage, blurImage, w, h, (int)((bxs[1] - 1) / 2f), bpp);
            BoxBlur_4(originalImage, blurImage, w, h, (int)((bxs[2] - 1) / 2f), bpp);
        }
 
        /// <summary>
        /// Box blur.
        /// Complexity O(n) (where n = amount of pixels * channels)
        /// w - width of image (is not multiplied on bpp)
        /// h - height of image
        /// r - radius of blur
        /// bpp - bytes per pixel (i.e. bpp=4 for ARGB)
        /// </summary>
        public static void BoxBlur_4(byte[] originalImage, byte[] blurImage, int w, int h, int r, int bpp = 1)
        {
            Array.Copy(originalImage, blurImage, originalImage.Length);
            BoxBlurH_4(blurImage, originalImage, w, h, r, bpp);
            BoxBlurT_4(originalImage, blurImage, w, h, r, bpp);
        }
 
        //Returns array of box-radiuses for Gauss convolution
        static double[] GetBoxesForGauss(double sigma, int n)  // standard deviation, number of boxes
        {
            var wIdeal = Math.Sqrt((12 * sigma * sigma / n) + 1);  // Ideal averaging filter width 
            var wl = (int)Math.Floor(wIdeal);
            if (wl % 2 == 0) wl--;
            var wu = wl + 2;
 
            var mIdeal = (12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4);
            var m = Math.Round(mIdeal);
            // var sigmaActual = Math.sqrt( (m*wl*wl + (n-m)*wu*wu - n)/12 );
 
            var sizes = new double[n];
            for (var i = 0; i < n; i++)
                sizes[i] = i < m ? wl : wu;
            return sizes;
        }
 
        //Horizontal convolution with accumulator
        static unsafe void BoxBlurH_4(byte[] originalImage, byte[] blurImage, int w, int h, int r, int bpp)
        {
            int to = w - r;
            double iarr = 1d / (r + r + 1);
 
            fixed (byte* originalPtr = originalImage)
            fixed (byte* blurPtr = blurImage)
            for (int iBpp = 0; iBpp < bpp; iBpp++ )
            for (int i = 0; i < h; i++)
            {
                byte* ti = originalPtr + i * w * bpp + iBpp, li = ti, ri = ti + r * bpp;
                byte* blurTi = blurPtr + i * w * bpp + iBpp;
                byte fv = *ti, lv = *(ti + w * bpp - bpp);
                double val = (r + 1) * fv;
 
                for (int j = 0; j < r; j++) val += *(ti + j*bpp);
                for (int j = 0; j <= r; j++)
                {
                    val += *ri - fv;
                    *blurTi = (byte)Math.Round(val * iarr);
                    ri += bpp;
                    blurTi += bpp;
                }
 
                for (int j = r + 1; j < to; j++)
                {
                    val += *ri - *li;
                    *blurTi = (byte)Math.Round(val * iarr);
                    ri += bpp;
                    li += bpp;
                    blurTi += bpp;
                }
 
                for (int j = w - r; j < w; j++)
                {
                    val += lv - *li;
                    *blurTi = (byte)Math.Round(val * iarr);
                    li += bpp;
                    blurTi += bpp;
                }
            }
        }
 
        //Vertical(or total) convolution with accumulator
        static unsafe void BoxBlurT_4(byte[] originalImage, byte[] blurImage, int w, int h, int r, int bpp)
        {
            w = w * bpp;
            int to = h - r;
            double iarr = 1d / (r + r + 1);
 
            fixed (byte* originalPtr = originalImage)
            fixed (byte* blurPtr = blurImage)
            for (int i = 0; i < w; i++)
            {
                byte* ti = originalPtr + i, li = ti, ri = ti + r * w;
                byte* blurTi = blurPtr + i;
                byte fv = *ti, lv = *(ti + w * (h - 1));
                double val = (r + 1) * fv;
 
                for (int j = 0; j < r; j++) val += *(ti + j * w);
                for (int j = 0; j <= r; j++)
                {
                    val += *ri - fv;
                    *blurTi = (byte)Math.Round(val * iarr);
                    ri += w; 
                    blurTi += w;
                }
 
                for (int j = r + 1; j < to; j++)
                {
                    val += *ri - *li;
                    *blurTi = (byte)Math.Round(val * iarr);
                    li += w; 
                    ri += w; 
                    blurTi += w;
                }
 
                for (int j = h - r; j < h; j++)
                {
                    val += lv - *li;
                    *blurTi = (byte)Math.Round(val * iarr);
                    li += w; 
                    blurTi += w;
                }
            }
        }
    }


Приведены два статических класса, один - обычный, другой - unsafe. Небезопасный дает небольшой прирост производительности (процентов на 10-20).

Ниже приведены примеры размытия:

Нажмите на изображение для увеличения
Название: 0_8.jpg
Просмотров: 1289
Размер:	7.0 Кб
ID:	3704

Первый - исходный квадратик. Второй - размытый box-блюром, и третий - гауссовским блюром. Оба - с радиусом размытия 4. Для этих методов у меня получилось добиться производительности 9-10 fps для гаусса, и около 16 fps - для box. Это для размера изображения 600x400. Но у меня изображение гораздо больше. И производительности этих методов - не хватило.

Я написал собственный метод размытия, и максимально оптимизировал его. Выше на картинке он справа. Код:

Blur
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
        unsafe void Blur(byte[] array, byte[] output)
        {
            const int stride = Sandbox.WIDTH * BPP;
 
            fixed (byte* pInStart = @array)
            fixed (byte* pOutStart = @output)
            {
                //очищаем верхнюю и нижнюю границы (цвет  - Teal)
                var p1 = pOutStart;
                var p2 = pOutStart + stride * (Sandbox.HEIGHT - 1);
 
                for (int x = stride / BPP; x > 0; x--)
                {
                    *p1 = 24; p1++;
                    *p1 = 24; p1++;
                    *p1 = 0; p1++;
                    *p1 = 255; p1++;
 
                    *p2 = 24; p2++;
                    *p2 = 24; p2++;
                    *p2 = 0; p2++;
                    *p2 = 255; p2++;
                }
 
                var finish = pInStart + array.Length - stride - BPP;
                var pIn = pInStart + stride;
                var pOut = pOutStart + stride + BPP;
 
                //размываем
                while (pIn < finish)
                {
                    *pOut = (byte)((*pIn + *(pIn - stride + BPP) + *(pIn + 2 * BPP) + *(pIn + stride + BPP)) >> 2);
                    pOut++; pIn++;
 
                    *pOut = (byte)((*pIn + *(pIn - stride + BPP) + *(pIn + 2 * BPP) + *(pIn + stride + BPP)) >> 2);
                    pOut++; pIn++;
 
                    *pOut = (byte)((*pIn + *(pIn - stride + BPP) + *(pIn + 2 * BPP) + *(pIn + stride + BPP)) >> 2);
                    pOut++; pIn++;
 
                    pOut++; pIn++;
                }
            }
        }


Поскольку в моем методе размытие происходит только на один пиксел за раз, то для получения результата, аналогичного картинкам выше, нужно вызвать метод несколько раз. Для картинки выше я пробежался по по пикселам 6 раз. Но даже при этом быстродействие моего метода составило 42 fps, против 16 fps для box.

Правда, если нужно размывать изображения с большим радиусом размытия, то box и гаусс могут оказаться быстрее. Но мне это не требовалось. Мне вполне достаточно размывать картинку на 1 пиксел на каждом кадре, но зато это должно делаться очень быстро.

В результате, размытие буферного битмапа 3600x800 моим методом делается примерно на скорости 45 fps.

На самом деле я бы хотел 65 fps, но выше поднять не удалось. Остановился на этом.

Кстати, для размытия можно было применять методы из WPF. Но мы же договорились - никакого DirectX :D

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

Весь проект целиком:

Исходник
Бинарник

Сразу прошу прощения за качество кода. Код местами грязный. Он находится в сыром виде, не вылизан, и является по сути просто полигоном для отработки алгоритмов.

Для максимального быстродействия нужно компилировать в Release, Ctrl-F5. Это важно, потому что в Debug производительность упадет раза в два.

Управление - WASD. Одновременное нажатие с shift - включает турбо режим, и можно долететь до звезды :) (пока только для тестов, потом будет отключено).

По итогам: моделирование физики идет с производительностью 48 fps, рендеринг на битмап около 40 fps, отрисовка 65 fps.

День получился насыщенный. И все что нужно я не успел. Поэтому всякие разности переносятся на День Четвертый.

Продолжение следует...
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 2
Комментарии
  1. Старый комментарий
    Аватар для Evg
    На форуме ты далеко не первый, кто пытается сделать игру. Но все, кто пошли твоим путём развития, в итоге сливались. Суть сводится к следующему. Человек очень глубоко закапывается в графику, в красивости отрисовки, вылизывание мельчайших деталей. А когда дело доходит до создания игрового процесса, то всё обламывается, потому что наконец приходит сознание, что красивая графика - это всего лишь малая часть игры, а построение непосредственно игрового процесса - это намного более сложное занятие
    Запись от Evg размещена 28.03.2016 в 09:38 Evg вне форума
  2. Старый комментарий
    Аватар для Storm23
    Цитата Сообщение от Evg
    На форуме ты далеко не первый, кто пытается сделать игру. Но все, кто пошли твоим путём развития, в итоге сливались. Суть сводится к следующему. Человек очень глубоко закапывается в графику, в красивости отрисовки, вылизывание мельчайших деталей. А когда дело доходит до создания игрового процесса, то всё обламывается
    А я и не собираюсь делать законченную игру. Мне интересен сам процесс и отработка алгоритмов. Потом я все это использую в других разработках.
    Запись от Storm23 размещена 28.03.2016 в 11:58 Storm23 вне форума
 
Новые блоги и статьи
Лето уходит
kumehtar 17.08.2026
Мысли в слух
kumehtar 17.08.2026
Забавно, насколько сейчас стала доступна информация. Например о магии, духовном развитии, медитациях, и других подобных направлениях, ранее зачастую тайных, передаваемых от учителя к ученику. Хотя. . .
Перемещение строк из ТЧ в другой документ с учетом текущего пробега
Maks 17.08.2026
Реализация из решения ниже выполнена на примере нетипового документа "Автозапчасти", с ТЧ "Шины". За основу взят алгоритм отсюда: https:/ / www. cyberforum. ru/ blogs/ 359708/ 10838. html Задача: . . .
Саморегулирующийся социальный контракт для сервера cross-section.
Hrethgir 14.08.2026
С кодом конечно таких глубоких размышлений пока не было, впрочем я уже привык к алгоритмизации. Суть предмета записи: снова в диалоге с нейросетью (я взял пока себе ник для учётки админа - Rector). . . .
Часы электронные
Uhbif79 12.08.2026
Выкладываю программу часов. Программа позволяет: 1. Использовать системное время и дату, 2. Есть возможность вводить время и дату вручную. 3. Реализованы 2 будильника: начало и конец рабочего дня. . . .
Часы с будильником на основе класса QLCDNumber
Uhbif79 12.08.2026
Всем добрый день, выкладываю программу часов с будильником на основе класса QLCDNumber. Здесь я пробовал самостоятельно создавал классы, впервые столкнулся с видимостью переменной одного класса из. . .
Установка MinGW GCC 16.2 и CMake
8Observer8 10.08.2026
VK Видео: https:/ / vkvideo. ru/ video-240781534_456239017 YouTube: eY5-5PyI9NM Текстовая версия
Неделя из жизни имитационной модели склада: мои кривые руки растут, откуда надо
anaschu 10.08.2026
Неделя из жизни имитационной модели склада: как я почти написал неправильную логику и что с этим делать Работаю сейчас над учебно-рабочим проектом: строю в AnyLogic имитационную модель процессов. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru