微风小杨 发表于 2016-1-31 13:33:22

Arduino + OLED12864 做一个简单的3d线框渲染引擎(附ssd1306的库)更新库和投影变换

本帖最后由 微风森林 于 2016-2-17 21:48 编辑

(代码已改为透视投影,但视频和图还是正交投影的视频,懒得再改了~~大家看我下面回复的那种效果吧)
________________________________________________________

提到3d渲染引擎,大家都会有一种遥不可及的感觉,对吧??

其实呢,没那么复杂。比如,我们用arduino就能完成3d引擎的两个步骤之一: vertex操作。(另一个是pixel/fragment操作)

只要空间数学功底好,3d其实就是对顶点的旋转,连接构成面,然后贴图而已。当然贴图用arduino基本上是不可能的。。。

我们的做法是什么呢?先给出3d物体的各个顶点坐标,指定顶点的连接顺序,然后将3d坐标正交投影到2d的屏幕上来,按连接顺序连线

什么?不知道正交投影?呵呵,这个词看似高端,其实就是这么回事啦:丢掉z坐标,只保留x,y,就能跟屏幕坐标系对应了



为了抛砖引玉,我们来最简单的一个场景:旋转一个立方体

我们对立方体进行旋转,获得旋转后的坐标,然后丢掉z,保留x和y,之后,按照规定好的连接顺序,将屏幕上的点连接起来
就这么简单!如果有兴趣,大家可以自己yy一个物体,把顶点替换掉代码里的,规定好连接顺序(第几个点和第几个点相连),就可以显示自制物体啦~~

//作者:微风森林

#define OLED_DC 9
#define OLED_CS 12
#define OLED_RESET 10

#include <SSD1306.h>
#include <SPI.h>
#include "MyObj.h"
SSD1306 oled(OLED_DC, OLED_RESET,OLED_CS);

static MyVertex mp[] ={{ -16, -16,-16}, \
                      {16, -16, -16}, \
                      {16, 16, -16}, \
                      {-16, 16, -16}, \
                      {-16, -16,16}, \
                      {16, -16, 16}, \
                      {16, 16, 16}, \
                      {-16, 16, 16}
};
static MyEdge me[] ={{0, 1}, \
                  {1, 2}, \
                  {2, 3}, \
                  {3, 0}, \
                  {4, 5}, \
                  {5, 6}, \
                  {6, 7}, \
                  {7, 4}, \
                  {0, 4}, \
                  {1, 5}, \
                  {2, 6}, \
                  {3, 7}
};

MyObjectobj={0, 0, mp, me,{1,0,0,0},{0,0,0}};

void setup()   {
Serial.begin(9600);
SPI.setClockDivider(0);
SPI.begin();

//correct vertex num and edge num

obj.numv=sizeof(mp)/sizeof(MyVertex);
obj.nume=sizeof(me)/sizeof(MyEdge);

oled.ssd1306_init(SSD1306_SWITCHCAPVCC);
oled.display(); // show splashscreen
delay(1000);
oled.ssd1306_command(SSD1306_INVERTDISPLAY); //图像反色,注释掉则为黑底白图
oled.clear();   // clears the screen and buffer
moveObject(obj,0,0,70);
renderObject(obj);
oled.display();
}

static float qdelta={0.999847695f,0,0.0174524f,0};
static float qview={0.25881904510252076234889883762405f,0.9659258262890682867497431997289f,0,0};
static float qtemp;

#define near 1.0
#define far 100.0
#define right 1.8
#define top 1.8

static float proj={
    near/right,    0,         0,                  0, \
    0,         near/top,      0,                  0
//    0,             0,   -(far+near)/(far-near), -2*far*near/(far-near),
//    0,             0,         -1,                   0
};
void loop()
{
rotateObject(obj,qdelta);
oled.clear();   // clears the screen and buffer
renderObject(obj);
oled.display();
}


void moveObject(MyObject &mo, float x, float y, float z) {
mo.offset=x;
mo.offset=y;
mo.offset=z;
}

void rotateObject(MyObject &mo, float* q) {
qproduct(q,obj.quat,qtemp);
mo.quat=qtemp;
mo.quat=qtemp;
mo.quat=qtemp;
mo.quat=qtemp;
qnormalized(qtemp);
}

