您的位置:首页 > 运维架构

[OpenGL] 场景管理四叉树demo

2017-05-25 23:48 831 查看
        所谓的四叉树场景管理就是先对场景进行预处理,用四叉树存储所有物体,运行中通过四叉树查询,只渲染当前区域的物体,以提高帧率,减少GPU的负担。

        整个demo,效果已经基本出来了,但在精度上有点问题,可能是因为邻域选的比较小,导致有时候视线里会突然冒出来一个物体。下图中红点代表人物。

       俯视:



        

        透视



包括三个类:

 

        场景管理类

        四叉树类

        物体类

 

物体类包含了物体的性质,目前以正方体代替物体,只包含边长,颜色和位置。实际中扩展的版本应该包含:

 

        顶点坐标

        下标索引

        纹理索引

        颜色数据

        ……

 

 

四叉树类包括以下几个部分:

 

性质:

        最小的地图砖块边长

        四叉树层数

        四叉树头节点

 

功能:

        插入节点(初始化)

        查找节点

 

场景管理包括以下几个部分:

 

场景创建:

 

        设计的四叉树比较简单,创建的方式用的是按层次建树,而不是递归方式,不允许一个物体同时处在两个区域,所以前期需要用特殊的地图编辑器(按理来说应该是一个桌面小软件,编辑好之后可以存成一个四叉树文件)。

        在这里,为了方便,写了一个简单的自动场景生成,详情见函数randBuildTree(),以迭代的方式一层层随机生成方块的位置。

 

数据结构:

 

std::set<Object*> set;//集合

std::list<Object*> list[MAX];//链表

 

        1. 物体的集合,包含了所有当前帧需要渲染的物体。(渲染内容)

        2. 当前位置邻域包含的物体,是一个链表的数组,数组的每个元素对应邻域的一块,每一块对应一个链表,包含了这一块的所有物体。(预备内容)

 

       之所以要把做一个邻域的链表,是因为不同的list查询的结果可能有重复的,所以最终渲染的时候要把物体放到集合中,保证唯一性。

         


    如果邻域选取3x3,那么数组大小为9,编号如上。

 

场景更新

 

           因为四叉树有很多层,不同层对应着不同尺度的物体,我们首先需要定义主角所在的层数,然后以这个层数为基础来搜索邻域。

         每一帧都调用一次更新函数,首先检查位置是否发生变化,可能发生的变化是:向右移动一格,向左移动一格,向上移动一格,向下移动一格。如果发生了,那么执行这样的操作:

 

 

 



 

        (1) 在预备链表中,移除红色区域的物体

        (2) 在预备链表中,加入黄色区域的物体

        (3) 更新显示的集合

         加入和移除是通过【四叉树的查找】来实现的。

         盗用了一下别人的图来显示一下这个过程:

         


代码

object.h

#pragma once
struct vec {
float x;
float y;
float z;
};
class Object
{
public:
float radius;
float center_x;
float center_y;
vec color;
Object(float _radius, float cx, float cy);
void draw();
};


sceneManage.h

#pragma once
#include "tree.h"
#include "object.h"
#include <set>
#include <list>

class sceneManage
{
public:
enum {
MAX = 9,
SIZE = 3,
LAYER = 6,
CURLAYER = 5,
BLOCKSIZE = 1,
LENGTH = 64,
};
private:

bool *isUsed;
quadtree* tree;
std::set<Object*> set;
std::list<Object*> list[MAX];
std::list<Object*> alllist;
int cur_x;
int cur_y;
int n;
int offset;
void removeSet(int i, int j, int k);
void removeSet(int i);
void updateSet(int i, int j, int k);
void updateSet(int i);
void randBuildTree();
void printMap();
public:

std::list<Object*>& getAllObj();
void init(float x, float y);
std::set<Object*>& getObj();
void moveTo(float x, float y);
sceneManage();
};


tree.h

