Arduino + OLED12864 做一个简单的3d线框渲染引擎

2016-02-012.1万
AI 快速预览详细 收起
本文介绍了如何使用Arduino和OLED12864屏幕创建一个简单的3D线框渲染引擎。通过顶点操作和正交投影,可以显示旋转的立方体或其他自定义3D物体。文章提供了详细的代码示例和步骤说明,适合初学者和STEM教育场景。
提到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);
  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 = {8, 12, mp, me,{1,0,0,0},{0,0,0}};
  32. void setup()   {
  33.   Serial.begin(9600);
  34.   SPI.begin();
  35.   oled.ssd1306_init(SSD1306_SWITCHCAPVCC);
  36.   oled.display(); // show splashscreen
  37.   delay(1000);
  38.   oled.ssd1306_command(SSD1306_INVERTDISPLAY);  //反色显示,注释掉则为黑底白图
  39.   oled.clear();   // clears the screen and buffer
  40.   moveObject(obj,64,32,0);
  41.   renderObject(obj);
  42.   oled.display();
  43. }
  44. static float qdelta[4]={0.999847695f,0,0.0174524f,0};
  45. static float qview[4]={0.99144486f,0.13052619f,0,0};
  46. static float qtemp[4];
  47. void loop()
  48. {
  49.   qproduct(qdelta,obj.quat,qtemp);
  50.   rotateObject(obj,qtemp);
  51.   oled.clear();   // clears the screen and buffer
  52.   renderObject(obj);
  53.   oled.display();
  54.   delay(10);
  55. }
  56. void moveObject(MyObject &mo, float x, float y, float z) {
  57.   mo.offset[0]=x;
  58.   mo.offset[1]=y;
  59.   mo.offset[2]=z;
  60. }
  61. void rotateObject(MyObject &mo, float* q) {
  62.   mo.quat[0]=q[0];
  63.   mo.quat[1]=q[1];
  64.   mo.quat[2]=q[2];
  65.   mo.quat[3]=q[3];
  66. }
  67. void renderObject(MyObject &mo) {
  68.   MyVertex* mv=new MyVertex[mo.numv];
  69.   qproduct(qview,mo.quat,qtemp);
  70.   qnormalized(qtemp);
  71.   for (int i = 0; i < mo.numv; i++) {
  72.     iqRot(qtemp,mo.v[i].location,mv[i].location);
  73.     mv[i].location[0] += mo.offset[0];
  74.     mv[i].location[1] += mo.offset[1];
  75.     mv[i].location[2] += mo.offset[2];
  76.   }
  77.   for (int i = 0; i < mo.nume; i++) {
  78.     int p1 = mo.e[i].connection[0];
  79.     int p2 = mo.e[i].connection[1];
  80.     oled.drawline(mv[p1].location[0], mv[p1].location[1], mv[p2].location[0], mv[p2].location[1], WHITE);
  81.   }
  82.   delete mv;
  83. }
  84. float iqRot(float q[],int v[],int result[]){
  85.   float prod[4];
  86.   prod[0] =  - q[1] * v[0] - q[2] * v[1] - q[3] * v[2];
  87.   prod[1] = q[0] * v[0] + q[2] * v[2] - q[3] * v[1];
  88.   prod[2] = q[0] * v[1] - q[1] * v[2] + q[3] * v[0];
  89.   prod[3] = q[0] * v[2] + q[1] * v[1] - q[2] * v[0];
  90.   result[0] = -prod[0] * q[1] + prod[1] * q[0] - prod[2] * q[3] + prod[3] * q[2];
  91.   result[1] = -prod[0] * q[2] + prod[1] * q[3] + prod[2] * q[0] - prod[3] * q[1];
  92.   result[2] = -prod[0] * q[3] - prod[1] * q[2] + prod[2] * q[1] + prod[3] * q[0];
  93. }
  94. void qproduct(const float* p, const float* q, float* qr) {
  95.   qr[0] = p[0] * q[0] - p[1] * q[1] - p[2] * q[2] - p[3] * q[3];
  96.   qr[1] = p[0] * q[1] + p[1] * q[0] + p[2] * q[3] - p[3] * q[2];
  97.   qr[2] = p[0] * q[2] - p[1] * q[3] + p[2] * q[0] + p[3] * q[1];
  98.   qr[3] = p[0] * q[3] + p[1] * q[2] - p[2] * q[1] + p[3] * q[0];
  99. }
  100. void qnormalized(float* q) {
  101.   float invnorm;
  102.   invnorm = fastinvsqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
  103.   if (invnorm < 100000000) {
  104.     q[0] *= invnorm;
  105.     q[1] *= invnorm;
  106.     q[2] *= invnorm;
  107.     q[3] *= invnorm;
  108.   } else {
  109.     q[0] = 1;
  110.     q[1] = 0;
  111.     q[2] = 0;
  112.     q[3] = 0;
  113.   }
  114. }
  115. float fastinvsqrt(float x) {
  116.   float halfx = 0.5f * x;
  117.   float y = x;
  118.   long i = *(long*)&y;
  119.   i = 0x5f3759df - (i>>1);
  120.   y = *(float*)&i;
  121.   y = y * (1.5f - (halfx * y * y));
  122.   return y;
  123. }
复制代码

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

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



  1. typedef struct Vertex{
  2.   int location[3];
  3. }MyVertex;
  4. typedef struct Edge{
  5.   char 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目录中


下载附件SSD1306.rar



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

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

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




创作许可协议

本项目采用 None(不开放任何权利,保留所有权利) 进行许可。

评论(3)
hnyzcj
hnyzcj
学习了,正好手里有,我们也会用起来
qq815076543322
qq815076543322
作者
回复hnyzcj
陈老师 真富有哦
qq865650214158
qq865650214158
回头研究研究,好玩!
qq815076543322
qq815076543322
作者
回复qq865650214158
缺东西 不然 就跟着做了
dsweiliang
dsweiliang
炫酷
qq815076543322
qq815076543322
作者
回复dsweiliang
高手 太多了
- 没有更多了 -