Софт рендер

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

Допустим, мы можем установить определенный пиксель в требуемый цвет, тогда мы можем рисовать любые графические примитивы и фигуры.

Для рисования примитивов – прямых, хорошо подходит алгоритм Брезенхема. Его алгоритм подсмотрен на вики ru.wikipedia.org/wiki/Алгоритм_Брезенхэма. Он использует целочисленную арифметику, что идеально подходит для микроконтроллеров, проверено для 8бит AVR. Также использовался буфер в оперативке, из которого вывод происходит на на черно-белый экранчик.

А теперь то, для чего это все – рисуем 3D-кубик. Это, так называемый софтверный рендерер. Данный код не есть что-то новое, в сети полно демок с похожим принципом работы. Из особенностей: Z-буфер не используется т.к. у нас всего лишь два цвета и мешевая форма модели (без всяких псевдо-текстур). Для простоты используются вычисления с плавающей точкой а не целочисленная арифметика, что, конечно, сильно не оптимально, но и так пойдет, пока что.

Для уменьшения размера кода использовались самодельные функции косинуса и синуса на 64 угла вращения (больше не требовалось).

const float cospi[]= {
    1,  //0
    0.99518473,
    0.98078528,
    0.95694034,
    0.92387953,
    0.88192126,
    0.83146961,
    0.77301045, //7
    0.70710678,
    0.63439328,
    0.55557023,
    0.47139674,
    0.38268343,
    0.29028468,
    0.19509032,
    0.09801714, //15
    0  //16
};

float cos_(unsigned char angle)
{
    unsigned char a1, a2;
    a1 = angle & 0x0f;
    a2 = angle >> 4; // определяем сектор
    if(a2 == 0) return (float)(cospi[a1]);
    if(a2 == 1) return (float)(cospi[16-a1] * (-1));
    if(a2 == 2) return (float)(cospi[a1] * (-1));
    if(a2 == 3) return (float)(cospi[16-a1] );
}

float sin_(unsigned char angle)
{
    unsigned char a1, a2;
    a1 = angle & 0x0f;
    a2 = angle >> 4;
    if(a2 == 0) return (float)(cospi[16-a1]);
    if(a2 == 1) return (float)(cospi[a1]);
    if(a2 == 2) return (float)(cospi[16-a1] * (-1));
    if(a2 == 3) return (float)(cospi[a1] * (-1));
}

Есть мнение, что лучще перенести массивы переменных во флешь, да и многое нужно поменять… но пока и так сойдет.
//Константы
#define CUBE_SIZE    33 // (длина ребра)/2
#define DOTS_COUNT    8 // Всего вершин
#define MESH_COUNT   12 // количество ребер
#define LCD_X_SIZE  176 // разрешение по горизонтали
#define LCD_Y_SIZE  132 // разрешение по вертикали

/** углы поворота по осям, от 0 до 63 включительно,
 *  это полный оборот на 360
 */
unsigned char dir_x = 0; 
unsigned char dir_y = 0;
unsigned char dir_z = 0; 

//точки, лучше поменять на long
signed short Xa[DOTS_COUNT], Ya[DOTS_COUNT], Za[DOTS_COUNT];

/** массивы содержат номера начальных (s1) и конечных (f1) точек,
 *  по ним соединяем все и рисуем ребра(сетку) 
 */
const unsigned char  s1[MESH_COUNT] = {
    0, 1, 2, 3, 0, 1, 2, 3, 4 ,5, 6, 7
};

const unsigned char f1[MESH_COUNT] = {
    1, 2, 3, 0, 4, 5, 6, 7, 5, 6, 7, 4
}; 

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

