本帖最后由 微风森林 于 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}
- };
- MyObject obj={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[4]={0.999847695f,0,0.0174524f,0};
- static float qview[4]={0.25881904510252076234889883762405f,0.9659258262890682867497431997289f,0,0};
- static float qtemp[4];
- #define near 1.0
- #define far 100.0
- #define right 1.8
- #define top 1.8
- static float proj[8]={
- 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[0]=x;
- mo.offset[1]=y;
- mo.offset[2]=z;
- }
- void rotateObject(MyObject &mo, float* q) {
- qproduct(q,obj.quat,qtemp);
- mo.quat[0]=qtemp[0];
- mo.quat[1]=qtemp[1];
- mo.quat[2]=qtemp[2];
- mo.quat[3]=qtemp[3];
- qnormalized(qtemp);
- }
- void renderObject(MyObject &mo) {
- MyVertex* mv=new MyVertex[mo.numv];
- qproduct(qview,mo.quat,qtemp);
- for (int i = 0; i < mo.numv; i++) {
- float vtemp[3];
- iqRot(qtemp,mo.v[i].location,vtemp);
- vtemp[0] += mo.offset[0];
- vtemp[1] += mo.offset[1];
- vtemp[2] += mo.offset[2];
- MatMulVect(proj, vtemp, mv[i].location);
- }
-
- for (int i = 0; i < mo.nume; i++) {
- int8_t p1 = mo.e[i].connection[0];
- int8_t p2 = mo.e[i].connection[1];
- oled.drawline(mv[p1].location[0]+64, mv[p1].location[1]+32, mv[p2].location[0]+64, mv[p2].location[1]+32, WHITE);
- }
-
- delete mv;
- }
- float iqRot(float q[],int8_t v[],float result[]){
- float prod[4];
- prod[0] = - q[1] * v[0] - q[2] * v[1] - q[3] * v[2];
- prod[1] = q[0] * v[0] + q[2] * v[2] - q[3] * v[1];
- prod[2] = q[0] * v[1] - q[1] * v[2] + q[3] * v[0];
- prod[3] = q[0] * v[2] + q[1] * v[1] - q[2] * v[0];
-
- result[0] = -prod[0] * q[1] + prod[1] * q[0] - prod[2] * q[3] + prod[3] * q[2];
- result[1] = -prod[0] * q[2] + prod[1] * q[3] + prod[2] * q[0] - prod[3] * q[1];
- result[2] = -prod[0] * q[3] - prod[1] * q[2] + prod[2] * q[1] + prod[3] * q[0];
- }
- void qproduct(const float* p, const float* q, float* qr) {
- qr[0] = p[0] * q[0] - p[1] * q[1] - p[2] * q[2] - p[3] * q[3];
- qr[1] = p[0] * q[1] + p[1] * q[0] + p[2] * q[3] - p[3] * q[2];
- qr[2] = p[0] * q[2] - p[1] * q[3] + p[2] * q[0] + p[3] * q[1];
- qr[3] = p[0] * q[3] + p[1] * q[2] - p[2] * q[1] + p[3] * q[0];
- }
- void qnormalized(float* q) {
- float invnorm;
- invnorm = fastinvsqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
- if (invnorm < 100000000) {
- q[0] *= invnorm;
- q[1] *= invnorm;
- q[2] *= invnorm;
- q[3] *= invnorm;
- } else {
- q[0] = 1;
- q[1] = 0;
- q[2] = 0;
- q[3] = 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[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;
- }
- }
复制代码
这是程序主体文件。SCL连13,SDA连11,RST连10,D/C连9。如果大家的oled还有使能脚CE,请连着12脚,或者直接把使能脚接VCC或GND,都试试看哪个可以点亮
我们还有个自定义的结构体定义,请存为MyObj.h并放入代码同一目录中
- typedef struct Vertex{
- int8_t location[3];
- }MyVertex;
- typedef struct Edge{
- int8_t connection[2];
- }MyEdge;
- typedef struct Object{
- int numv;
- int nume;
- MyVertex* v;
- MyEdge* e;
- float quat[4];
- float offset[3];
- }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}
};
|