[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
sceneManage.h
tree.h
object.cpp
sceneManage.cpp
tree.cpp
main.cpp
整个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; }
相关文章推荐
- 《Pro Ogre 3D Programming》 读书笔记 之 第五章 场景管理 第一部分 (转)
- 场景管理
- Android 7.1 SystemUI--任务管理--场景一:长按某个缩略图,拖动分屏的流程
- 3D游戏中的场景管理(八叉树和BSP树简介)
- UIFramework之Unity4.x 场景管理策略
- gaia引擎分析(二)场景管理
- 【Unity】四叉树/八叉树管理和动态加载场景物件
- zookeeper 典型应用场景-集群管理
- [图形学] 游戏中的场景管理 - 四叉树 八叉树
- 引擎技术研究之场景管理------四叉树与视椎剔除技术
- 管理场景(基础)
- 场景管理:四叉树算法C++实现
- (转)3D引擎场景管理
- 游戏场景管理
- Nebula3的场景管理
- OpenGL绘制简单场景,实现旋转缩放平移和灯光效果
- OpenGL场景截取后存储(BMP图片)
- 《Pro Ogre 3D Programming》 读书笔记 之 第五章 场景管理 第二部分 (转)
- 在OpenGL中,如果场景坐标用大地坐标,由于float的限制会造成误差
- 基于BSP的场景管理