#pragma once
#include <list>
#include "object.h"

struct Box
{
float xmin;
float xmax;
float ymin;
float ymax;
Box();
Box(float _xmin, float _xmax, float _ymin, float _ymax);
};

struct treeNode {
Box box;
treeNode* child[4];
std::list<Object*> List;
bool hasChild;
treeNode();
void setBox(treeNode* parent, int i);
};

class quadtree
{
private:
float blockSize;
int layer;
treeNode* head;
std::list<Object*> allObj;
public:
void print();
std::list<Object*> findAll();
std::list<Object*> find(float x, float y, int layer = -1);
quadtree(float size, int l);
void add(int layer, int x, int y, Object* obj);
};


object.cpp

#include "object.h"
#include <stdlib.h>
#include <stdio.h>
#include "gl/glut.h"

Object::Object(float r, float cx, float cy) :radius(r), center_x(cx), center_y(cy)
{
color.x = (1.0*(rand() % 100) / 100);
color.y = (1.0*(rand() % 100) / 100);
color.z = (1.0*(rand() % 100) / 100);
//printf("radius = %f \n", radius);
}

void Object::draw()
{
glPushMatrix();
glColor3f(color.x, color.y, color.z);
//printf("%f %f %f\n", center_x - radius / 2, radius / 2, center_y - radius / 2);
glTranslatef(center_x - radius / 2, radius / 2, center_y - radius / 2);
glutSolidCube(radius);
glPopMatrix();
}


sceneManage.cpp

#include "sceneManage.h"
#include <fstream>
#include <time.h>

sceneManage::sceneManage()
{
srand(time(nullptr));
n = pow(2.0, LAYER);
offset = 2 * BLOCKSIZE;
tree = new quadtree(BLOCKSIZE, LAYER);
// block大小为1, 层数为6
randBuildTree();
}

void sceneManage::init(float x, float y)
{
list[0] = tree->find(x - offset, y + offset, LAYER);
list[1] = tree->find(x - offset, y, LAYER);
list[2] = tree->find(x - offset, y - offset, LAYER);
list[3] = tree->find(x, y - offset, LAYER);
list[4] = tree->find(x + offset, y - offset, LAYER);
list[5] = tree->find(x + offset, y, LAYER);
list[6] = tree->find(x + offset, y + offset, LAYER);
list[7] = tree->find(x, y + offset, LAYER);
}

void sceneManage::printMap()
{
std::ofstream out("map.txt");
for (int i = 0; i < 64; i++) {
for (int j = 0; j < 64; j++) {
if (isUsed[64 * i + j]) {
out << "X ";
}
else {
out << ". ";
}
}
out << "\n";
}
out << "\n";
}

void sceneManage::randBuildTree()
{
isUsed = new bool[4096];
for (int i = 0; i < 4096; i++) {
isUsed[i] = false;
}
int num[6];
num[0] = 0; // 4
num[1] = 0; // 16
num[2] = 2; // 64
num[3] = 20; // 256
num[4] = 40; // 1024
num[5] = 200; // 4096

int size = 64;
int half = size >> 1;
int nsize = 2;
for (int k = 0; k < 6; k++) {
//printf("size = %d half=%d\n", size,half);
for (int i = 0; i < num[k]; i++) {
int r;
do {
r = rand() % 4096;
} while (isUsed[r]);
int r1 = (r / 64) % nsize;//x
int r2 = (r % 64) % nsize;//y
float h = 1.0 * half / 2;
//printf("h = %.1f\n", h);
//printf("r1 = %f r2 = %f\n", -32 + h + r2 *half, -32 + h + r1 *half);
//printf("h = %.0f r = %.0f cx = %.0f cy = %.0f ", h, half, -h + r2 *half, -h + r1 *half);
Object* obj = new Object(half, -LENGTH / 2 * BLOCKSIZE + h + r2 *half, -LENGTH / 2 * BLOCKSIZE + h + r1 *half);
tree->add(k + 1, r1, r2, obj);

for (int k1 = r1 * half; k1 < (r1 + 1) * half; k1++) {
for (int k2 = r2 * half; k2 < (r2 + 1) * half; k2++) {
isUsed[k1 * 64 + k2] = true;
}
}
}
size = half;
half = size >> 1;
nsize = nsize << 1;
}
//tree->print();
printMap();
}

