极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 54414|回复: 26

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

[复制链接]
发表于 2016-1-31 13:33:22 | 显示全部楼层 |阅读模式
本帖最后由 微风森林 于 2016-2-17 21:48 编辑

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

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

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

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

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

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



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

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

  1. //作者:微风森林

  2. #define OLED_DC 9
  3. #define OLED_CS 12
  4. #define OLED_RESET 10

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

  9. static MyVertex mp[] ={{ -16, -16,-16}, \
  10.                       {16, -16, -16}, \
  11.                       {16, 16, -16}, \
  12.                       {-16, 16, -16}, \
  13.                       {-16, -16,16}, \
  14.                       {16, -16, 16}, \
  15.                       {16, 16, 16}, \
  16.                       {-16, 16, 16}
  17. };
  18. static MyEdge me[] ={{0, 1}, \
  19.                     {1, 2}, \
  20.                     {2, 3}, \
  21.                     {3, 0}, \
  22.                     {4, 5}, \
  23.                     {5, 6}, \
  24.                     {6, 7}, \
  25.                     {7, 4}, \
  26.                     {0, 4}, \
  27.                     {1, 5}, \
  28.                     {2, 6}, \
  29.                     {3, 7}
  30. };

  31. MyObject  obj={0, 0, mp, me,{1,0,0,0},{0,0,0}};

  32. void setup()   {
  33.   Serial.begin(9600);
  34.   SPI.setClockDivider(0);
  35.   SPI.begin();

  36.   //correct vertex num and edge num

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

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

  48. static float qdelta[4]={0.999847695f,0,0.0174524f,0};
  49. static float qview[4]={0.25881904510252076234889883762405f,0.9659258262890682867497431997289f,0,0};
  50. static float qtemp[4];

  51. #define near 1.0
  52. #define far 100.0
  53. #define right 1.8
  54. #define top 1.8

  55. static float proj[8]={
  56.     near/right,    0,         0,                    0, \
  57.     0,         near/top,      0,                    0
  58. //    0,             0,   -(far+near)/(far-near), -2*far*near/(far-near),
  59. //    0,             0,         -1,                   0
  60. };
  61. void loop()
  62. {
  63.   rotateObject(obj,qdelta);
  64.   oled.clear();   // clears the screen and buffer
  65.   renderObject(obj);
  66.   oled.display();
  67. }


  68. void moveObject(MyObject &mo, float x, float y, float z) {
  69.   mo.offset[0]=x;
  70.   mo.offset[1]=y;
  71.   mo.offset[2]=z;
  72. }

  73. void rotateObject(MyObject &mo, float* q) {
  74.   qproduct(q,obj.quat,qtemp);
  75.   mo.quat[0]=qtemp[0];
  76.   mo.quat[1]=qtemp[1];
  77.   mo.quat[2]=qtemp[2];
  78.   mo.quat[3]=qtemp[3];
  79.   qnormalized(qtemp);
  80. }

  81. void renderObject(MyObject &mo) {
  82.   MyVertex* mv=new MyVertex[mo.numv];

  83.   qproduct(qview,mo.quat,qtemp);

  84.   for (int i = 0; i < mo.numv; i++) {
  85.     float vtemp[3];
  86.     iqRot(qtemp,mo.v[i].location,vtemp);
  87.     vtemp[0] += mo.offset[0];
  88.     vtemp[1] += mo.offset[1];
  89.     vtemp[2] += mo.offset[2];

  90.     MatMulVect(proj, vtemp, mv[i].location);
  91.   }

  92.   
  93.   for (int i = 0; i < mo.nume; i++) {
  94.     int8_t p1 = mo.e[i].connection[0];
  95.     int8_t p2 = mo.e[i].connection[1];
  96.     oled.drawline(mv[p1].location[0]+64, mv[p1].location[1]+32, mv[p2].location[0]+64, mv[p2].location[1]+32, WHITE);
  97.   }
  98.   
  99.   delete mv;
  100. }

  101. float iqRot(float q[],int8_t v[],float result[]){
  102.   float prod[4];
  103.   prod[0] =  - q[1] * v[0] - q[2] * v[1] - q[3] * v[2];
  104.   prod[1] = q[0] * v[0] + q[2] * v[2] - q[3] * v[1];
  105.   prod[2] = q[0] * v[1] - q[1] * v[2] + q[3] * v[0];
  106.   prod[3] = q[0] * v[2] + q[1] * v[1] - q[2] * v[0];
  107.   
  108.   result[0] = -prod[0] * q[1] + prod[1] * q[0] - prod[2] * q[3] + prod[3] * q[2];
  109.   result[1] = -prod[0] * q[2] + prod[1] * q[3] + prod[2] * q[0] - prod[3] * q[1];
  110.   result[2] = -prod[0] * q[3] - prod[1] * q[2] + prod[2] * q[1] + prod[3] * q[0];
  111. }

  112. void qproduct(const float* p, const float* q, float* qr) {
  113.   qr[0] = p[0] * q[0] - p[1] * q[1] - p[2] * q[2] - p[3] * q[3];
  114.   qr[1] = p[0] * q[1] + p[1] * q[0] + p[2] * q[3] - p[3] * q[2];
  115.   qr[2] = p[0] * q[2] - p[1] * q[3] + p[2] * q[0] + p[3] * q[1];
  116.   qr[3] = p[0] * q[3] + p[1] * q[2] - p[2] * q[1] + p[3] * q[0];
  117. }

  118. void qnormalized(float* q) {
  119.   float invnorm;
  120.   invnorm = fastinvsqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
  121.   if (invnorm < 100000000) {
  122.     q[0] *= invnorm;
  123.     q[1] *= invnorm;
  124.     q[2] *= invnorm;
  125.     q[3] *= invnorm;
  126.   } else {
  127.     q[0] = 1;
  128.     q[1] = 0;
  129.     q[2] = 0;
  130.     q[3] = 0;
  131.   }
  132. }
  133. float fastinvsqrt(float x) {
  134.   float halfx = 0.5f * x;
  135.   float y = x;
  136.   long i = *(long*)&y;
  137.   i = 0x5f3759df - (i>>1);
  138.   y = *(float*)&i;
  139.   y = y * (1.5f - (halfx * y * y));
  140.   return y;
  141. }

  142. float MatMulVect(float M[], float V[], int8_t result[]){
  143.   for(uint8_t i=0; i<2; i++){
  144.      result[i] = (M[i*4]*V[0]+M[i*4+1]*V[1]+M[i*4+2]*V[2]+M[i*4+3])/-V[2]*128;
  145.   }
  146. }
复制代码


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

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

  1. typedef struct Vertex{
  2.   int8_t location[3];
  3. }MyVertex;
  4. typedef struct Edge{
  5.   int8_t connection[2];
  6. }MyEdge;
  7. typedef struct Object{
  8.   int numv;
  9.   int nume;
  10.   MyVertex* v;
  11.   MyEdge* e;
  12.   float quat[4];
  13.   float offset[3];
  14. }MyObject;
复制代码


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


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

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

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




======================================
新增纸飞机代码如下
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}
};



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