void Cube_draw(char c_color)
{
    unsigned char i;
    unsigned char angle; //буфер для угла
    signed char x1,y1,z1;  // координаты точек от центра объекта
    float f;          // временная переменная
    char x2d[MESH_COUNT], y2d[MESH_COUNT]; // ”плоские” точки
    
    //Расставляем точки модели, считаем что центр куба 
    //совпадает с центром координат
    Xa[0] = -CUBE_SIZE; Ya[0] =  CUBE_SIZE; Za[0] =  CUBE_SIZE;
    Xa[1] =  CUBE_SIZE; Ya[1] =  CUBE_SIZE; Za[1] =  CUBE_SIZE;
    Xa[2] =  CUBE_SIZE; Ya[2] = -CUBE_SIZE; Za[2] =  CUBE_SIZE;
    Xa[3] = -CUBE_SIZE; Ya[3] = -CUBE_SIZE; Za[3] =  CUBE_SIZE;
    Xa[4] = -CUBE_SIZE; Ya[4] =  CUBE_SIZE; Za[4] = -CUBE_SIZE;
    Xa[5] =  CUBE_SIZE; Ya[5] =  CUBE_SIZE; Za[5] = -CUBE_SIZE;
    Xa[6] =  CUBE_SIZE; Ya[6] = -CUBE_SIZE; Za[6] = -CUBE_SIZE;
    Xa[7] = -CUBE_SIZE; Ya[7] = -CUBE_SIZE; Za[7] = -CUBE_SIZE;

    //Вращаем наши точки, фактически матрицы вращения упрощенные
    for (i = 0; i < DOTS_COUNT; i++) //по X
    {
        y1 = Ya[i] ;
        z1 = Za[i] ;
        angle = dir_x ;
        Ya[i] =  cos_(angle) * y1 - sin_(angle) * z1;
        Za[i] =  cos_(angle) * z1 + sin_(angle) * y1 ;
    }

    for (i = 0; i < DOTS_COUNT; i++) //по Y
    {
        x1 = Xa[i] ;
        z1 = Za[i] ;
        angle = dir_y ;
        Xa[i] =  cos_(angle) * x1 + sin_(angle) * z1;
        Za[i] = -sin_(angle) * x1 + cos_(angle) * z1;
    }

    for (i = 0; i < DOTS_COUNT; i++) //и Z незабыть!
    {
        x1 = Xa[i] ;
        y1 = Ya[i] ;
        angle = dir_z ;
        Xa[i] =  cos_(angle) * x1 - sin_(angle) * y1;
        Ya[i] =  cos_(angle) * y1 + sin_(angle) * x1;
    }

    //Трансформация координат вершин в экранные
    for (i = 0; i < DOTS_COUNT ; i++)
    {
        //1000 и 1200 определяют расстояние от  объекта до камеры и 
        f = 1000 / (1100 -  (float)Za[i]);
        // рисуем объект с центром в по-центре экрана
        x2d[i] = (unsigned char)((f * (float)Xa[i]) + LCD_X_SIZE/2);
        y2d[i] = (unsigned char)((f * (float)Ya[i]) + LCD_Y_SIZE/2);
    }

    //Рисуем ребра/сетку
    for(i = 0; i < MESH_COUNT; i++)
    {
        _draw_line( x2d[s1[i]], y2d[s1[i]], x2d[f1[i]], y2d[f1[i]], c_color); //Рисуем линию
    }
}


Проверка функции такая — зависаем в главном цикле и рисуем два кубика — черный (это очистка) а потом зеленый, и задержку ждем.


    //Очистка всего экрана
    _fill_screen(c_black);

    while(1)
    {
        Cube_dr// стираем
        //Инкрементируем углы
        if(dir_x++ >= 63) dir_x = 0;
        if(dir_y++ >= 63) dir_y = 0;
        if(dir_z++ >= 63) dir_z = 0;
        Cube_draw(c_green); // рисуем
        _delay(500000);
    }

Если есть электронный гироскоп, то можно изменять его данными углы вращения, например.

Для рисования чего-то более сложного чем кубик нужно поработать в 3Д-редакторе, например в блендере, для получения данных отрисовки.
Чтобы не усложнять, вот тот-же кубик в окне этого редактора,

а вот его obj-файл:

# Blender3D v248 OBJ File: 
# www.blender3d.org
mtllib untitled.mtl
v 2.881155 4.978182 -2.641401
v 2.881155 4.978182 -0.641401
v 0.881155 4.978182 -0.641401
v 0.881155 4.978182 -2.641401
v 2.881155 6.978182 -2.641400
v 2.881154 6.978182 -0.641400
v 0.881155 6.978182 -0.641401
v 0.881155 6.978182 -2.641401
usemtl (null)
s off
f 1 2 3 4
f 5 8 7 6
f 1 5 6 2
f 2 6 7 3
f 3 7 8 4
f 5 1 4 8


Видно, что в принципе можно найти взаимосвязь и использовать это для загрузки сложных 3Д-моделей.
  • 0
  • 19 августа 2011, 17:15
  • KT3012

Комментарии (25)

RSS свернуть / развернуть
А картинки/видео?
+1
А зачем? И так вроде все ясно.
0
Глупый вопрос, на самом деле. Видео давай! :)
0
Кубик не видели, что ли?
0
… если реализация на каком нибудь мк, то хотелось бы взглянуть!
0
Сла-а-айды!!! Сла-а-айды!!! ;)
0
  • avatar
  • Resp
  • 19 августа 2011, 17:55