void sceneManage::removeSet(int i)
{
std::list<Object*>::iterator it;
for (it = list[i].begin(); it != list[i].end(); it++) {
set.erase(*it);
}
}

void sceneManage::removeSet(int i, int j, int k)
{
std::list<Object*>::iterator it;
for (it = list[i].begin(); it != list[i].end(); it++) {
set.erase(*it);
}
for (it = list[j].begin(); it != list[j].end(); it++) {
set.erase(*it);
}
for (it = list[k].begin(); it != list[k].end(); it++) {
set.erase(*it);
}
}

void sceneManage::updateSet(int i)
{
std::list<Object*>::iterator it;
for (it = list[i].begin(); it != list[i].end(); it++) {
set.insert(*it);
}
}

void sceneManage::updateSet(int i, int j, int k)
{
std::list<Object*>::iterator it;
for (it = list[i].begin(); it != list[i].end(); it++) {
set.insert(*it);
}
for (it = list[j].begin(); it != list[j].end(); it++) {
set.insert(*it);
}
for (it = list[k].begin(); it != list[k].end(); it++) {
set.insert(*it);
}
}

std::list<Object*>& sceneManage::getAllObj()
{
return tree->findAll();
}

std::set<Object*>& sceneManage::getObj()
{
return set;
}

void sceneManage::moveTo(float x, float y)
{
x /= BLOCKSIZE;
y /= BLOCKSIZE;
if (x == cur_x && y == cur_y) {
return;
}
if (x == cur_x + 1) {
printf("move right\n");
removeSet(0, 1, 2);
list[0] = tree->find(x - offset, y + offset, CURLAYER);
list[1] = tree->find(x - offset, y, CURLAYER);
list[2] = tree->find(x - offset, y - offset, CURLAYER);
updateSet(0, 1, 2);
}
else if (y == cur_y + 1) {
printf("move front\n");
removeSet(2, 3, 4);
list[2] = tree->find(x - offset, y - offset, CURLAYER);
list[3] = tree->find(x, y - offset, CURLAYER);
list[4] = tree->find(x + offset, y - offset, CURLAYER);
updateSet(2, 3, 4);
}
else if (x == cur_x - 1) {
printf("move left\n");
removeSet(4, 5, 6);
list[4] = tree->find(x + offset, y - offset, CURLAYER);
list[5] = tree->find(x + offset, y, CURLAYER);
list[6] = tree->find(x + offset, y + offset, CURLAYER);
updateSet(4, 5, 6);
}
else if (y == cur_y - 1) {
printf("move back\n");
removeSet(0, 7, 6);
list[0] = tree->find(x - offset, y + offset, CURLAYER);
list[7] = tree->find(x, y + offset, CURLAYER);
list[6] = tree->find(x + offset, y + offset, CURLAYER);
updateSet(0, 7, 6);
}
list[8] = tree->find(x, y, CURLAYER);
removeSet(8);
updateSet(8);
cur_x = x;
cur_y = y;
}


tree.cpp

#include "tree.h"
#include <math.h>
#include <queue>
//#include <QTextStream>
//static QTextStream cout(stdout, QIODevice::WriteOnly);

Box::Box() {}
Box::Box(float _xmin, float _xmax, float _ymin, float _ymax) :
xmin(_xmin), xmax(_xmax), ymin(_ymin), ymax(_ymax)
{

}

treeNode::treeNode()
{
for (int i = 0; i < 4; i++) {
child[i] = nullptr;
}
hasChild = false;
}