注,为了节约内存,代码做了一些修改并已同步,之前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}
};


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

发表于 2016-2-1 18:34:47 | 显示全部楼层
帅,加个陀螺仪,搞成自动的
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-2-1 20:39:31 | 显示全部楼层
本帖最后由 微风森林 于 2016-2-1 20:42 编辑
麦克小小狼 发表于 2016-2-1 18:34
帅,加个陀螺仪,搞成自动的


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

纸飞机模型如下:

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}
};
MyObject  obj={5, 7, mp, me,{1,0,0,0},{0,0,0}};
回复 支持 反对

使用道具 举报

发表于 2016-2-1 23:39:59 | 显示全部楼层
非常棒,收藏了
回复 支持 反对

使用道具 举报

发表于 2016-2-2 00:00:44 | 显示全部楼层
非常棒,收藏了
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-2-4 19:25:44 | 显示全部楼层
本帖最后由 微风森林 于 2016-2-6 00:12 编辑

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



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

使用道具 举报

发表于 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
编译出错呀小哥,1.6.5。你用的是什么版本的IDE呢。
C:\Users\Test\AppData\Local\Temp\build593811516894 ...


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

第一个代码文件是ino文档,第二个代码文件要存成.h头文件,放入同一个目录
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-2-17 19:21:20 | 显示全部楼层
主程序已更新了透视投影变换的矩阵,大家可以试试

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

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

使用道具 举报

发表于 2016-2-29 23:51:17 | 显示全部楼层
我只能说!你很牛b
回复 支持 反对

使用道具 举报

发表于 2016-4-7 13:48:01 | 显示全部楼层
回复 支持 反对

使用道具 举报

发表于 2016-4-8 13:41:28 | 显示全部楼层
吊暴了,表示支持,加油。
回复 支持 反对

使用道具 举报

发表于 2016-4-11 22:29:34 | 显示全部楼层
谢谢楼主分享~\(≧▽≦)/~啦啦啦
回复 支持 反对

使用道具 举报

发表于 2016-4-17 10:44:00 | 显示全部楼层
楼主好厉害,我想问一下怎么让它转到特定角度呢,而不是只是旋转
回复 支持 反对

使用道具 举报

发表于 2016-4-18 14:26:43 | 显示全部楼层
这个得收藏~~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-3-28 21:38 , Processed in 0.050224 second(s), 28 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表