From e644c15e07979418c9b8ba1efa29fd9aa4f0b5cf Mon Sep 17 00:00:00 2001 From: Allanis Date: Mon, 19 Feb 2018 22:03:17 +0000 Subject: [PATCH] [Add] Bounding interval hierarchy tree. --- configure.ac | 2 +- src/Makefile.am | 6 +- src/collider/Makefile.am | 5 + src/collider/geom_tree.cpp | 417 +++++++++++++++++++++++++++++++++++++ src/collider/geom_tree.h | 38 ++++ src/sbre_viewer.cpp | 96 +++++++++ 6 files changed, 560 insertions(+), 4 deletions(-) create mode 100644 src/collider/Makefile.am create mode 100644 src/collider/geom_tree.cpp create mode 100644 src/collider/geom_tree.h diff --git a/configure.ac b/configure.ac index 07494ad..90ac1fa 100644 --- a/configure.ac +++ b/configure.ac @@ -32,5 +32,5 @@ LIBS="$LIBS $SDL_LIBS -lSDL_image" CXXFLAGS="$CFLAGS" -AC_CONFIG_FILES([Makefile src/Makefile src/sbre/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile src/sbre/Makefile src/collider/Makefile]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 6b9094c..bbae6ab 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ ## Process this file with automake to produce Makefile.in -SUBDIRS = sbre/ +SUBDIRS = sbre/ collider/ bin_PROGRAMS = Lephisto3D ModelViewer noinst_LIBRARIES = libgui.a @@ -21,9 +21,9 @@ Lephisto3D_SOURCES = main.cpp glfreetype.cpp body.cpp space.cpp ship.cpp player. star_system.cpp sector.cpp system_info_view.cpp generic_system_view.cpp date.cpp space_station.cpp \ space_station_view.cpp model_body.cpp ship_type.cpp info_view.cpp model_coll_mesh_data.cpp \ object_viewer_view.cpp custom_starsystems.cpp serializer.cpp -Lephisto3D_LDADD = sbre/libsbre.a libgui.a +Lephisto3D_LDADD = sbre/libsbre.a collider/libcollider.a libgui.a ModelViewer_SOURCES = sbre_viewer.cpp glfreetype.cpp -ModelViewer_LDADD = sbre/libsbre.a libgui.a +ModelViewer_LDADD = sbre/libsbre.a collider/libcollider.a libgui.a diff --git a/src/collider/Makefile.am b/src/collider/Makefile.am new file mode 100644 index 0000000..1435d45 --- /dev/null +++ b/src/collider/Makefile.am @@ -0,0 +1,5 @@ +noinst_LIBRARIES = libcollider.a +libcollider_a_SOURCES = geom_tree.cpp + +include_HEADERS = geom_tree.h + diff --git a/src/collider/geom_tree.cpp b/src/collider/geom_tree.cpp new file mode 100644 index 0000000..d039c35 --- /dev/null +++ b/src/collider/geom_tree.cpp @@ -0,0 +1,417 @@ +/* + * Bounding interval hierarchy tree building algorithm. + * + * These things get used in interactive raytracers. They are nice because: + * n log n builders are easy to write. + * memory requirment is more predictable than kd-trees. + * You don't need a 'mailbox' as objects only appear once in the hierarchy. + * single ray traversal performance is comparable to kd-tree and way faster than bvh. + */ +#define MAX_LEAF_PRIMS 2 +#define MAX_DEPTH 20 +#define MAX_SPLITPOS_RETRIES 32 + +#include +#include +#include +#include +#include "../aabb.h" +#include "geom_tree.h" + +#define DEBUG +#define MIN(a,b) ((a)<(b) ? (a) : (b)) +#define MAX(a,b) ((a)>(b) ? (a) : (b)) + +class DisplayList; + +struct tri_t { + int triIdx; + tri_t* next; + tri_t* GetNext(void) { return next; } +}; + +class BIHNode { +public: + BIHNode(void) { m_isleaf = 1; m_axis = 0; m_list = 0; m_left = 0; } + void Add(tri_t* tri) { + tri->next = GetList(); + SetList(tri); + } + + float FindNiceSplitPos(int splitAxis, int* numPoints, int* numPrims); + void Dump(int depth, Aabb& box); + void SetAxis(int axis) { m_axis = axis; } + int GetAxis(void) const { return m_axis; } + void SetSplitPos1(float p) { splitPos1 = p; } + void SetSplitPos2(float p) { splitPos2 = p; } + float GetSplitPos1(void) { return splitPos1; } + float GetSplitPos2(void) { return splitPos2; } + void AllocChild(GeomTree* geomTree); + void SetLeaf(bool isLeaf) { m_isleaf = isLeaf; } + bool IsLeaf(void) { return m_isleaf; } + tri_t* GetList(void) { return m_list; } + BIHNode* GetLeft(void) { return m_left; } + BIHNode* GetRight(void) { return GetLeft() + 1; } + void SetLeft(BIHNode* left) { m_left = left; } + void SetList(tri_t* t) { m_list = t; } + +private: + short m_axis; + short m_isleaf; + union { + BIHNode* m_left; + tri_t* m_list; + }; + float splitPos1, splitPos2; +}; + +void BIHNode::AllocChild(GeomTree* geomTree) { + m_left = geomTree->AllocNode(); + geomTree->AllocNode(); + /* Right child is implicitly lefet+1. */ +} + +BIHNode* GeomTree::AllocNode(void) { + assert(m_nodesAllocPos+1 < m_nodesAllocSize); + return &m_nodes[m_nodesAllocPos++]; +} + +GeomTree::~GeomTree(void) { + delete [] m_nodes; + delete [] m_triAlloc; +} + +GeomTree::GeomTree(int numTris, float* vertices, int* indices) { + m_vertices = vertices; + m_indices = indices; + m_aabb.min = vector3d(FLT_MAX, FLT_MAX, FLT_MAX); + m_aabb.max = vector3d(FLT_MAX, FLT_MAX, FLT_MAX); + + m_triAlloc = new tri_t[numTris]; + for(int i = 0; i < numTris; i++) { + m_aabb.Update(vector3d(vertices[3*i], vertices[3*i+1], vertices[3*i+2])); + m_triAlloc[i].triIdx = 3*i; + m_triAlloc[i].next = m_triAlloc+i+1; + } + m_triAlloc[numTris-1].next = 0; + + printf("Building BIHTree of %d triangles\n", numTris); + printf("Aabb: %f,%f,%f -> %f,%f,%f\n", + m_aabb.min.x, + m_aabb.min.y, + m_aabb.min.z, + m_aabb.max.x, + m_aabb.max.y, + m_aabb.max.z); + m_nodes = new BIHNode[numTris*2]; + m_nodesAllocSize = numTris*2; + m_nodesAllocPos = 0; + m_triAllocPos = 0; + + BIHNode* root = AllocNode(); + root->SetList(m_triAlloc); + + Aabb splitBox = m_aabb; + BihTreeGhBuild(root, m_aabb, splitBox, 0, numTris); +} + +void GeomTree::BihTreeGhBuild(BIHNode* a_node, Aabb& a_box, Aabb& a_splitBox, int a_depth, int a_prims) { + tri_t** prim_lump = (tri_t**)alloca(sizeof(tri_t*)*a_prims); + int num = 0; + + for(tri_t* kdprim = a_node->GetList(); kdprim != NULL; kdprim = kdprim->GetNext()) { + prim_lump[num++] = kdprim; + } + + /* Simple master split pos picking for the moment. */ + float splitPos; + float splitPos1; + float splitPos2; + + a_node->SetLeaf(false); + a_node->AllocChild(this); + BIHNode* left = a_node->GetLeft(); + BIHNode* right = a_node->GetRight(); + + int s1count, s2count, splitAxis, attempt; + attempt = 0; + + for(;;) { + splitAxis = 0; + vector3d splitBoxSize = a_splitBox.max - a_splitBox.min; + if(splitBoxSize.y > splitBoxSize.x) splitAxis = 1; + if((splitBoxSize.z > splitBoxSize.y) && (splitBoxSize.z > splitBoxSize.x)) splitAxis = 2; + + /* Split pos in middle of a_splitBox. */ + splitPos = 0.5f * (a_splitBox.min[splitAxis] + a_splitBox.max[splitAxis]); + + //printf("\n%d: %f ", attemt, splitPos); + splitPos1 = a_box.min[splitAxis]; + splitPos2 = a_box.max[splitAxis]; + + s1count = 0, s2count = 0; + float fooSum = 0.0f; + + left->SetList(0); + right->SetList(0); + + for(int i = 0; i < a_prims; i++) { + const int v0 = m_indices[prim_lump[i]->triIdx]; + const int v1 = m_indices[prim_lump[i]->triIdx+1]; + const int v2 = m_indices[prim_lump[i]->triIdx+2]; + + float p0, p1, p2; + float mid; + + p0 = m_vertices[3*v0 + splitAxis]; + p1 = m_vertices[3*v1 + splitAxis]; + p2 = m_vertices[3*v2 + splitAxis]; + + mid = (p0 + p1 + p2)*0.3333333333333333333f; + + float p_min, p_max; + + p_min = MIN(p0, MIN(p1, p2)); + p_max = MAX(p0, MAX(p1, p2)); + + tri_t* foo = prim_lump[i]; + + fooSum += mid; + + if(mid < splitPos) { + left->Add(foo); + s1count++; + if(p_max > splitPos1) splitPos1 = p_max; + } else { + right->Add(foo); + s2count++; + if(p_min < splitPos2) splitPos2 = p_min; + } + } + if(s1count == a_prims) { + /* + * If one side takes up the whole darn parent aabb then + * just give up trying to split. + */ + if(splitPos1 >= a_box.max[splitAxis]) { + if(attempt < MAX_SPLITPOS_RETRIES) { + /* Try splitting at average point. */ + //printf("YES!!! %d, %f\n", attempt, splitPos); + a_splitBox.max = splitPos; + attempt++; + continue; + } + + printf("Warning: Fat node with %d primitives\n", a_prims); + + a_node->SetLeaf(true); + a_node->SetList(left->GetList()); + return; + } + } else if(s2count == a_prims) { + if(splitPos2 <= a_box.min[splitAxis]) { + if(attempt < MAX_SPLITPOS_RETRIES) { + /* Try splitting at average point. */ + //printf("YES!!! %d, %f\n", attempt, splitPos); + a_splitBox.min[splitAxis] = splitPos; + attempt++; + continue; + } + printf("Warning: Fat node with %d primitives\n", a_prims); + + a_node->SetLeaf(true); + a_node->SetList(right->GetList()); + return; + } + } + break; + } + //printf(prims total %d, left %d, right %d\n", a_prims, s1count, s2count); + + a_node->SetLeaf(false); + a_node->SetAxis(splitAxis); + + Aabb b1, b2; + + b1 = a_box; + b1.max[splitAxis] = splitPos1; + b2 = a_box; + b2.min[splitAxis] = splitPos2; + + a_node->SetSplitPos1(splitPos1); + a_node->SetSplitPos2(splitPos2); + + if(a_depth > MAX_DEPTH) return; + + if(s1count > MAX_LEAF_PRIMS) { + Aabb splitBox = a_splitBox; + splitBox.max[splitAxis] = splitPos; + BihTreeGhBuild(left, b1, splitBox, a_depth+1, s1count); + } + else left->SetLeaf(true); + + if(s2count > MAX_LEAF_PRIMS) { + Aabb splitBox = a_splitBox; + splitBox.min[splitAxis] = splitPos; + BihTreeGhBuild(right, b2, splitBox, a_depth+1, s2count); + } + else right->SetLeaf(true); +} + +#define SIGN_OF(f) (*((unsigned int*)(&f)) >> 31) + +void GeomTree::TraceRay(vector3f& start, vector3f& dir, isect_t* isect) { + float len = dir.Length(); + isect->dist = len; + isect->triIdx = -1; + vector3f normDir = dir*(1.0f/len); + TraverseRay(start, normDir, isect); +} + +void GeomTree::TraverseRay(vector3f& a_origin, vector3f& a_dir, isect_t* isect) { + float entry_t = 0, exit_t = isect->dist; + vector3f rcpD = vector3f(1.0f/a_dir.x, 1.0f/a_dir.y, 1.0f/a_dir.z); + int Dneg[3]; +#ifdef DEBUG + int num_raytri_tests = 0; +#endif + + Dneg[0] = (a_dir.x < 0 ? 1 : 0); + Dneg[1] = (a_dir.y < 0 ? 1 : 0); + Dneg[2] = (a_dir.z < 0 ? 1 : 0); + + for(int i = 0; i < 3; i++) { + if(Dneg[i]) { + if(a_origin[i] < m_aabb.min[i]) return; + } + else if(a_origin[i] > m_aabb.max[i]) return; + } + + /* Clip ray segment to box. */ + for(int i = 0; i < 3; i++) { + float clip_min = (m_aabb.min[i] - a_origin[i]) * rcpD[i]; + float clip_max = (m_aabb.max[i] - a_origin[i]) * rcpD[i]; + if(a_dir[i] > 0.0f) { + entry_t = MAX(entry_t, clip_min); + exit_t = MIN(exit_t, clip_max); + } else { + entry_t = MAX(entry_t, clip_max); + exit_t = MIN(exit_t, clip_min); + } + if(entry_t > exit_t) return; + } + +#if 0 + /* From final kd-tree version. */ + /* Init stack. */ + int entrypoint = 0, exitpoint = 1; + /* Init traversal. */ + KDNode* farchild, *currnode; + currnode = obj.m_dlist->sceneTree; + m_Stack[entrypoint].t = entry_t; + m_Stack[entrypoint].pb = 0 + D * entry_t; + m_Stack[exitpoint].t = exit_t; + m_Stack[exitpoint].pb = 0 + D * exit_t; + m_Stack[exitpoint].node = 0; +#endif + + /* Init stack. */ + int stackpos = -1; + /* Init traversal. */ + BIHNode* currnode = &m_nodes[0]; + + struct bihstack { + BIHNode* node; + float entry_t, exit_t; + } bihstack[32]; + + /* Traverse bih-tree. */ + while(currnode) { + while (!currnode->IsLeaf()) { +#ifdef DEBUG + //m_stats.treeNodeTraversals++; +#endif + const int axis = currnode->GetAxis(); + + float d[2]; + d[0] = (currnode->GetSplitPos1() - a_origin[axis]) * rcpD[axis]; + d[1] = (currnode->GetSplitPos2() - a_origin[axis]) * rcpD[axis]; + + const int dir = Dneg[axis]; + const int dir1 = 1-Dneg[axis]; + const float d1 = d[dir]; + const float d2 = d[dir1]; + + if(d1 >= entry_t) { + /* Front side. */ + if(d2 >= exit_t) { + /* And not backside. */ + currnode = currnode->GetLeft()+dir; + exit_t = MIN(d1, exit_t); + continue; + } + /* Both. */ + stackpos++; + bihstack[stackpos].node = currnode->GetLeft()+dir1; + bihstack[stackpos].entry_t = MAX(d2, entry_t); + bihstack[stackpos].exit_t = exit_t; + + currnode = currnode->GetLeft()+dir; + exit_t = MIN(d1, exit_t); + } else if(d2 < exit_t) { + /* Back side only. */ + currnode = currnode->GetLeft() + dir1; + entry_t = MAX(d2, entry_t); + } else { + goto pop_bstack; + } + } + /* Early termination. */ + if(isect->dist < entry_t) goto pop_bstack; + + /* Woop, we are a leaf node. */ + for(tri_t* p = currnode->GetList(); p != NULL; p = p->next) { +#ifdef DEBUG + num_raytri_tests++; +#endif + RayTriIntersect(a_origin, a_dir, p->triIdx, isect); + } + +pop_bstack: + if(stackpos < 0) break; + + currnode = bihstack[stackpos].node; + entry_t = bihstack[stackpos].entry_t; + exit_t = bihstack[stackpos].exit_t; + stackpos--; + } +} + +#define EPSILON 0.00001f +void GeomTree::RayTriIntersect(vector3f& origin, vector3f& dir, int triIdx, isect_t* isect) { + vector3f a(&m_vertices[3*m_indices[triIdx]]); + vector3f b(&m_vertices[3*m_indices[triIdx+1]]); + vector3f c(&m_vertices[3*m_indices[triIdx+2]]); + + vector3f v0_cross, v1_cross, v2_cross; + const vector3f n = vector3f::Cross(c-a, b-a); + + v1_cross = vector3f::Cross(b-origin, a-origin); + v2_cross = vector3f::Cross(a-origin, c-origin); + v0_cross = vector3f::Cross(c-origin, b-origin); + float nominator = vector3f::Dot(n, (a-origin)); + + const float v0d = vector3f::Dot(v0_cross,dir); + const float v1d = vector3f::Dot(v1_cross,dir); + const float v2d = vector3f::Dot(v2_cross,dir); + + if(((v0d > 0) && (v1d > 0) && (v2d > 0)) || + ((v0d < 0) && (v1d < 0 && v2d < 0))) { + const float dist = nominator / vector3f::Dot(dir, n); + if((dist > EPSILON) && (dist < isect->dist)) { + isect->dist = dist; + isect->triIdx = triIdx; + } + } +} + diff --git a/src/collider/geom_tree.h b/src/collider/geom_tree.h new file mode 100644 index 0000000..29e1b01 --- /dev/null +++ b/src/collider/geom_tree.h @@ -0,0 +1,38 @@ +#pragma once +#include "../aabb.h" + +class BIHNode; +class tri_t; + +struct isect_t { + /* triIdx = -1 if no intersection. */ + int triIdx; + float dist; +}; + +class GeomTree { +public: + GeomTree(int numTris, float* vertices, int* indices); + ~GeomTree(void); + Aabb GetAabb(void) { return m_aabb; } + void TraceRay(vector3f& start, vector3f& dir, isect_t* isect); + +private: + friend class BIHNode; + void RayTriIntersect(vector3f& a_origin, vector3f& a_dir, int triIdx, isect_t* isect); + void TraverseRay(vector3f& a_origin, vector3f& a_dir, isect_t* isect); + BIHNode* AllocNode(void); + void BihTreeGhBuild(BIHNode* a_node, Aabb& a_box, Aabb& a_splitBox, int a_depth, int a_prims); + + Aabb m_aabb; + BIHNode* m_nodes; + int m_nodesAllocPos; + int m_nodesAllocSize; + tri_t* m_triAlloc; + int m_triAllocPos; + int m_triAllocSize; + + const float* m_vertices; + const int* m_indices; +}; + diff --git a/src/sbre_viewer.cpp b/src/sbre_viewer.cpp index b9b31fb..0627767 100644 --- a/src/sbre_viewer.cpp +++ b/src/sbre_viewer.cpp @@ -2,6 +2,7 @@ #include "sbre/sbre.h" #include "glfreetype.h" #include "gui.h" +#include "collider/geom_tree.h" static SDL_Surface* g_screen; static int g_width, g_height; @@ -11,6 +12,7 @@ static int g_mouseButton[5]; static int g_model = 0; /* sbre model number. Set with argc. */ static float g_zbias; +static GLuint mytexture; static void PollEvents(void) { SDL_Event event; @@ -152,6 +154,78 @@ static void render_coll_mesh(const CollMesh* m) { glEnable(GL_LIGHTING); } +float foo[512][512]; +float aspectRatio = 1.0; +float camera_zoom = 1.0; +static void raytraceCollMesh(vector3f camPos, vector3f camera_up, vector3f camera_forward, GeomTree* geomtree) { + memset(foo, 0, sizeof(float)*512*512); + + vector3f toPoint, xMov, yMov; + + vector3f topLeft, topRight, botRight, cross; + topLeft = topRight = botRight = camera_forward + camera_zoom; + cross = vector3f::Cross (camera_forward, camera_up) * aspectRatio; + topLeft = topLeft + camera_up - cross; + topRight = topRight + camera_up + cross; + botRight = botRight - camera_up + cross; + + xMov = topRight - topLeft; + yMov = botRight - topRight; + float xstep = 1.0f / 512; + float ystep = 1.0f / 512; + float xpos, ypos; + ypos = 0.0f; + + Uint32 t = SDL_GetTicks(); + for(int y = 0; y < 512; y++, ypos += ystep) { + xpos = 0.0f; + for(int x = 0; x < 512; x++, xpos += xstep) { + toPoint = topLeft + (xMov * xpos) + (yMov*ypos); + toPoint.Normalize(); + toPoint *= 10000; + + isect_t isect; + geomtree->TraceRay(camPos, toPoint, &isect); + + if(isect.triIdx != -1) { + foo[x][y] = 10.0/isect.dist; + } else { + foo[x][y] = 0; + } + } + } + printf("%3f million rays/sec\n", (512*512)/(1000.0*(SDL_GetTicks()-t))); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_LUMINANCE, GL_FLOAT, foo); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, 1, 0, 1, -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + glBindTexture(GL_TEXTURE_2D, mytexture); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBegin(GL_TRIANGLE_FAN); + glTexCoord2i(1,1); + glVertex3f(1,1,0); + glTexCoord2i(0,1); + glVertex3f(0,1,0); + glTexCoord2i(0,0); + glVertex3f(0,0,0); + glTexCoord2i(1,0); + glVertex3f(1,0,0); + glEnd(); + glDisable(GL_TEXTURE_2D); + printf("done..\n"); +} + void Viewer::MainLoop(void) { matrix4x4d rot = matrix4x4d::Identity(); float distance = 100; @@ -160,6 +234,10 @@ void Viewer::MainLoop(void) { CollMesh* cmesh = (CollMesh*)calloc(1, sizeof(CollMesh)); sbreGenCollMesh(cmesh, g_model, ¶ms, 1.0f); + Uint32 t= SDL_GetTicks(); + GeomTree* geomtree = new GeomTree(cmesh->ni/3, cmesh->pVertex, cmesh->pIndex); + printf("Geom tree build in $dms\n", SDL_GetTicks() -t); + for(;;) { PollEvents(); @@ -186,6 +264,7 @@ void Viewer::MainLoop(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushAttrib(GL_ALL_ATTRIB_BITS); + if(!g_renderCollMesh) { SetSbreParams(); sbreSetViewport(g_width, g_height, g_width*0.5, 1.0f, 10000.0f, 0.0f, 1.0f); sbreSetDirLight(lightCol, lightDir); @@ -205,6 +284,15 @@ void Viewer::MainLoop(void) { //sbreRenderCollMesh(cmesh, &p, &m); } else sbreRenderModel(&p, &m, g_model, ¶ms); + + } else { + vector3d _p = rot * vector3d(0,0,-distance); + vector3f camPos(_p.x, _p.y, _p.z); + vector3f forward = -vector3f::Normalize(camPos); + vector3f up = vector3f::Cross(vector3f(0,1,0), forward); + up.Normalize(); + raytraceCollMesh(camPos, up, forward, geomtree); + } glPopAttrib(); Gui::Draw(); @@ -213,6 +301,7 @@ void Viewer::MainLoop(void) { g_frameTime = (SDL_GetTicks() - lastFoo) * 0.001; lastFoo = SDL_GetTicks(); } + delete geomtree; } int main(int argc, char** argv) { @@ -272,6 +361,13 @@ int main(int argc, char** argv) { glEnable(GL_LIGHT0); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glGenTextures(1, &mytexture); + glBindTexture(GL_TEXTURE_2D, mytexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glClearColor(0,0,0,0); glViewport(0,0, g_width, g_height); GLFTInit();