void treeNode::setBox(treeNode* parent, int i)
{
switch (i) {
case 0: {
box.xmax = parent->box.xmin + (parent->box.xmax - parent->box.xmin) / 2;
box.xmin = parent->box.xmin;
box.ymax = parent->box.ymax;
box.ymin = parent->box.ymin + (parent->box.ymax - parent->box.ymin) / 2;
break;
}
case 1: {
box.xmax = parent->box.xmin + (parent->box.xmax - parent->box.xmin) / 2;
box.xmin = parent->box.xmin;
box.ymax = parent->box.ymin + (parent->box.ymax - parent->box.ymin) / 2;
box.ymin = parent->box.ymin;
break;
}
case 2: {
box.xmax = parent->box.xmax;
box.xmin = parent->box.xmin + (parent->box.xmax - parent->box.xmin) / 2;
box.ymax = parent->box.ymin + (parent->box.ymax - parent->box.ymin) / 2;
box.ymin = parent->box.ymin;
break;
}
case 3: {
box.xmax = parent->box.xmax;
box.xmin = parent->box.xmin + (parent->box.xmax - parent->box.xmin) / 2;
box.ymax = parent->box.ymax;
box.ymin = parent->box.ymin + (parent->box.ymax - parent->box.ymin) / 2;
break;
}
}
}

std::list<Object*> quadtree::findAll()
{
return allObj;
}

quadtree::quadtree(float size, int l)
{
int n = pow(2, l - 1);
blockSize = size;
layer = l;
head = new treeNode();
head->box.xmin = -n * size;
head->box.xmax = n * size;
head->box.ymin = -n * size;
head->box.ymax = n * size;
}

std::list<Object*> quadtree::find(float x, float y, int layer)
{
treeNode* node = head;
treeNode* parent = nullptr;
int count = 0;
while (node != nullptr) {
//	printf("count = %d\n", count);
if (count == layer) {
break;
}
int index;
//	printf("%f %f\n", node->box.xmax - node->box.xmin, node->box.ymax - node->box.ymin);
int i = 2 * (x + head->box.xmax) / (node->box.xmax - node->box.xmin);
i = i % 2;
int j = 2 * (y + head->box.ymax) / (node->box.ymax - node->box.ymin);
j = j % 2;
if (i == 0 && j == 0) {
index = 1;
}
else if (i == 0 && j == 1) {
index = 0;
}
else if (i == 1 && j == 0) {
index = 2;
}
else if (i == 1 && j == 1) {
index = 3;
}
parent = node;
node = node->child[index];
//	printf("index = %d\n", index);
count++;
}
return parent->List;
}

void quadtree::add(int layer, int x, int y, Object* obj)
{
allObj.push_back(obj);
int n = pow(2, layer);
int *index = new int[layer];
int half = n >> 1;
for (int i = layer - 1; i >= 0; i--) {
if (x % 2 == 1) {
if (y % 2 == 1) {
index[i] = 2;
}
else {
index[i] = 1;
}
}
else {
if (y % 2 == 1) {
index[i] = 3;
}
else {
index[i] = 0;
}
}
x >>= 1;
y >>= 1;
}
treeNode* node = head;
for (int i = 0; i < layer - 1; i++) {
if (!node->child[index[i]]) {
node->child[index[i]] = new treeNode();
node->child[index[i]]->setBox(node, index[i]);
}
if (node != head)node->List.push_back(obj);
node->hasChild = true;
node = node->child[index[i]];
}
treeNode* childNode = new treeNode();
childNode->List.push_back(obj);
if (node != head)node->List.push_back(obj);
node->child[index[layer - 1]] = childNode;
childNode->setBox(node, index[layer - 1]);
node->hasChild = true;
}