Си-и-ись… кхм… Сла-а-айды!!! Сла-а-айды!!!
-1
Гм, а с каких пор у куба 9 вершин? Я еще понимаю 24…
0
  • avatar
  • Vga
  • 19 августа 2011, 18:25
спс, три раза хотел изменить, склероз…
0
Материал для продвинутых. Если бы я не занимался рендерингом 3D, то вообще не всосал бы.)))
ИМХО.
Думаю начинающим было бы интересно как происходит преобразование с разных проекций и с разными углами осей, причем в картинках а не просто в коде, как происходит масштабирование и отрисовка Z буфера. Хотя про это столько написано)))
0
Да-да, за тридэ — к геймдевелоперам :)
Плюс есть одна монументальная книга «OpenGL Руководство для профессионалов» чтоли, так вот там про огл почти ничего, зато дофига про то, как собственно рендеринг работает.
0
Исходя из твоих слов эта статья тут неуместна)))
0
Ну почему, как вводная. А за подробностями все равно придется идти туда, вопросы рендеринга там неоднократно разжеваны, в том числе и софтового (хотя нынче он и малоактуален для геймдева, даже под приставками ребота идет через OpenGL ES или подобный API).
0
Даёшь КС 1.6 на тиньке! Хотя я сам занимался гейм-девелопингом, но так, просто интересно было.
+1
Я не оспариваю нужность статьи, она хорошая, автору большой плюс. Но сам посуди — рендеринг на си это общая тема, ведь не важно где компилировать. А вот если затрагивать темы такие как: сколько займет вывод точки, окружности, сколько FPS выдаст используя тот или иной МК, то это уже как раз тематика.
Вспомнил как во времена спекки мы мерились пиписьками пытаясь «обогнать» друг друга написав более быстрый код для вывода одной точки по x,y. Уже не помню точно, но быстрой было в районе 40 тактов процессора.
+1
На тему графики для начинающих можно почитать книжку Д.Роджерс — Алгоритмические основы машинной графики. Я подробно не смотрел, пролистал просто, но вроде для мк самое то.
0
Да книг полно) Вообще изобретать не надо, берем серию книг по графике на спектруме и лопатим. Там есть и матричное преобразование и много много интересного, а главное — заточенное под слабый проц, хоть и с большим плюсом — DRAM))
0
На самом деле, трехмерные кубики не так полезны в наших мирских делах, как двухмерные квардаты/круги/полигоны/тд.
0
В ссылку на педивикию прокралась наглая точка, поэтому ссылка ведет на неизвестную страницу
0
www.youtube.com/watch?v=AqZfx6gPDoI&feature=youtu.be
добрый день! запустил ваш код, есть несколько вопросов:
1)как сделать куб а не пирамиду?
2)что нужно изменить чтобы размер фигуры не менялся?
3)f = (float)((float)1000 / ( 1200 — Za[i] )); зачем каждый раз считать f? ведь Za[i] в цикле не меняется?
0
  • avatar
  • del
  • 25 октября 2013, 01:27
1,2) — это все следствие примитивности и общей кривости кода, переписал код, проверил, обновил статью.
3) меняется. Тут не менялось нечто другое, исправил.
0
Все работает, ВОТ видео.
0
Ну хоть кто-то видео показал, а то с автора так и не допросились)

Кстати, вопрос автору: почему не применяются классические матрицы трансформации? Я не думаю, что они менее эффективны, чем выбранные формулы (тем более что тут даже не кэшируется рассчитанное значение синуса и косинуса угла).
0
Мне не на что видео снять. К вопросу почему такой вид процедур трансформации — мне тогда казалось, что так понятнее и проще будет, код упростил слишком. Самому неприятно смотреть, но нужно же начинать с чего-то, было просто интересно на МК это запустить. Про кэширование правильное замечание, да и на математику с целыми числами перевести не помешает. Но сейчас не особо хочется этим заниматься, тем более есть готовые 3D «движки», например Microtouch, в исходниках есть рендерер икосаэдра и демо DOOM — www.ladyada.net/products/microtouch/
Вот еще классная штучка, в исходниках тоже есть 3D — rossumblog.com/2010/10/25/building-the-rbox/
0
Замечательно. Жду продолжения про рендеринг шрифтов и наложение текстур.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.