void renderObject(MyObject &mo) {
MyVertex* mv=new MyVertex;

qproduct(qview,mo.quat,qtemp);

for (int i = 0; i < mo.numv; i++) {
    float vtemp;
    iqRot(qtemp,mo.v.location,vtemp);
    vtemp += mo.offset;
    vtemp += mo.offset;
    vtemp += mo.offset;

    MatMulVect(proj, vtemp, mv.location);
}


for (int i = 0; i < mo.nume; i++) {
    int8_t p1 = mo.e.connection;
    int8_t p2 = mo.e.connection;
    oled.drawline(mv.location+64, mv.location+32, mv.location+64, mv.location+32, WHITE);
}

delete mv;
}

float iqRot(float q[],int8_t v[],float result[]){
float prod;
prod =- q * v - q * v - q * v;
prod = q * v + q * v - q * v;
prod = q * v - q * v + q * v;
prod = q * v + q * v - q * v;

result = -prod * q + prod * q - prod * q + prod * q;
result = -prod * q + prod * q + prod * q - prod * q;
result = -prod * q - prod * q + prod * q + prod * q;
}

void qproduct(const float* p, const float* q, float* qr) {
qr = p * q - p * q - p * q - p * q;
qr = p * q + p * q + p * q - p * q;
qr = p * q - p * q + p * q + p * q;
qr = p * q + p * q - p * q + p * q;
}

void qnormalized(float* q) {
float invnorm;
invnorm = fastinvsqrt(q * q + q * q + q * q + q * q);
if (invnorm < 100000000) {
    q *= invnorm;
    q *= invnorm;
    q *= invnorm;
    q *= invnorm;
} else {
    q = 1;
    q = 0;
    q = 0;
    q = 0;
}
}
float fastinvsqrt(float x) {
float halfx = 0.5f * x;
float y = x;
long i = *(long*)&y;
i = 0x5f3759df - (i>>1);
y = *(float*)&i;
y = y * (1.5f - (halfx * y * y));
return y;
}

float MatMulVect(float M[], float V[], int8_t result[]){
for(uint8_t i=0; i<2; i++){
   result = (M*V+M*V+M*V+M)/-V*128;
}
}


这是程序主体文件。SCL连13,SDA连11,RST连10,D/C连9。如果大家的oled还有使能脚CE,请连着12脚,或者直接把使能脚接VCC或GND,都试试看哪个可以点亮

我们还有个自定义的结构体定义,请存为MyObj.h并放入代码同一目录中

typedef struct Vertex{
int8_t location;
}MyVertex;
typedef struct Edge{
int8_t connection;
}MyEdge;
typedef struct Object{
int numv;
int nume;
MyVertex* v;
MyEdge* e;
float quat;
float offset;
}MyObject;

第三,我使用的OLED库文件叫做SSD1306,请将库文件解压放入arduino的library目录中


大功已告成!可以看代码很简单,对顶点的旋转用到了四元数,这基本上是各种3d引擎的基本运算了。如果这个旋转四元数用imu融合的四元数会怎样?大家自己去试吧~~

PS:我这边代码会让oled屏幕有几个亮点,初步分析是数组越界访问了oled的framebuffer,懒得去仔细分析了,如果大家分析出是哪里有问题请麻烦告知一声

本程序是最简单的正交投影的情况。如果需要实现正常的视锥体透视投影,需要计算一个投影矩阵。由于是抛砖引玉我这里就不去做的,感兴趣可以翻翻资料,以后有空我也会讲讲

http://v.youku.com/v_show/id_XMTQ2MTgwNjA3Ng==.html


======================================
新增纸飞机代码如下
static MyVertex mp[] ={{-20,0,-20}, \
                      {0,0,20}, \
                      {20,0,-20}, \
                      {0,0,-20}, \
                      {0,10,-20}
};
static MyEdge me[] ={{0, 1}, \
                  {1, 2}, \
                  {2, 3}, \
                  {3, 4}, \
                  {4, 1}, \
                  {1, 3}, \
                  {3, 0}
};

http://v.youku.com/v_show/id_XMTQ2MzA2OTgwOA==.html

======================================
新增一辆简单坦克如下

注,为了节约内存,代码做了一些修改并已同步,之前copy的同学可以重新copy一下