void quadtree::print()
{
treeNode* tmp;
std::queue<treeNode*>q;
q.push(head);
int cur = 1;
int curnum = 1;
int nextnum = 0;
while (!q.empty()) {
tmp = q.front();
q.pop();
if (tmp->hasChild) {
curnum--;
//cout << "[";
printf("[");
for (int i = 0; i < 4; i++) {
if (tmp->child[i]) {
std::list<Object*>::iterator it;
for (it = tmp->child[i]->List.begin(); it != tmp->child[i]->List.end(); it++) {
printf("{%.0f %.0f %.0f}", (*it)->radius, (*it)->center_x, (*it)->center_y);
}
q.push(tmp->child[i]);
nextnum++;
}
else printf("_");
if (i != 3)printf(",");

}
printf("]");
}
if (curnum == 0) {
printf("\n");
cur++;
curnum = nextnum;
nextnum = 0;
}
}
}


main.cpp

#define _CRT_SECURE_NO_WARNINGS
//simd
#include <stdlib.h>
#include <time.h>
#include "sceneManage.h"
#include"gl/glut.h"

float cx = 0, cz = 0;
float center[] = { 0, 0, 0 };
float eye[] = { 0, 100,3};
float tx, ty = 0, ax, ay = 0, mx, my, zoom = 0;
bool isLine = false;
bool isDown = false;
bool isDrawAll = false;
sceneManage* scene;
float step = 1.0f;

void getFPS()
{
static int frame = 0, time, timebase = 0;
static char buffer[256]; //字符串缓冲区

frame++;
time = glutGet(GLUT_ELAPSED_TIME);
//返回两次调用glutGet(GLUT_ELAPSED_TIME)的时间间隔,单位为毫秒
if (time - timebase > 1000) { //时间间隔差大于1000ms时
sprintf(buffer, "FPS:%4.2f\n position(%.2f,%.2f)",
frame*1000.0 / (time - timebase),cx,cz); //写入buffer中
//printf("%f\n", frame*1000.0 / (time - timebase));
timebase = time; //上一次的时间间隔
frame = 0;
}

char *c;
glColor3f(0,0,0);
glDisable(GL_DEPTH_TEST);     // 禁止深度测试
glMatrixMode(GL_PROJECTION);  // 选择投影矩阵
glPushMatrix();               // 保存原矩阵
glLoadIdentity();             // 装入单位矩阵
glOrtho(0, 480, 0, 480, -1, 1);    // 位置正投影
glMatrixMode(GL_MODELVIEW);   // 选择Modelview矩阵
glPushMatrix();               // 保存原矩阵
glLoadIdentity();             // 装入单位矩阵
glRasterPos2f(10, 10);
for (c = buffer; *c != '\0'; c++) {
glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *c); //绘制字符
}
glMatrixMode(GL_PROJECTION);  // 选择投影矩阵
glPopMatrix();                // 重置为原保存矩阵
glMatrixMode(GL_MODELVIEW);   // 选择Modelview矩阵
glPopMatrix();                // 重置为原保存矩阵
glEnable(GL_DEPTH_TEST);      // 开启深度测试
}

void reshape(int width, int height)
{
if (height == 0) height = 1;
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
float whRatio = (GLfloat)width / (GLfloat)height;
gluPerspective(45, whRatio, 1, 500);
glMatrixMode(GL_MODELVIEW);
}

void idle()
{
glutPostRedisplay();
}

void init(void)
{
//glClearColor(1.0, 0.0, 0.0, 0.0);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glColor4f(1.0, 1.0, 1.0, 1.0f);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

scene = new sceneManage();
scene->init(cx, -cz);
}

void redraw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1, 1, 1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], 0, 1, 0);

if (isLine)glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glPushMatrix();
glColor3f(0.3f, 0, 0);
glTranslatef(cx, 0.1f, cz);
glutSolidSphere(0.3f, 20, 10);
glPopMatrix();

