Arduino + OLED12864 做一个简单的3d线框渲染引擎
提到3d渲染引擎,大家都会有一种遥不可及的感觉,对吧??其实呢,没那么复杂。比如,我们用arduino就能完成3d引擎的两个步骤之一: vertex操作。(另一个是pixel/fragment操作)
只要空间数学功底好,3d其实就是对顶点的旋转,连接构成面,然后贴图而已。当然贴图用arduino基本上是不可能的。。。
我们的做法是什么呢?先给出3d物体的各个顶点坐标,指定顶点的连接顺序,然后将3d坐标正交投影到2d的屏幕上来,按连接顺序连线
什么?不知道正交投影?呵呵,这个词看似高端,其实就是这么回事啦:丢掉z坐标,只保留x,y,就能跟屏幕坐标系对应了
http://image.geek-workshop.com/forum/201601/31/130945qs1s0h8hhy5q7h0x.jpg
为了抛砖引玉,我们来最简单的一个场景:旋转一个立方体
我们对立方体进行旋转,获得旋转后的坐标,然后丢掉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);
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 = {8, 12, mp, me,{1,0,0,0},{0,0,0}};
void setup() {
Serial.begin(9600);
SPI.begin();
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,64,32,0);
renderObject(obj);
oled.display();
}
static float qdelta={0.999847695f,0,0.0174524f,0};
static float qview={0.99144486f,0.13052619f,0,0};
static float qtemp;
void loop()
{
qproduct(qdelta,obj.quat,qtemp);
rotateObject(obj,qtemp);
oled.clear(); // clears the screen and buffer
renderObject(obj);
oled.display();
delay(10);
}
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) {
mo.quat=q;
mo.quat=q;
mo.quat=q;
mo.quat=q;
}
void renderObject(MyObject &mo) {
MyVertex* mv=new MyVertex;
qproduct(qview,mo.quat,qtemp);
qnormalized(qtemp);
for (int i = 0; i < mo.numv; i++) {
iqRot(qtemp,mo.v.location,mv.location);
mv.location += mo.offset;
mv.location += mo.offset;
mv.location += mo.offset;
}
for (int i = 0; i < mo.nume; i++) {
int p1 = mo.e.connection;
int p2 = mo.e.connection;
oled.drawline(mv.location, mv.location, mv.location, mv.location, WHITE);
}
delete mv;
}
float iqRot(float q[],int v[],int 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;
}
这是程序主体文件。SCL连13,SDA连11,RST连10,D/C连9。如果大家的oled还有使能脚CE,请连着GND或者VCC,都试一下看哪个可以点亮屏幕
我们还有个自定义的结构体定义,请存为MyObj.h并放入代码同一目录中
typedef struct Vertex{
int location;
}MyVertex;
typedef struct Edge{
char 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
炫酷 回头研究研究,好玩! 学习了,正好手里有,我们也会用起来 dsweiliang 发表于 2016-2-1 08:44
炫酷
高手 太多了 Jason_G 发表于 2016-2-1 09:35
回头研究研究,好玩!
缺东西 不然 就跟着做了 hnyzcj 发表于 2016-2-1 12:21
学习了,正好手里有,我们也会用起来
陈老师 真富有哦
页:
[1]