static MyVertex mp[] ={
            {15,10,16}, \
            {15,10,-21}, \
            {-15,10,-21}, \
            {-15,10,16}, \
            {15,0,23}, \
            {15,0,-27}, \
            {-15,0,-27}, \
            {-15,0,23}, \

            {12,0,9}, \
            {-12,0,9}, \
            {-12,0,-18}, \
            {12,0,-18}, \
            {9,-6,6}, \
            {-9,-6,6}, \
            {-9,-6,-15}, \
            {9,-6,-15}, \

            {0,-3,7.5}, \
            {0,-3,38}, \
            {-3,-3,38}, \
            {3,-3,38}
};
static MyEdge me[] ={
            {0, 1}, \
//            {1, 2}, \
            {2, 3}, \
//            {3, 0}, \
            {4, 5}, \
            {5, 6}, \
            {6, 7}, \
            {7, 4}, \
            {0, 4}, \
            {1, 5}, \
            {2, 6}, \
            {3, 7}, \

            {8, 9}, \
            {9, 10}, \
            {10, 11}, \
            {11, 8}, \
            {12, 13}, \
            {13, 14}, \
            {14, 15}, \
            {15, 12}, \
            {8, 12}, \
            {9, 13}, \
            {10, 14}, \
            {11, 15}, \

            {16, 17}, \
            {17, 18}, \
            {17, 19}
};


http://v.youku.com/v_show/id_XMTQ2MzIwMzA5Ng==.html

麦克小小狼 发表于 2016-2-1 18:34:47

帅,加个陀螺仪,搞成自动的:lol

微风小杨 发表于 2016-2-1 20:39:31

本帖最后由 微风森林 于 2016-2-1 20:42 编辑

麦克小小狼 发表于 2016-2-1 18:34 static/image/common/back.gif
帅,加个陀螺仪,搞成自动的

mpu6050我已经试过了:lol 我弄了个飞机模型来转

纸飞机模型如下:

static MyVertex mp[] ={{-20,0,-20}, \
                      {0,0,20}, \
                      {20,0,-20}, \
                      {0,0,-20}, \
                      {0,10,-20}
};
static MyEdge me[] ={{0, 1}, \
                  {1, 2}, \
                  {2, 3}, \
                  {3, 4}, \
                  {4, 1}, \
                  {1, 3}, \
                  {3, 0}
};
MyObjectobj={5, 7, mp, me,{1,0,0,0},{0,0,0}};

bh7jec 发表于 2016-2-1 23:39:59

非常棒,收藏了

wetnt 发表于 2016-2-2 00:00:44

:)非常棒,收藏了

微风小杨 发表于 2016-2-4 19:25:44

本帖最后由 微风森林 于 2016-2-6 00:12 编辑

加入透视投影矩阵后的效果,是这样

http://v.youku.com/v_show/id_XMTQ2NTU4MDI0MA==.html

也就是所谓的近大远小的透视规律。以后有空,我会跟大家说说这个矩阵

febao8310 发表于 2016-2-13 21:15:16

编译出错呀小哥,1.6.5。你用的是什么版本的IDE呢。
C:\Users\Test\AppData\Local\Temp\build5938115168946750201.tmp/core.a(main.cpp.o): In function `main':
C:\Program Files\Arduino\hardware\arduino\avr\cores\arduino/main.cpp:40: undefined reference to `setup'
C:\Program Files\Arduino\hardware\arduino\avr\cores\arduino/main.cpp:43: undefined reference to `loop'
collect2.exe: error: ld returned 1 exit status
Error compiling.

微风小杨 发表于 2016-2-17 19:12:30

本帖最后由 微风森林 于 2016-2-17 19:13 编辑

febao8310 发表于 2016-2-13 21:15 static/image/common/back.gif
编译出错呀小哥,1.6.5。你用的是什么版本的IDE呢。
C:\Users\Test\AppData\Local\Temp\build593811516894 ...

我也是1.6.5。你这错误....应该是ide抽风了吧。

第一个代码文件是ino文档,第二个代码文件要存成.h头文件,放入同一个目录

微风小杨 发表于 2016-2-17 19:21:20

主程序已更新了透视投影变换的矩阵,大家可以试试

库也已经修改了不合理地方,现在不会出现内存泄露的问题了

不过这个矩阵讲起来比较麻烦,如有需要,我可以找个时间讲讲

winoga 发表于 2016-2-29 23:51:17

我只能说!你很牛b

FredKhoo 发表于 2016-4-7 13:48:01

我来搬运些基础知识:
https://www.ntu.edu.sg/home/ehchua/programming/opengl/CG_BasicsTheory.html

degage 发表于 2016-4-8 13:41:28

吊暴了,表示支持,加油。

22881916 发表于 2016-4-11 22:29:34

谢谢楼主分享~\(≧▽≦)/~啦啦啦

22881916 发表于 2016-4-17 10:44:00

楼主好厉害,我想问一下怎么让它转到特定角度呢,而不是只是旋转

飞翔的红猪 发表于 2016-4-18 14:26:43

这个得收藏~~
页: [1] 2
查看完整版本: Arduino + OLED12864 做一个简单的3d线框渲染引擎(附ssd1306的库)更新库和投影变换