/*
glPushMatrix();
glTranslatef(cx, 0.1, cz);
glColor3f(1,0, 0);
glRotatef(90, 1, 0, 0);
glRectd(-4, -4, 4, 4);
glPopMatrix();*/

glPushMatrix();
glColor3f(0.2, 0.2, 0.2);
glRotatef(90, 1, 0, 0);
for (int i = 0; i < 64; i++){
for (int j = 0; j < 64; j++){
glPushMatrix();
glTranslatef(-scene->LENGTH / 2 * scene->BLOCKSIZE + i*scene->BLOCKSIZE, -scene->LENGTH / 2 * scene->BLOCKSIZE + j*scene->BLOCKSIZE,0);
glRectd(0, 0,scene->BLOCKSIZE, scene->BLOCKSIZE);
glPopMatrix();
}
}
//glRectd(-scene->LENGTH / 2 * scene->BLOCKSIZE, -scene->LENGTH / 2 * scene->BLOCKSIZE, scene->LENGTH / 2 * scene->BLOCKSIZE, scene->LENGTH / 2 * scene->BLOCKSIZE);
glPopMatrix();

if (isDrawAll) {
std::list<Object*>::iterator it;
std::list<Object*> list = scene->getAllObj();
printf("size = %d\n", list.size());
for (it = list.begin(); it != list.end(); it++) {
(*it)->draw();
}
}
else {
scene->moveTo(cx, -cz);
glColor3f(0, 0, 0);
glPushMatrix();
//glutSolidCube(3);
std::set<Object*> set = scene->getObj();
std::set<Object*>::iterator it;
int count = 0;
for (it = set.begin(); it != set.end(); it++) {
count++;
(*it)->draw();
}
//if(count!=0)printf("count = %d\n",count);
glPopMatrix();
}
getFPS();
glutSwapBuffers();
}

void myMouse(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON) {
if (state == GLUT_DOWN) {
isDown = true;
mx = x;
my = y;
}
else if (state == GLUT_UP) {
isDown = false;
}
}
glutPostRedisplay();
}

void mouseMotion(int x, int y)
{
if (isDown) {
ax += 1.0f*(y - my) / 10.0f;
ay += 1.0f*(x - mx) / 10.0f;
mx = x;
my = y;
}
glutPostRedisplay();
}

void myKeyboard(unsigned char key, int x, int y)
{
//printf("%d  \n",key);
switch (key) {
case 'a': { //左移
cx -= step;
eye[0] -= step;
center[0] -= step;
if (cx < -31) {
cx = -31;
eye[0] = -31;
center[0] = -31;
}
break;
}
case 'd': { //右移
cx += step;
eye[0] += step;
center[0] += step;
if (cx > 31) {
cx = 31;
eye[0] = 31;
center[0] = 31;
}
break;
}
case 'w': { //上移
cz -= step;
eye[2] -= step;
center[2] -= step;
if (cz < -31) {
cz = -31;
eye[2] = -31;
center[2] = -31;
}
break;
}
case 's': { //下移
cz += step;
eye[2] += step;
center[2] += step;
if (cz > 31) {
cz = 31;
eye[2] = 31;
center[2] = 31;
}
break;
}
case 'z': { //后移
zoom += 1;
break;
}
case 'c': { //前移
zoom -= 1;
break;
}
case 'p': {
// 切换绘制模式
if (isLine) {
isLine = false;
}
else isLine = true;
break;
}
case 'm': {
if (isDrawAll) {
isDrawAll = false;
}
else isDrawAll = true;
}
}
//glutPostRedisplay();
}

int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
glutInitWindowSize(800, 600);
int windowHandle = glutCreateWindow("Simple GLUT App");
glutDisplayFunc(redraw);
glutReshapeFunc(reshape);
glutMouseFunc(myMouse);
glutMotionFunc(mouseMotion);
glutKeyboardFunc(myKeyboard);
glutIdleFunc(idle);
init();
glutMainLoop();
//system("pause");
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: