nsf gamedev

Журнал

27 сен 2014

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

Лежит тут: https://github.com/nsf/nextgame

Работа над движком продолжается. Недавно тыкал систему материалов в рендере ( http://imgur.com/a/Ui6OD ), тени ( http://imgur.com/a/vcI9E ), ну и всякое разное.

http://i.imgur.com/CJqMosa.jpg

http://i.imgur.com/SgjGtgb.jpg

Ссылка

29 апр 2014

Да, я знаю. Обещал статью написать про воксели. Честно признаюсь - даже начал, но пока еще не закончил. Оказалось, что мало знаю о вокселях. Хотел написать подробно. Меня в первую очередь интересует практическое применение вокселей в геймдеве. А для этого не достаточно просто взять их и сконвертировать в геометрию и подключить физику.

Изначально идея была взять воксели и их использовать в качестве материала для LOD механизма. Т.е. упрощать не отображаемую геометрию, а исходный материал. Думал это будет полезно и для сетевой игры - меньше передавать данных по кабелю. Но оказалось, что как только я упрощаю "воксельные поля", они становятся на 100% непригодными для работы с игровой логикой. Кроме этого, "продвинутые" алгоритмы визуализации вокселей усложняют работу с ними. Так например в популярных ныне двойственных алгоритмах, для того чтобы получить информацию об одном генерируемом кваде, нужно рассмотреть 3х3х2 вокселей (18), когда как в старом marching cubes достаточно лишь 8 вокселей. У всех алгоритмов есть плюсы и миунсы. Двойственные алгоритмы генерируют более качественный mesh, но он по умолчанию non-manifold, т.е. топологию качественной не назовешь, это решается дополнительными усилиями. MC хорош тем, что генерирует исключительно manifold геометрию, но у него имеются проблемы с вырожденными треугольниками. Все решаемо.

На данный момент решил вернутся к базовому marching cubes и добавить пост-процессинг для упрощения геометрии. Результаты пока впечатляют, скоро перейду непосредственно к работе с LOD механизмами и логикой редактирования. Привожу пару скриншотов работы алгоритма упрощения геометрии: http://imgur.com/a/LFEBr. Результат убедителен, уменьшая количество треугольников в 12.75 раз, визуальное качество практически остается не тронутым. Конечно, нужно понимать, что это карта с одним материалом, но с другой стороны приведенная геометрия достаточно капризная, реальный ландшафт будет чуток проще.

Ссылка

16 мар 2014

Многие наверянка уже не раз слышали это сравнение. Дескать, С++ это своего рода швейцарский нож среди языков программирования. Кто-то представляет такое сравнение в выгодном свете, подчеркивая этим факт, что в С++ есть все необходимое. Для других же, это признак излишества и композиционной бесвкусицы. Но задумывались ли вы о социальных эффектах сего сравнения? Приходит к вам программист, а вы его спрашиваете: "Умеешь гвозди забивать?" - и даете ему молоток. Крайне высока вероятность, что кандидат все-таки сообразит какой конец к чему нужно приложить. А если дать швейцарский ножик? Стало быть, отличный инструмент для вскрытия неадекватности получается.

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

Ссылка

1 янв 2014

Сразу предупреждаю, качество на ютубе так себе. Смотрите лучше по альтернативной ссылке в vimeo.

Запустить видео по клику - Как делать игрыЗапустить видео по клику - Как делать игры

Vimeo: https://vimeo.com/83150040

Что на видео? Если вкратце, то ничего продвинутого. Ландшафт представлен в виде воксельных данных, генерируется простой комбинацией 3ех самплов разного перлин нойса (один из самплов из 3д перлин нойса). Геометрия генерируется алгоритмом naive surface nets. Как видно в видео имеется поддержка разных уровней детализации. И базовая система чанков с возможностью распараллеливания работы на несколько потоков. Что касается рендера там совсем ничего интересного. Перлин нойс небо на шейдерах, освещение из фиксированного источника, считается только диффузная часть, нормал мап никаких нет пока что, текстуринг обычный трипланарный.

Видео записано на i5-3470, с использованием 3ех рабочих потоков. Видяшка вполне обычная gtx560.

За время работы накопилось много опыта касательно данной темы, которым хочется поделиться. Но когда и как я его оформлю пока не ясно. Может напишу статью, может таки сделаю видео с комментариями. Что-то точно сделаю, вопрос лишь когда.

Ссылка | Комментарии [3]

7 ноя 2013

Изображение

Продолжается мое путешествие в страну слоников и фей вокселей. Потихоньку пишу систему чанков и LOD'ов. Пока только картинки: http://imgur.com/a/rymiz

Думаю сделать видео с комментами в ближайшем будущем.

Изображение

19 окт 2013

Изображение

К моему удивлению нашел применение геометрическим шейдерам - per-vertex материал! В вершинный буфер грузим индекс материала. В геометрическом шейдере для каждой вершины создаем два новых вектора из 3 элементов, в первом индексы, во втором вес материала:
vec3(mat[0], mat[1], mat[2]); vec3(1, 0, 0)
vec3(mat[0], mat[1], mat[2]); vec3(0, 1, 0)
vec3(mat[0], mat[1], mat[2]); vec3(0, 0, 1)

Соответственно так побеждается интерполяция и в фрагментном шейдере имеем индекс материала и его вес для каждой вершины в соответствующих каналах этих новых двух векторов. Естественно текстуры храним в Texture Arrays. Не уверен на счет производительности по сравнению с другими методами, но работает. :)

Может кто знает что на эту тему? Хочется per-vertex материал, при этом чтобы геометрия осталась из индексированных вершин.

P.S. Если интересно про то, как делал ландшафт, в комментах отпишитесь, может напишу и про это.

Ссылка | Комментарии [5]

5 мая 2013

На мое удивление, в большинстве мест, где я читал о constexpr, упоминаются самые очевидные и не интересные вещи. Например о том, какие ограничения существуют для constexpr функций, хотя по факту достаточно лишь сказать, что constexpr функция должна состоять из одного return стейтмента. А вот что на мой взгляд действительно важно знать.

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

Место в грамматике


Интуитивно кажется, что constexpr это нечто похожее на const, а поэтому является частью типа в грамматике С++11, но это не так. На самом деле constexpr это совершенно отдельный declaration specifier. Вполне себе такой же как и typedef или friend, а поэтому может появляться именно там, где могут появляться эти два. Почему это важно? А потому что при работе с неимоверно сложным синтаксисом declarations в С++, стоит понимать, что правила которые действуют для const, не действуют для constexpr. Поясню на примере с указателями:
constexpr int number = 43;
int main(int argc, char **argv) {
        constexpr int *p = &number; // error: cannot initialize a variable of type 'int *const' with an rvalue of type 'const int *'
        return 0;
}

Любезный clang ясно дает понять, тип указателя p - int *const, вместо ожидаемого const int*. Так вот, собственно первый вывод: constexpr применяется к declaration в целом и не является частью типа. Если constexpr с указателями в общем-то редко используется, знать этот факт вдвойне важно при определении функций. Дело в том, что constexpr не является частью типа возвращаемого значения функции как это было бы в случае с const. Приведу опять пример с указателем и функциями, где этот факт четко иллюстрирован:

int number = 10;
constexpr int *number_addr() {
        return &number;
}
int main(int argc, char **argv) {
        int *p = number_addr();
        return 0;
}

Вполне компилируется и работает. Согласен, что пример не особо полезен на практике, но позже я покажу почему важно это все знать. Бывают реально случаи, когда указатели используются в constexpr функциях. А пока давайте суммируем наконец то, как constexpr связан с const:

  • При объявлении переменных, constexpr применяет const к самой переменной (а не к типу на который она указывает, если это указатель).
  • При объявлении функций, constexpr не влияет на тип функции. В ход, однако, вступают ограничения, о которых все так любят писать.
  • При объявлении функций-членов (они же методы), constexpr объявляет данную функцию-член константной. Не влияет на тип возвращаемого значения.
  • При объявлении конструктора, constexpr как в случае с обычной функцией ничего не меняет, только включает ограничения.

Надеюсь, все выше сказанное помогло в какой-то мере понять каким образом constexpr применяется к сущностям языка и устронило недопомнимание на тему const vs constexpr.

Когда применять constexpr


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

Если коротко, то constexpr следует применять везде, где можно. И если достаточно понятно, когда применять constexpr к переменным и обычным функциям, то применение в классах требует небольшого пояснения. Рассмотрим пример:

class vec3 {
        union {
                struct {
                        float _x, _y, _z;
                };
                float _v[3];
        };

public:
        constexpr vec3(): _x(0), _y(0), _z(0) {} // (1)
        constexpr vec3(float x, float y, float z): _x(x), _y(y), _z(z) {}
        constexpr vec3(const vec3&) = default; // (2)
        vec3 &operator=(const vec3&) = default; // (3)
        constexpr float x() { return _x; } // (4)
        constexpr float y() { return _y; }
        constexpr float z() { return _z; }
        constexpr const float *v() { return _v; } // (5)
        float *v() { return _v; } // (6)
};

1. С конструкторами я думаю понятно. Если у конструктора пустое тело и есть возможность сделать его constexpr - делайте.
2. Default и delete члены тоже можно объявлять как constexpr. Вполне возможно, что они становятся constexpr при возможности по умолчанию, но для целей самодокументирования никогда не повредит явно указать это.
3. Оператор присвоения обычно не определяется как constexpr, потому что традицонно он возвращает не константную ссылку на *this. А как мы знаем из первой секции этого поста, constexpr делает функцию-член константной (this становится const). Просто не уместно, да и область применения сомнительна, хотя вполне возможно и этот оператор сделать constexpr.
4. Если кто-то еще не понял, объявление constexpr float x() { return _x; } внутри класса эквивалентно float x() const { return _x; }, а поэтому везде где нам хотелось бы определить константную функцию-член и где есть возможность сделать ее constexpr функцией, надо делать.
5. Не забываем важную информацию из первой секции поста - constexpr не влияет на тип возвращаемого значения функции, а поэтому constexpr const сочетание очень даже имеет смысл. Кроме того, т.к. constexpr неявно делает функцию-член константной, мы не можем вернуть float* используя const float _v[3].
6. Еще раз, учитывая, что (5) является константной функией-членом, можно сделать перегрузку для не константного случая, как это традиционно делается в С++ для акцессоров возвращающих указатель или ссылку. Так что все тут вполне корректно.

Заключение


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

Ссылка | Комментарии [2]

3 мар 2013

Совсем маленький, возможно абсолютно бесполезный пример. Просто пришло в голову, решил записать куда-то.

#include <string>
#include <cstdio>
#include <cstddef>

class formatter {
  const char *format;
public:
  explicit formatter(const char *format): format(format) {}

  template <typename ...Args>
  std::string operator()(Args ...args) {
    char tmp[4096];
    size_t n = std::snprintf(tmp, sizeof(tmp), format, args...);
    return {tmp, n};
  }
};

formatter operator "" _fmt(const char *format, size_t) {
  return formatter(format);
}

int main(int argc, char **argv) {
  std::string s = "hello, %s"_fmt("nsf");
  std::string s2 = "%d + %d = %d"_fmt(1, 2, 3);
  std::printf("%s\n", s.c_str());
  std::printf("%s\n", s2.c_str());
  return 0;
}

Стоит отметить, что возможно лучше использовать variadic функцию вместо variadic шаблона - меньше кода компилятор будет генерировать. Но вспоминать va_start/va_end и va_list было лень, поэтому так.

Ссылка

11 июня 2010

http://www.realworldtech.com/forums/index.cfm?action=detail&i… &roomid=2

Ctrl+F, Linus Torvalds, наслаждайтесь.

Заранее добавлю, что в теме замечен и Walter Bright, автор языка D, являющегося попыткой сделать С++ "правильно".

А теперь мои размышления на эту тему:

Помню как ясный день и сейчас свой опыт ковыряния опен сорс С++ проектов и схожих Си проектов одновременно. Была у меня такая необходимость. И как вы догадываетесь большинство опен сорс программ написаны ленивой ручкой в свободное от повседневных тягот время, а поэтому не содержат ни документации (хотя она чаще есть, чем ее нет, если проект хоть чуточку используется людьми), ни комментариев (с этим вообще все плохо). Но задача есть, разобраться с кодом нужно, поэтому вооружаемся grep'ом на перевес и начинаем активно смотреть, что же в коде происходит. Вы когда-нибудь пробовали такое делать с C++ кодом? Только честно. Ой, а тут метод, а какому классу он принадлежит? А что это за аргументы у функции, не вызывают ли они перегруженный вариант случайно? Ой, а этот метод состоит из одной строчки и вызывает такой метод у другого класса, о боже мой, куда я попал. Ну и так далее. Не сложно догадаться, что понять происходящее внутри Си программы значительно проще. А теперь вспомните великую мудрость, что программа чаще читается, чем пишется и задумайтесь, для чего все эти упоминаемые в треде С++ фичи, которые мы все так любим? Ведь они для того, чтобы проще писать код, не так ли? А куда дели мудрость?

Однако, Линус взамен С++ предлагает взять нам мифический "real language with real features", вот только его не существует. Такой вот де-факто парадокс, если хочется всех этих самых фич, то С++ лучшее, что есть на сегодня по многим параметрам. А среди немаловажных свойств такого несуществующего языка должны быть: хорошие компиляторы, сопутствующие библиотеки, человеческие ресурсы aka программисты и прочее прочее, далеко не прямо связанное с самим языком. Вспомните такой опен сорс проект игровой направленности как OpenMW (открытая реализация движка TES III: Morrowing), ведь они решили делать переход с D на C++ именно по причинам приведенным выше. Линус одобряет Go от гугла и напоминает, что он вроде хороший, но не зря же его назвали экспериментальным и он чертовски прав! Кто ж его не одобрит? Молодцы товарищи Кен Томпсон и Роб Пайк (обратите внимание как хорошо русское слово "молодец" описывает ситуацию имея в своем составе корень "молод"). Рано еще, рано, но ведь когда-нибудь это должно свершиться! "Дайте нам новый язык, чтобы был как Си, но для современных нужд!" - скандируют массы. Где же ты, о великий создатель языка будущего?!

А нету... А жаль...

Ссылка | Комментарии [2]

5 июня 2010

Мне правда жаль будущее поколение молодых мозго***в. Решил я как-то значицо написать супер крутой коллбэк в стиле С++0x, да не сдавался до конца..

#include <stdio.h>
#include <functional>

//-------------------------------------------------------------------------
// Our main callback class, can be binded to function or member function
// of a particular class easily(?)
//-------------------------------------------------------------------------
template <typename Ret, typename ...Args>
struct funcptr_def {
  typedef Ret (*type)(Args...);
};

template <typename Signature>
struct callback;

template <typename Ret, typename ...Args>
struct callback<Ret(Args...)> {
  typedef typename funcptr_def<Ret, Args..., void*>::type function_ptr_type;

  function_ptr_type fptr;
  void *data;

  void bind(function_ptr_type fptr, void *data = 0)
  {
    this->fptr = fptr;
    this->data = data;
  }

  Ret call(Args ...args)
  {
    return (*fptr)(std::forward<Args>(args)..., data);
  }
};

//-------------------------------------------------------------------------
// Helper binder
//-------------------------------------------------------------------------

template <int ...> struct int_tuple {};

template <int I, typename IntTuple, typename ...Types>
struct make_indices_impl;

template <int I, int ...Indices, typename T, typename ...Types>
struct make_indices_impl<I, int_tuple<Indices...>, T, Types...>
{
  typedef typename make_indices_impl<I+1, int_tuple<Indices..., I>, Types...>::type type;
};

template <int I, int ...Indices>
struct make_indices_impl<I, int_tuple<Indices...>> {
  typedef int_tuple<Indices...> type;
};

template <typename ...Types>
struct make_indices : make_indices_impl<0, int_tuple<>, Types...> {};

//-------------------------------------------------------------------------

template <typename T>
void *get_void_ptr(T &&arg)
{
  return arg;
}

template <typename T, typename ...Args>
void *get_void_ptr(T &&arg, Args &&...args)
{
  return get_void_ptr(std::forward<Args>(args)...);
}

//-------------------------------------------------------------------------

template <typename Ret>
struct callback_helper_impl {
  template <typename T, typename MemberFunctionPtr, typename ...Args, int ...Indices>
  static Ret callback_run(T *object, MemberFunctionPtr m, const std::tuple<Args&...>& args, int_tuple<Indices...>)
  {
    return (object->*m)(std::get<Indices>(args)...);
  }
};

template <typename Signature, typename MemberSignature, MemberSignature FunctionPtr>
struct callback_helper;

template <typename Ret, typename ...Args, typename T, typename ...MemArgs, Ret(T::*MemberFunction)(MemArgs...)>
struct callback_helper<Ret(Args...), decltype(MemberFunction), MemberFunction> {
  typedef typename make_indices<MemArgs...>::type indices;

  static Ret function(Args ...args)
  {
    T *object = (T*)get_void_ptr(args...);
    return callback_helper_impl<Ret>::callback_run(object, MemberFunction, std::tie(args...), indices());
  }
};

// for convenience (orly?!!?111 omgwtfbbq)
#define WRAP_METHOD(f) decltype(f), f

//-------------------------------------------------------------------------
// Example class
//-------------------------------------------------------------------------
struct dog_t {
  void bark(int a, int b)
  {
    printf("bark! %d %d\n", a, b);
  }
};

//-------------------------------------------------------------------------
// Example function
//-------------------------------------------------------------------------
void say_hello(int a, int b, void *notused)
{
  printf("Hello! %d %d\n", a, b);
}


int main(int argc, char **argv)
{
  dog_t dog;
  callback<void (int, int)> cb;

  int i = 1;

  // function
  cb.bind(say_hello);
  cb.call(i, 2);

  // member function
  cb.bind(callback_helper<void (int, int, void*), WRAP_METHOD(&dog_t::bark)>::function, &dog);
  cb.call(i, 2);

  return 0;
}

27 мая 2010

Зачем это нужно? История умалчивает. Медитируйте. :)

#include <cstdio>
#include <cstdint>
#include <new>
#include <utility>

// class represents space for a type with manual construction and destruction
// (also can PODify any class, e.g. convert full featured Class to POD type)
template <typename T>
struct storage {
  template <typename ...Args>
  void construct(Args &&...args)  { new (buf) T(std::forward<Args>(args)...); }

  void destruct() { get()->~T(); }

  T *operator->() { return get(); }
  T &operator*() { return *get(); }

  T *get() { return reinterpret_cast<T*>(buf); }

  uint8_t buf[sizeof(T)];
};

struct WeirdClassWithLotsOfData {
  // have no default constructor
  WeirdClassWithLotsOfData(int a, int b, int c): a(a), b(b), c(c)
  {
    for (size_t i = 0; i < 100; ++i)
      d[i] = i;
    printf("Weird class constructed: %d %d %d\n", a, b, c);
  }

  ~WeirdClassWithLotsOfData()
  {
    printf("Weird class destroyed: %d %d %d\n", a, b, c);
  }

  void say_hello()
  {
    printf("Hello: %d %d %d\n", a, b, c);
  }

  int a, b, c;
  int d[100];
};

// allocate space for all data in one malloc
struct PackOfWeirdness {
  storage<WeirdClassWithLotsOfData> objects[5];

  PackOfWeirdness()
  {
    // do weird initialization
    for (int i = 4; i >= 0; --i)
      objects[i].construct(i+1, i+2, i+3);
  }

  ~PackOfWeirdness()
  {
    for (size_t i = 0; i < 5; ++i)
      objects[i].destruct();
  }

  void reinit()
  {
    for (size_t i = 0; i < 5; ++i) {
      objects[i].destruct();
      objects[i].construct(i+1, i*i, i*i*i);
    }
  }
};

int main(int argc, char **argv)
{
  printf("Before pack of weirdness\n");
  {
    auto pack = new PackOfWeirdness;
    for (size_t i = 0; i < 5; ++i)
      pack->objects[i]->say_hello();
    pack->reinit();
    delete pack;
  }
  printf("After pack of weirdness\n");
  return 0;
}

Ссылка

6 ноя 2007

Только что состоялся первый билд модифицированного open source рендера yafray (http://yafray.org). А также первый успешный тест!
Теперь yafray может рендерить лайтмапы!!!
Я (скорей всего уже завтра) продолжу улучшать эту модифицированную версию. Если кому это интересно, пожалуйста отпишитесь в комментариях, я тогда буду более подробно освещать все действия. А также если кто-то желает помочь - отпишитесь в комментариях опять же или лучше найдите меня на #gamedev @ IRCNET.RU.

Yafray имеет очень удобный интерфейс как в виде библиотеки, так и консольный с своим форматом файла (xml). Поэтому я надеюсь сделать из него хороший опенсорс лайтмап рендер.
Насчет всяких фич типа radiosity не уверен, но буду работать понемногу.

Вот небольшой отчет с скринами..
BLENDER
1. Мини сцена отрендерена блендером
http://d2k5upload.parahelix.org/files/1194369232_lightmap_blender.jpg

2. Лайтмап полученный встроенными средствами в blender
http://d2k5upload.parahelix.org/files/1194369275_lightmap_blender_lm.jpg
http://img128.imageshack.us/img128/1601/lightmapblenderlmsf9.jpg (copy)

3. Отрендеренная блендером сцена с натянутым лайтмапом (который п. 2)
http://d2k5upload.parahelix.org/files/1194369255_lightmap_blender… ghtmapped.jpg
http://img128.imageshack.us/img128/4229/lightmapblenderlightmapkt8.jpg (copy)

4. Тоже что и п. 3, только отрендерено yafray'ем.
http://d2k5upload.parahelix.org/files/1194369265_lightmap_blender… ed_yafray.jpg

yafray
5. Мини сцена отрендерена yafray'ем.
http://d2k5upload.parahelix.org/files/1194369427_lightmap_yafray.jpg

6. Лайтмап сгенерированный модифицированным мной yafray'ем (пока криво, но это первый билд!)
http://d2k5upload.parahelix.org/files/1194369440_lightmap_yafray_lm.jpg
http://img134.imageshack.us/img134/8913/lightmapyafraylmzl2.jpg (copy)

7. Отрендеренная yafray'ем сцена с натянутым лайтмапом (который п. 6)
http://d2k5upload.parahelix.org/files/1194369432_lightmap_yafray_… ghtmapped.jpg
http://img87.imageshack.us/img87/3290/lightmapyafraylightmappsj8.jpg (copy)

8. Тоже что и п. 7, только отрендерено блендером
http://d2k5upload.parahelix.org/files/1194369436_lightmap_yafray_… d_blender.jpg

Жду ваших комментариев :)

Ссылка

2 ноя 2007

Решил создать своё персональное сообщество для того, чтобы делиться опытом с остальными любителями и профессионалами в геймдеве :) Я интересуюсь геймдевом уже давно и у меня накопился ряд мыслей, которые неплохо было бы изложить в виде статеек. Что в ближайшее время я постараюсь и сделать. Надеюсь вам будет интересно. %)

Первая статья будет о текстурировании ландшафта.
Далее планирую одну статейку о convolution kernel image processing/filtering.
И потом немного о blender'е в качестве универсального редактора для game контента.

Ссылка

Архив