#include "Mesh.h" #include "glInterface.h" #include "OcclusionQuery.h" #include "GlRenderer.h" #include "ViewCellsManager.h" #include "SceneGraph.h" #include "ViewCell.h" #include "Beam.h" #include "KdTree.h" #include "Environment.h" #include "Triangle3.h" #include "IntersectableWrapper.h" #include "BvHierarchy.h" #include "KdTree.h" #include "SamplingStrategy.h" #include "Preprocessor.h" #include "SceneGraph.h" #ifdef USE_CG #include #include #endif // if 1 = SAFE RENDERING OF PVS primitives without VBOs for Pvs error estimation #define EVAL_ERROR 0 namespace GtpVisibilityPreprocessor { static bool arbQuerySupport = false; static bool nvQuerySupport = false; static GLuint frontDepthMap; static GLuint backDepthMap; const int depthMapSize = 512; static void InitExtensions() { GLenum err = glewInit(); if (GLEW_OK != err) { // problem: glewInit failed, something is seriously wrong cerr << "Error: " << glewGetErrorString(err) << endl; exit(1); } if (GLEW_ARB_occlusion_query) arbQuerySupport = true; if (GLEW_NV_occlusion_query) nvQuerySupport = true; if (!arbQuerySupport && !nvQuerySupport) { cout << "I require the GL_ARB_occlusion_query or the GL_NV_occlusion_query OpenGL extension to work.\n"; exit(1); } if (!GLEW_ARB_vertex_buffer_object) { cout << "vbos not supported" << endl; } else { cout << "vbos supported" << endl; } } GlRenderer::GlRenderer(SceneGraph *sceneGraph, ViewCellsManager *viewCellsManager, KdTree *tree): Renderer(sceneGraph, viewCellsManager), mKdTree(tree), mUseFalseColors(false), mVboId(-1), mData(NULL), mIndices(NULL), //mUseVbos(true), mUseVbos(false), mComputeGVS(false), mCurrentFrame(-1) { mSceneGraph->CollectObjects(mObjects); #if 1 viewCellsManager->GetViewPoint(mViewPoint); mViewDirection = Vector3(0,0,1); #else // for sg snapshot mViewPoint = Vector3(32.8596, 9.86079, -1023.79); mViewDirection = Vector3(-0.92196, 0, 0.387286); // inside mViewPoint = Vector3(14.1254, 10.9818, -1032.75); mViewDirection = Vector3(-0.604798, 0, 0.796379); // outside mViewPoint = Vector3(35.092, 17.7078, -857.966); mViewDirection = Vector3(-0.411287, 0, -0.911506); // strange viewcell for error measurements (id 534) mViewPoint = Vector3(1405.9, 218.284, -736.785); mViewDirection = Vector3(0.989155, 0, 0.146877); #endif mFrame = 0; mWireFrame = false; Environment::GetSingleton()->GetBoolValue("Preprocessor.detectEmptyViewSpace", mDetectEmptyViewSpace); //mSnapErrorFrames = false; mSnapErrorFrames = true; mSnapPrefix = "snap/"; mUseForcedColors = false; mRenderBoxes = false; //mUseGlLists = true; mUseGlLists = false; if (mViewCellsManager->GetViewCellPointsList()->size()) mPvsStatFrames = (int)mViewCellsManager->GetViewCellPointsList()->size(); else Environment::GetSingleton()->GetIntValue("Preprocessor.pvsRenderErrorSamples", mPvsStatFrames); Debug<<"Info: will evaluate pixel error with "<GetItem()); glBegin(GL_TRIANGLES); Vector3 normal = t->GetNormal(); glNormal3f(normal.x, normal.y, normal.z); glVertex3f(t->mVertices[0].x, t->mVertices[0].y, t->mVertices[0].z); glVertex3f(t->mVertices[1].x, t->mVertices[1].y, t->mVertices[1].z); glVertex3f(t->mVertices[2].x, t->mVertices[2].y, t->mVertices[2].z); glEnd(); } void GlRenderer::RenderIntersectable(Intersectable *object) { if (!object || (object->mRenderedFrame == mCurrentFrame)) return; object->mRenderedFrame = mCurrentFrame; glPushAttrib(GL_CURRENT_BIT); if (mUseFalseColors) SetupFalseColor(object->mId); switch (object->Type()) { case Intersectable::MESH_INSTANCE: RenderMeshInstance((MeshInstance *)object); break; case Intersectable::VIEW_CELL: RenderViewCell(static_cast(object)); break; case Intersectable::TRANSFORMED_MESH_INSTANCE: RenderTransformedMeshInstance(static_cast(object)); break; case Intersectable::TRIANGLE_INTERSECTABLE: RenderTriangle(static_cast(object)); break; case Intersectable::BVH_INTERSECTABLE: { BvhNode *node = static_cast(object); if (mRenderBoxes) RenderBox(node->GetBoundingBox()); else RenderBvhNode(node); break; } case Intersectable::KD_INTERSECTABLE: { KdNode *node = (static_cast(object))->GetItem(); if (mRenderBoxes) RenderBox(mKdTree->GetBox(node)); else RenderKdNode(node); break; } default: cerr<<"Rendering this object not yet implemented\n"; break; } glPopAttrib(); } void GlRenderer::RenderRays(const VssRayContainer &rays, int colorType, int showDistribution, int maxAge) { float importance; glBegin(GL_LINES); VssRayContainer::const_iterator it = rays.begin(), it_end = rays.end(); for (; it != it_end; ++it) { VssRay *ray = *it; // only show distributions that were checked if (((ray->mDistribution == SamplingStrategy::DIRECTION_BASED_DISTRIBUTION) && ((showDistribution & 1) == 0)) || ((ray->mDistribution == SamplingStrategy::SPATIAL_BOX_BASED_DISTRIBUTION) && ((showDistribution & 2) == 0)) || ((ray->mDistribution == SamplingStrategy::OBJECT_DIRECTION_BASED_DISTRIBUTION) && ((showDistribution & 4) == 0)) || ((ray->mDistribution == SamplingStrategy::MUTATION_BASED_DISTRIBUTION) && ((showDistribution & 8) == 0)) || ((mViewCellsManager->GetPreprocessor()->mPass - ray->mPass) >= maxAge)) { continue; } switch (colorType) { case 0: glColor3f(1.0f, 0.0f, 0.0f); break; case 1: importance = 1.0f * ray->Length() / Magnitude(mViewCellsManager->GetViewSpaceBox().Diagonal()); glColor3f(importance, importance, importance); break; case 2: importance = log10(1e3 * ray->mPvsContribution) / 3.0f; glColor3f(importance, importance, importance); break; case 3: { // nested switches ok? switch (ray->mDistribution) { case SamplingStrategy::SPATIAL_BOX_BASED_DISTRIBUTION: glColor3f(1, 0, 0); break; case SamplingStrategy::MUTATION_BASED_DISTRIBUTION: glColor3f(0, 1, 0); break; case SamplingStrategy::DIRECTION_BASED_DISTRIBUTION: glColor3f(0, 1, 1); break; case SamplingStrategy::OBJECT_DIRECTION_BASED_DISTRIBUTION: glColor3f(1, 1, 0); break; } } } glVertex3fv(&ray->mOrigin.x); glVertex3fv(&ray->mTermination.x); } glEnd(); } void GlRenderer::RenderViewCell(ViewCell *vc) { if (vc->GetMesh()) { if (!mUseFalseColors) { if (vc->GetValid()) glColor3f(0,1,0); else glColor3f(0,0,1); } RenderMesh(vc->GetMesh()); } else { // render viewcells in the subtree if (!vc->IsLeaf()) { ViewCellInterior *vci = (ViewCellInterior *) vc; ViewCellContainer::iterator it = vci->mChildren.begin(); for (; it != vci->mChildren.end(); ++it) { RenderViewCell(*it); } } else { // cerr<<"Empty viewcell mesh\n"; } } } void GlRenderer::RenderMeshInstance(MeshInstance *mi) { RenderMesh(mi->GetMesh()); } void GlRenderer::RenderTransformedMeshInstance(TransformedMeshInstance *mi) { // apply world transform before rendering Matrix4x4 m; mi->GetWorldTransform(m); glPushMatrix(); glMultMatrixf((float *)m.x); RenderMesh(mi->GetMesh()); glPopMatrix(); } void GlRenderer::SetupFalseColor(const unsigned int id) { // swap bits of the color glColor3ub(id&255, (id>>8)&255, (id>>16)&255); } unsigned int GlRenderer::GetId(const unsigned char r, const unsigned char g, const unsigned char b) const { return r + (g << 8) + (b << 16); } void GlRenderer::SetupMaterial(Material *m) { if (m) glColor3fv(&(m->mDiffuseColor.r)); } void GlRenderer::RenderMesh(Mesh *mesh) { int i = 0; if (!mUseFalseColors && !mUseForcedColors) SetupMaterial(mesh->mMaterial); for (i = 0; i < mesh->mFaces.size(); i++) { if (mWireFrame) glBegin(GL_LINE_LOOP); else glBegin(GL_POLYGON); Face *face = mesh->mFaces[i]; Vector3 normal = mesh->GetNormal(i); glNormal3f(normal.x, normal.y, normal.z); for (int j = 0; j < face->mVertexIndices.size(); j++) { glVertex3fv(&mesh->mVertices[face->mVertexIndices[j]].x); } glEnd(); } } void GlRenderer::InitGL() { mSphere = (GLUquadric *)gluNewQuadric(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glFrontFace(GL_CCW); glCullFace(GL_BACK); glShadeModel(GL_FLAT); glDepthFunc(GL_LESS ); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); InitExtensions(); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glEnable(GL_NORMALIZE); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // create some occlusion queries OcclusionQuery::GenQueries(mOcclusionQueries, 100); SceneGraphInterior *interior = mSceneGraph->GetRoot(); SceneGraphNodeContainer::iterator ni = interior->mChildren.begin(); for (; ni != interior->mChildren.end(); ni++) { CreateVertexArrays(static_cast(*ni)); } } void GlRenderer::SetupProjection(const int w, const int h, const float angle) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(angle, 1.0, 0.1, 2.0*Magnitude(mSceneGraph->GetBox().Diagonal())); glMatrixMode(GL_MODELVIEW); } void GlRenderer::SetupCamera() { Vector3 target = mViewPoint + mViewDirection; Vector3 up(0,1,0); if (fabs(DotProd(mViewDirection, up)) > 0.99f) up = Vector3(1, 0, 0); glLoadIdentity(); gluLookAt(mViewPoint.x, mViewPoint.y, mViewPoint.z, target.x, target.y, target.z, up.x, up.y, up.z); } void GlRenderer::_RenderScene() { ObjectContainer::const_iterator oi = mObjects.begin(); for (; oi != mObjects.end(); oi++) RenderIntersectable(*oi); } void GlRenderer::_RenderSceneTrianglesWithDrawArrays() { EnableDrawArrays(); if (mUseVbos) glBindBufferARB(GL_ARRAY_BUFFER_ARB, mVboId); int offset = (int)mObjects.size() * 3; char *arrayPtr = mUseVbos ? NULL : (char *)mData; glVertexPointer(3, GL_FLOAT, 0, (char *)arrayPtr); glNormalPointer(GL_FLOAT, 0, (char *)arrayPtr + offset * sizeof(Vector3)); glDrawArrays(GL_TRIANGLES, 0, (int)mObjects.size() * 3); //DisableDrawArrays(); } void GlRenderer::_RenderDynamicObject(SceneGraphLeaf *leaf) { // apply world transform before rendering Matrix4x4 m; leaf->GetTransform(m); glPushMatrix(); glMultMatrixf((float *)m.x); glBegin(GL_TRIANGLES); ObjectContainer::const_iterator oi = leaf->mGeometry.begin(); for (; oi != leaf->mGeometry.end(); oi++) { TriangleIntersectable *object = (TriangleIntersectable *)*oi; Triangle3 *t = &(object->GetItem()); Vector3 normal = t->GetNormal(); glNormal3f(normal.x, normal.y, normal.z); glVertex3f(t->mVertices[0].x, t->mVertices[0].y, t->mVertices[0].z); glVertex3f(t->mVertices[1].x, t->mVertices[1].y, t->mVertices[1].z); glVertex3f(t->mVertices[2].x, t->mVertices[2].y, t->mVertices[2].z); } glEnd(); glPopMatrix(); #if 1 // test the box of the object AxisAlignedBox3 box = leaf->GetBox(); RenderBox(box); #endif } void GlRenderer::_RenderSceneTriangles() { glBegin(GL_TRIANGLES); ObjectContainer::const_iterator oi = mObjects.begin(); for (; oi != mObjects.end(); oi++) { if ((*oi)->Type() == Intersectable::TRIANGLE_INTERSECTABLE) { TriangleIntersectable *object = (TriangleIntersectable *)*oi; Triangle3 *t = &(object->GetItem()); Vector3 normal = t->GetNormal(); glNormal3f(normal.x, normal.y, normal.z); glVertex3f(t->mVertices[0].x, t->mVertices[0].y, t->mVertices[0].z); glVertex3f(t->mVertices[1].x, t->mVertices[1].y, t->mVertices[1].z); glVertex3f(t->mVertices[2].x, t->mVertices[2].y, t->mVertices[2].z); } } glEnd(); } bool GlRenderer::RenderScene() { mCurrentFrame++; Intersectable::NewMail(); #if DYNAMIC_OBJECTS_HACK Preprocessor *p = mViewCellsManager->GetPreprocessor(); // handle dynamic objects DynamicObjectsContainer::const_iterator dit, dit_end = p->mDynamicObjects.end(); for (dit = p->mDynamicObjects.begin(); dit != dit_end; ++ dit) { #if USE_TRANSFORMED_MESH_INSTANCE_HACK RenderIntersectable(*dit); #else _RenderDynamicObject(*dit); #endif } #endif #if 1 _RenderSceneTrianglesWithDrawArrays(); #else static int glList = -1; if (mUseGlLists) { if (glList == -1) { glList = glGenLists(1); glNewList(glList, GL_COMPILE); _RenderSceneTriangles(); glEndList(); } glCallList(glList); } else _RenderSceneTriangles(); #endif return true; } void GlRendererBuffer::EvalQueryWithItemBuffer() { // read back the texture glReadPixels(0, 0, GetWidth(), GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, mPixelBuffer); unsigned int *p = mPixelBuffer; for (int y = 0; y < GetHeight(); y++) { for (int x = 0; x < GetWidth(); x++, p++) { unsigned int id = (*p) & 0xFFFFFF; if (id != 0xFFFFFF) { ++ mObjects[id]->mCounter; } } } } /****************************************************************/ /* GlRendererBuffer implementation */ /****************************************************************/ GlRendererBuffer::GlRendererBuffer(SceneGraph *sceneGraph, ViewCellsManager *viewcells, KdTree *tree): GlRenderer(sceneGraph, viewcells, tree) { mPixelBuffer = NULL; // implement width and height in subclasses } void GlRendererBuffer::EvalQueryWithOcclusionQueries( //RenderCostSample &sample ) { glDepthFunc(GL_LEQUAL); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glDepthMask(GL_FALSE); // simulate detectemptyviewspace using backface culling if (mDetectEmptyViewSpace) { glEnable(GL_CULL_FACE); //cout << "culling" << endl; } else { //cout << "not culling" << endl; glDisable(GL_CULL_FACE); } //const int numQ = 1; const int numQ = (int)mOcclusionQueries.size(); //glFinish(); #if 0 //-- now issue queries for all objects for (int j = 0; j < (int)mObjects.size(); ++ j) { mOcclusionQueries[j]->BeginQuery(); RenderIntersectable(mObjects[j]); mOcclusionQueries[j]->EndQuery(); unsigned int pixelCount; pixelCount = mOcclusionQueries[j]->GetQueryResult(); mObjects[j]->mCounter += pixelCount; } #else int q = 0; //-- now issue queries for all objects for (int j = 0; j < (int)mObjects.size(); j += q) { for (q = 0; ((j + q) < (int)mObjects.size()) && (q < numQ); ++ q) { mOcclusionQueries[q]->BeginQuery(); RenderIntersectable(mObjects[j + q]); mOcclusionQueries[q]->EndQuery(); } //cout << "q: " << q << endl; // collect results of the queries for (int t = 0; t < q; ++ t) { unsigned int pixelCount; //-- reenable other state #if 0 bool available; do { available = mOcclusionQueries[t]->ResultAvailable(); if (!available) cout << "W"; } while (!available); #endif pixelCount = mOcclusionQueries[t]->GetQueryResult(); //if (pixelCount > 0) // cout <<"o="<mCounter += pixelCount; } //j += q; } #endif //glFinish(); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glDepthMask(GL_TRUE); glEnable(GL_CULL_FACE); } void GlRenderer::RandomViewPoint() { // do not use this function since it could return different viewpoints for // different executions of the algorithm // mViewCellsManager->GetViewPoint(mViewPoint); while (1) { Vector3 pVector = Vector3(halton.GetNumber(1), halton.GetNumber(2), halton.GetNumber(3)); mViewPoint = mViewCellsManager->GetViewSpaceBox().GetPoint(pVector); ViewCell *v = mViewCellsManager->GetViewCell(mViewPoint); if (v && v->GetValid()) break; // generate a new vector halton.GenerateNext(); } Vector3 dVector = Vector3(2*M_PI*halton.GetNumber(4), M_PI*halton.GetNumber(5), 0.0f); mViewDirection = Normalize(Vector3(sin(dVector.x), // cos(dVector.y), 0.0f, cos(dVector.x))); halton.GenerateNext(); } void GlRenderer::RenderBox(const AxisAlignedBox3 &box) { glBegin(GL_LINE_LOOP); glVertex3d(box.Min().x, box.Max().y, box.Min().z ); glVertex3d(box.Max().x, box.Max().y, box.Min().z ); glVertex3d(box.Max().x, box.Min().y, box.Min().z ); glVertex3d(box.Min().x, box.Min().y, box.Min().z ); glEnd(); glBegin(GL_LINE_LOOP); glVertex3d(box.Min().x, box.Min().y, box.Max().z ); glVertex3d(box.Max().x, box.Min().y, box.Max().z ); glVertex3d(box.Max().x, box.Max().y, box.Max().z ); glVertex3d(box.Min().x, box.Max().y, box.Max().z ); glEnd(); glBegin(GL_LINE_LOOP); glVertex3d(box.Max().x, box.Min().y, box.Min().z ); glVertex3d(box.Max().x, box.Min().y, box.Max().z ); glVertex3d(box.Max().x, box.Max().y, box.Max().z ); glVertex3d(box.Max().x, box.Max().y, box.Min().z ); glEnd(); glBegin(GL_LINE_LOOP); glVertex3d(box.Min().x, box.Min().y, box.Min().z ); glVertex3d(box.Min().x, box.Min().y, box.Max().z ); glVertex3d(box.Min().x, box.Max().y, box.Max().z ); glVertex3d(box.Min().x, box.Max().y, box.Min().z ); glEnd(); glBegin(GL_LINE_LOOP); glVertex3d(box.Min().x, box.Min().y, box.Min().z ); glVertex3d(box.Max().x, box.Min().y, box.Min().z ); glVertex3d(box.Max().x, box.Min().y, box.Max().z ); glVertex3d(box.Min().x, box.Min().y, box.Max().z ); glEnd(); glBegin(GL_LINE_LOOP); glVertex3d(box.Min().x, box.Max().y, box.Min().z ); glVertex3d(box.Max().x, box.Max().y, box.Min().z ); glVertex3d(box.Max().x, box.Max().y, box.Max().z ); glVertex3d(box.Min().x, box.Max().y, box.Max().z ); glEnd(); } void GlRenderer::RenderBvhNode(BvhNode *node) { if (node->IsLeaf()) { BvhLeaf *leaf = (BvhLeaf *) node; #if 0 if (leaf->mGlList == 0) { leaf->mGlList = glGenLists(1); if (leaf->mGlList != 0) glNewList(leaf->mGlList, GL_COMPILE); for (int i=0; i < leaf->mObjects.size(); i++) RenderIntersectable(leaf->mObjects[i]); if (leaf->mGlList != 0) glEndList(); } if (leaf->mGlList != 0) glCallList(leaf->mGlList); #else for (int i=0; i < leaf->mObjects.size(); i++) RenderIntersectable(leaf->mObjects[i]); #endif } else { BvhInterior *in = (BvhInterior *)node; RenderBvhNode(in->GetBack()); RenderBvhNode(in->GetFront()); } } void GlRenderer::RenderKdNode(KdNode *node) { if (node->IsLeaf()) { #if !EVAL_ERROR RenderKdLeaf(static_cast(node)); #else KdLeaf *leaf = static_cast(node); for (int i=0; i < leaf->mObjects.size(); i++) { RenderIntersectable(leaf->mObjects[i]); } #endif } else { KdInterior *inter = static_cast(node); RenderKdNode(inter->mBack); RenderKdNode(inter->mFront); } } void GlRendererBuffer::EvalRenderCostSample(RenderCostSample &sample, const bool useOcclusionQueries, const int threshold ) { // choose a random view point mViewCellsManager->GetViewPoint(mViewPoint); sample.mPosition = mViewPoint; //cout << "viewpoint: " << mViewPoint << endl; // take a render cost sample by rendering a cube Vector3 directions[6]; directions[0] = Vector3(1,0,0); directions[1] = Vector3(0,1,0); directions[2] = Vector3(0,0,1); directions[3] = Vector3(-1,0,0); directions[4] = Vector3(0,-1,0); directions[5] = Vector3(0,0,-1); sample.mVisibleObjects = 0; // reset object counters ObjectContainer::const_iterator it, it_end = mObjects.end(); for (it = mObjects.begin(); it != it_end; ++ it) { (*it)->mCounter = 0; } ++ mFrame; //glCullFace(GL_FRONT); glCullFace(GL_BACK); glDisable(GL_CULL_FACE); // query all 6 directions for a full point sample for (int i = 0; i < 6; ++ i) { mViewDirection = directions[i]; SetupCamera(); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); mUseFalseColors = true; // the actual scene rendering fills the depth (for occlusion queries) // and the frame buffer (for item buffer) RenderScene(); if (0) { char filename[256]; sprintf(filename, "snap/frame-%04d-%d.png", mFrame, i); // QImage im = toImage(); // im.save(filename, "PNG"); } // evaluate the sample if (useOcclusionQueries) { EvalQueryWithOcclusionQueries(); } else { EvalQueryWithItemBuffer(); } } // now evaluate the statistics over that sample // currently only the number of visible objects is taken into account sample.Reset(); for (it = mObjects.begin(); it != it_end; ++ it) { Intersectable *obj = *it; if (obj->mCounter >= threshold) { ++ sample.mVisibleObjects; sample.mVisiblePixels += obj->mCounter; } } //cout << "RS=" << sample.mVisibleObjects << " "; } GlRendererBuffer::~GlRendererBuffer() { #if 0 #ifdef USE_CG if (sCgFragmentProgram) cgDestroyProgram(sCgFragmentProgram); if (sCgContext) cgDestroyContext(sCgContext); #endif #endif } void GlRendererBuffer::SampleRenderCost(const int numSamples, vector &samples, const bool useOcclusionQueries, const int threshold ) { MakeCurrent(); if (mPixelBuffer == NULL) mPixelBuffer = new unsigned int[GetWidth()*GetHeight()]; // using 90 degree projection to capture 360 view with 6 samples SetupProjection(GetHeight(), GetHeight(), 90.0f); //samples.resize(numSamples); halton.Reset(); // the number of queries queried in batch mode const int numQ = 500; //const int numQ = (int)mObjects.size(); if (useOcclusionQueries) { cout << "\ngenerating " << numQ << " queries ... "; OcclusionQuery::GenQueries(mOcclusionQueries, numQ); cout << "finished" << endl; } // sampling queries for (int i = 0; i < numSamples; ++ i) { cout << "."; EvalRenderCostSample(samples[i], useOcclusionQueries, threshold); } DoneCurrent(); } void GlRenderer::ClearErrorBuffer() { for (int i=0; i < mPvsStatFrames; i++) { mPvsErrorBuffer[i].mError = 1.0f; } mPvsStat.maxError = 0.0f; } void GlRendererBuffer::EvalPvsStat() { MakeCurrent(); GlRenderer::EvalPvsStat(); DoneCurrent(); // mRenderingFinished.wakeAll(); } void GlRendererBuffer::EvalPvsStat(const SimpleRayContainer &viewPoints) { MakeCurrent(); GlRenderer::EvalPvsStat(viewPoints); DoneCurrent(); } void GlRendererBuffer::SampleBeamContributions(Intersectable *sourceObject, Beam &beam, const int desiredSamples, BeamSampleStatistics &stat) { // TODO: should be moved out of here (not to be done every time) // only back faces are interesting for the depth pass glShadeModel(GL_FLAT); glDisable(GL_LIGHTING); // needed to kill the fragments for the front buffer glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0); // assumes that the beam is constructed and contains kd-tree nodes // and viewcells which it intersects // Get the number of viewpoints to be sampled // Now it is a sqrt but in general a wiser decision could be made. // The less viewpoints the better for rendering performance, since less passes // over the beam is needed. // The viewpoints could actually be generated outside of the bounding box which // would distribute the 'efective viewpoints' of the object surface and thus // with a few viewpoints better sample the viewpoint space.... //TODO: comment in //int viewPointSamples = sqrt((float)desiredSamples); int viewPointSamples = max(desiredSamples / (GetWidth() * GetHeight()), 1); // the number of direction samples per pass is given by the number of viewpoints int directionalSamples = desiredSamples / viewPointSamples; Debug << "directional samples: " << directionalSamples << endl; for (int i = 0; i < viewPointSamples; ++ i) { Vector3 viewPoint = beam.mBox.GetRandomPoint(); // perhaps the viewpoint should be shifted back a little bit so that it always lies // inside the source object // 'ideally' the viewpoints would be distributed on the soureObject surface, but this // would require more complicated sampling (perhaps hierarchical rejection sampling of // the object surface is an option here - only the mesh faces which are inside the box // are considered as candidates) SampleViewpointContributions(sourceObject, viewPoint, beam, directionalSamples, stat); } // note: // this routine would be called only if the number of desired samples is sufficiently // large - for other rss tree cells the cpu based sampling is perhaps more efficient // distributing the work between cpu and gpu would also allow us to place more sophisticated // sample distributions (silhouette ones) using the cpu and the jittered once on the GPU // in order that thios scheme is working well the gpu render buffer should run in a separate // thread than the cpu sampler, which would not be such a big problem.... // disable alpha test again glDisable(GL_ALPHA_TEST); } void GlRendererBuffer::SampleViewpointContributions(Intersectable *sourceObject, const Vector3 viewPoint, Beam &beam, const int samples, BeamSampleStatistics &stat) { // 1. setup the view port to match the desired samples glViewport(0, 0, samples, samples); // 2. setup the projection matrix and view matrix to match the viewpoint + beam.mDirBox SetupProjectionForViewPoint(viewPoint, beam, sourceObject); // 3. reset z-buffer to 0 and render the source object for the beam // with glCullFace(Enabled) and glFrontFace(GL_CW) // save result to the front depth map // the front depth map holds ray origins // front depth buffer must be initialised to 0 float clearDepth; glGetFloatv(GL_DEPTH_CLEAR_VALUE, &clearDepth); glClearDepth(0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); glCullFace(GL_FRONT); glColorMask(0, 0, 0, 0); // stencil is increased where the source object is located glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 0x1, 0x1); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); #if 0 static int glSourceObjList = -1; if (glSourceObjList != -1) { glSourceObjList = glGenLists(1); glNewList(glSourceObjList, GL_COMPILE); RenderIntersectable(sourceObject); glEndList(); } glCallList(glSourceObjList); #else RenderIntersectable(sourceObject); #endif // copy contents of the front depth buffer into depth texture glBindTexture(GL_TEXTURE_2D, frontDepthMap); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, depthMapSize, depthMapSize); // reset clear function glClearDepth(clearDepth); // 4. set up the termination depth buffer (= standard depth buffer) // only rays which have non-zero entry in the origin buffer are valid since // they realy start on the object surface (this is tagged by setting a // stencil buffer bit at step 3). glStencilFunc(GL_EQUAL, 0x1, 0x1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDepthMask(1); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); // setup front depth buffer glEnable(GL_TEXTURE_2D); #if 0 #ifdef USE_CG // bind pixel shader implementing the front depth buffer functionality cgGLBindProgram(sCgFragmentProgram); cgGLEnableProfile(sCgFragmentProfile); #endif #endif // 5. render all objects inside the beam // we can use id based false color to read them back for gaining the pvs glColorMask(1, 1, 1, 1); // if objects not stored in beam => extract objects if (beam.mFlags & !Beam::STORE_OBJECTS) { vector::const_iterator it, it_end = beam.mKdNodes.end(); Intersectable::NewMail(); for (it = beam.mKdNodes.begin(); it != it_end; ++ it) { mKdTree->CollectObjects(*it, beam.mObjects); } } // (objects can be compiled to a gl list now so that subsequent rendering for // this beam is fast - the same hold for step 3) // Afterwards we have two depth buffers defining the ray origin and termination #if 0 static int glObjList = -1; if (glObjList != -1) { glObjList = glGenLists(1); glNewList(glObjList, GL_COMPILE); ObjectContainer::const_iterator it, it_end = beam.mObjects.end(); for (it = beam.mObjects.begin(); it != it_end; ++ it) { // render all objects except the source object if (*it != sourceObject) RenderIntersectable(*it); } glEndList(); } glCallList(glObjList); #else ObjectContainer::const_iterator it, it_end = beam.mObjects.end(); for (it = beam.mObjects.begin(); it != it_end; ++ it) { // render all objects except the source object if (*it != sourceObject) RenderIntersectable(*it); } #endif // 6. Use occlusion queries for all viewcell meshes associated with the beam -> // a fragment passes if the corresponding stencil fragment is set and its depth is // between origin and termination buffer // create new queries if necessary OcclusionQuery::GenQueries(mOcclusionQueries, (int)beam.mViewCells.size()); // check whether any backfacing polygon would pass the depth test? // matt: should check both back /front facing because of dual depth buffer // and danger of cutting the near plane with front facing polys. glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glDepthMask(GL_FALSE); glDisable(GL_CULL_FACE); ViewCellContainer::const_iterator vit, vit_end = beam.mViewCells.end(); int queryIdx = 0; for (vit = beam.mViewCells.begin(); vit != vit_end; ++ vit) { mOcclusionQueries[queryIdx ++]->BeginQuery(); RenderIntersectable(*vit); mOcclusionQueries[queryIdx]->EndQuery(); ++ queryIdx; } // at this point, if possible, go and do some other computation // 7. The number of visible pixels is the number of sample rays which see the source // object from the corresponding viewcell -> remember these values for later update // of the viewcell pvs - or update immediately? queryIdx = 0; for (vit = beam.mViewCells.begin(); vit != vit_end; ++ vit) { // fetch queries unsigned int pixelCount = mOcclusionQueries[queryIdx ++]->GetQueryResult(); if (pixelCount) Debug << "view cell " << (*vit)->GetId() << " visible pixels: " << pixelCount << endl; } // 8. Copmpute rendering statistics // In general it is not neccessary to remember to extract all the rays cast. I hope it // would be sufficient to gain only the intergral statistics about the new contributions // and so the rss tree would actually store no new rays (only the initial ones) // the subdivision of the tree would only be driven by the statistics (the glrender could // evaluate the contribution entropy for example) // However might be an option to extract/store only those the rays which made a contribution // (new viewcell has been discovered) or relative contribution greater than a threshold ... ObjectContainer pvsObj; stat.pvsSize = ComputePvs(beam.mObjects, pvsObj); // to gain ray source and termination // copy contents of ray termination buffer into depth texture // and compare with ray source buffer #if 0 VssRayContainer rays; glBindTexture(GL_TEXTURE_2D, backDepthMap); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, depthMapSize, depthMapSize); ComputeRays(Intersectable *sourceObj, rays); #endif //////// //-- cleanup // reset gl state glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glDepthMask(GL_TRUE); glEnable(GL_CULL_FACE); glDisable(GL_STENCIL_TEST); #if 0 #ifdef USE_CG cgGLDisableProfile(sCgFragmentProfile); #endif #endif glDisable(GL_TEXTURE_2D); // remove objects from beam if (beam.mFlags & !Beam::STORE_OBJECTS) beam.mObjects.clear(); } void GlRendererBuffer::SetupProjectionForViewPoint(const Vector3 &viewPoint, const Beam &beam, Intersectable *sourceObject) { float left, right, bottom, top, znear, zfar; beam.ComputePerspectiveFrustum(left, right, bottom, top, znear, zfar, mSceneGraph->GetBox()); //Debug << left << " " << right << " " << bottom << " " << top << " " << znear << " " << zfar << endl; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(left, right, bottom, top, znear, zfar); //glFrustum(-1, 1, -1, 1, 1, 20000); const Vector3 center = viewPoint + beam.GetMainDirection() * (zfar - znear) * 0.3f; const Vector3 up = Normalize(CrossProd(beam.mPlanes[0].mNormal, beam.mPlanes[4].mNormal)); #ifdef GTP_DEBUG Debug << "view point: " << viewPoint << endl; Debug << "eye: " << center << endl; Debug << "up: " << up << endl; #endif glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(viewPoint.x, viewPoint.y, viewPoint.z, center.x, center.y, center.z, up.x, up.y, up.z); } void GlRendererBuffer::InitGL() { //MakeCurrent(); GlRenderer::InitGL(); #if 0 // initialise dual depth buffer textures glGenTextures(1, &frontDepthMap); glBindTexture(GL_TEXTURE_2D, frontDepthMap); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, depthMapSize, depthMapSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glGenTextures(1, &backDepthMap); glBindTexture(GL_TEXTURE_2D, backDepthMap); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, depthMapSize, depthMapSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); #ifdef USE_CG // cg initialization cgSetErrorCallback(handleCgError); sCgContext = cgCreateContext(); if (cgGLIsProfileSupported(CG_PROFILE_ARBFP1)) sCgFragmentProfile = CG_PROFILE_ARBFP1; else { // try FP30 if (cgGLIsProfileSupported(CG_PROFILE_FP30)) sCgFragmentProfile = CG_PROFILE_FP30; else { Debug << "Neither arbfp1 or fp30 fragment profiles supported on this system" << endl; exit(1); } } sCgFragmentProgram = cgCreateProgramFromFile(sCgContext, CG_SOURCE, "../src/dual_depth.cg", sCgFragmentProfile, NULL, NULL); if (!cgIsProgramCompiled(sCgFragmentProgram)) cgCompileProgram(sCgFragmentProgram); cgGLLoadProgram(sCgFragmentProgram); cgGLBindProgram(sCgFragmentProgram); Debug << "---- PROGRAM BEGIN ----\n" << cgGetProgramString(sCgFragmentProgram, CG_COMPILED_PROGRAM) << "---- PROGRAM END ----\n"; #endif #endif //DoneCurrent(); } void GlRendererBuffer::ComputeRays(Intersectable *sourceObj, VssRayContainer &rays) { for (int i = 0; i < depthMapSize * depthMapSize; ++ i) { //todo glGetTexImage() } } bool GlRendererBuffer::ValidViewPoint() { MakeCurrent(); SetupProjection(GetWidth(), GetHeight()); bool result = GlRenderer::ValidViewPoint(); DoneCurrent(); return result; } void GlRenderer::EvalPvsStat() { mPvsStat.Reset(); halton.Reset(); SetupProjection(GetWidth(), GetHeight()); cout << "Random Pvs STATS, mPvsStatFrames=" << mPvsStatFrames << endl; for (int i=0; i < mPvsStatFrames; i++) { float err; // set frame id for saving the error buffer mFrame = i; // cerr<<"RV"< 0.0f) { int pvsSize; float error = GetPixelError(pvsSize); // emit UpdatePvsErrorItem(i, // mPvsErrorBuffer[i]); // swapBuffers(); mPvsErrorBuffer[i].mError = error; mPvsErrorBuffer[i].mPvsSize = pvsSize; cout<<"("<= 0.0f) { if (err > mPvsStat.maxError) mPvsStat.maxError = err; mPvsStat.sumError += err; mPvsStat.sumPvsSize += mPvsErrorBuffer[i].mPvsSize; if (err == 0.0f) mPvsStat.errorFreeFrames++; mPvsStat.frames++; } } glFinish(); static bool first = true; if (first) { bool exportRandomViewCells; Environment::GetSingleton()->GetBoolValue("ViewCells.exportRandomViewCells", exportRandomViewCells); if (0 && exportRandomViewCells) { char buff[512]; Environment::GetSingleton()->GetStringValue("Scene.filename", buff); string filename(buff); string viewCellPointsFile; if (strstr(filename.c_str(), ".obj")) viewCellPointsFile = ReplaceSuffix(filename, ".obj", ".vc"); else if (strstr(filename.c_str(), ".dat")) viewCellPointsFile = ReplaceSuffix(filename, ".dat", ".vc"); else if (strstr(filename.c_str(), ".x3d")) viewCellPointsFile = ReplaceSuffix(filename, ".x3d", ".vc"); cout << "exporting random view cells" << endl; preprocessor->mViewCellsManager->ExportRandomViewCells(viewCellPointsFile); cout << "finished" << endl; } first = false; } cout< 0.0f) { // compute the pixel error const float error = GetPixelError(pvsSize); mPvsErrorBuffer[i].mError = error; mPvsErrorBuffer[i].mPvsSize = pvsSize; const int pixelError = (int)mPvsErrorBuffer[i].mError * GetWidth() * GetHeight(); if (0 && (pixelError > 0) && (pvsSize > 0)) { cout << "err: " << i << "," << mViewPoint << "," << mViewDirection << " " << mPvsErrorBuffer[i].mError * GetWidth() * GetHeight() << endl; } } const float err = mPvsErrorBuffer[i].mError; // hack: // do not account for the case that no PVS is rendered - hack for 08 rebuttal GVS evaluation // drop the first frame (for some reason, the first frame yields wrong pixel error) if (//(mFrame > 0) && (err >= 0.0f) && (err < (1.0f - 1e-6f)) && (pvsSize > 0)) { if (err > mPvsStat.maxError) { mPvsStat.maxError = err; cout << "new max error: " << mPvsStat.maxError * GetWidth()*GetHeight() << endl; } mPvsStat.sumError += err; mPvsStat.sumPvsSize += mPvsErrorBuffer[i].mPvsSize; if (err == 0.0f) ++ mPvsStat.errorFreeFrames; // $$ JB // moved it back here not to count frames with negative err (backfacing triangle) ++ mPvsStat.frames; } } glFinish(); cout << endl << flush; } bool GlRenderer::ValidViewPoint() { //cout<<"VV4 "; if (!mDetectEmptyViewSpace) return true; //cout << "vp: " << mViewPoint << " dir: " << mViewDirection << endl; OcclusionQuery *query = mOcclusionQueries[0]; // now check whether any backfacing polygon would pass the depth test SetupCamera(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable( GL_CULL_FACE ); glCullFace(GL_BACK); //cout<<"VV1 "; RenderScene(); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glDepthMask(GL_FALSE); glDisable( GL_CULL_FACE ); query->BeginQuery(); // cout<<"VV2 "; RenderScene(); // cout<<"VV3 "; query->EndQuery(); // at this point, if possible, go and do some other computation glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glDepthMask(GL_TRUE); glEnable( GL_CULL_FACE ); // int wait = 0; // while (!query->ResultAvailable()) { // wait++; // } // reenable other state unsigned int pixelCount = query->GetQueryResult(); // cout<<"VV4 "; if (pixelCount > 0) { return false; // backfacing polygon found -> not a valid viewspace sample } return true; } float GlRenderer::GetPixelError(int &pvsSize) { return -1.0f; } void GlRenderer::RenderViewPoint() { mWireFrame = true; glPushMatrix(); glTranslatef(mViewPoint.x, mViewPoint.y, mViewPoint.z); glScalef(5.0f, 5.0f, 5.0f); glPushAttrib(GL_CURRENT_BIT); glColor3f(1.0f, 0.0f, 0.0f); gluSphere((::GLUquadric *)mSphere, 1e-3*Magnitude(mViewCellsManager->GetViewSpaceBox().Size()), 6, 6); glPopAttrib(); glPopMatrix(); mWireFrame = false; } void GlRenderer::EnableDrawArrays() { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); } void GlRenderer::DisableDrawArrays() { glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } #if 0 void GlRenderer::RenderKdLeaf(KdLeaf *leaf) { int bufferSize = 0; // count #new triangles for (size_t i = 0; i < leaf->mObjects.size(); ++ i) { TriangleIntersectable *obj = static_cast(leaf->mObjects[i]); // check if already rendered if (!obj->Mailed2()) bufferSize += 3; //else cout << obj->mMailbox << " " << obj->sMailId << " "; } Vector3 *vertices = new Vector3[bufferSize]; Vector3 *normals = new Vector3[bufferSize]; int j = 0; for (size_t i = 0; i < leaf->mObjects.size(); ++ i) { TriangleIntersectable *obj = static_cast(leaf->mObjects[i]); // check if already rendered if (obj->Mailed2()) continue; obj->Mail2(); Triangle3 tri = obj->GetItem(); vertices[j * 3 + 0] = tri.mVertices[0]; vertices[j * 3 + 1] = tri.mVertices[1]; vertices[j * 3 + 2] = tri.mVertices[2]; Vector3 n = tri.GetNormal(); normals[j * 3 + 0] = n; normals[j * 3 + 1] = n; normals[j * 3 + 2] = n; ++ j; } glVertexPointer(3, GL_FLOAT, 0, vertices); glNormalPointer(GL_FLOAT, 0, normals); glDrawArrays(GL_TRIANGLES, 0, bufferSize); DEL_PTR(vertices); DEL_PTR(normals); } #else void GlRenderer::RenderKdLeaf(KdLeaf *leaf) { if (!leaf->mIndexBufferSize) return; size_t offset = mObjects.size() * 3; char *arrayPtr = mUseVbos ? NULL : (char *)mData; glVertexPointer(3, GL_FLOAT, 0, (char *)arrayPtr); glNormalPointer(GL_FLOAT, 0, (char *)arrayPtr + offset * sizeof(Vector3)); glDrawElements(GL_TRIANGLES, leaf->mIndexBufferSize, GL_UNSIGNED_INT, mIndices + leaf->mIndexBufferStart); } #endif void GlRenderer::PreparePvs(const ObjectPvs &pvs) { int indexBufferSize = 0; KdNode::NewMail2(); ObjectPvsIterator it = pvs.GetIterator(); while (it.HasMoreEntries()) { Intersectable *obj = it.Next(); switch (obj->Type()) { case Intersectable::KD_INTERSECTABLE: { KdNode *node = static_cast(obj)->GetItem(); _UpdatePvsIndices(node, indexBufferSize); } break; } } mIndexBufferSize = indexBufferSize; } void GlRenderer::_UpdatePvsIndices(KdNode *node, int &indexBufferSize) { if (node->Mailed2()) return; node->Mail2(); // if (mObjects.size() * 3 < indexBufferSize) cerr << "problem: " << mObjects.size() * 3 << " < " << indexBufferSize << endl; if (!node->IsLeaf()) { KdInterior *kdInterior = static_cast(node); _UpdatePvsIndices(kdInterior->mBack, indexBufferSize); _UpdatePvsIndices(kdInterior->mFront, indexBufferSize); } else { KdLeaf *leaf = static_cast(node); leaf->mIndexBufferStart = indexBufferSize; for (size_t i = 0; i < leaf->mObjects.size(); ++ i) { TriangleIntersectable *obj = static_cast(leaf->mObjects[i]); if (obj->mRenderedFrame != mCurrentFrame) { obj->mRenderedFrame = mCurrentFrame; mIndices[indexBufferSize + 0] = (obj->GetId() - 1) * 3 + 0; mIndices[indexBufferSize + 1] = (obj->GetId() - 1) * 3 + 1; mIndices[indexBufferSize + 2] = (obj->GetId() - 1) * 3 + 2; indexBufferSize += 3; } } leaf->mIndexBufferSize = indexBufferSize - leaf->mIndexBufferStart; } } void GlRenderer::CreateVertexArrays(SceneGraphLeaf *leaf) { mData = new Vector3[leaf->mGeometry.size() * 6]; mIndices = new unsigned int[leaf->mGeometry.size() * 3]; size_t offset = leaf->mGeometry.size() * 3; for (size_t i = 0; i < leaf->mGeometry.size(); ++ i) { TriangleIntersectable *obj = static_cast(leaf->mGeometry[i]); Triangle3 tri = obj->GetItem(); const Vector3 n = tri.GetNormal(); mData[i * 3 + 0] = tri.mVertices[0]; mData[i * 3 + 1] = tri.mVertices[1]; mData[i * 3 + 2] = tri.mVertices[2]; mData[offset + i * 3 + 0] = n; mData[offset + i * 3 + 1] = n; mData[offset + i * 3 + 2] = n; } if (mUseVbos) { EnableDrawArrays(); glGenBuffersARB(1, &mVboId); glBindBufferARB(GL_ARRAY_BUFFER_ARB, mVboId); glVertexPointer(3, GL_FLOAT, 0, (char *)NULL); glNormalPointer(GL_FLOAT, 0, (char *)NULL + offset * sizeof(Vector3)); glBufferDataARB(GL_ARRAY_BUFFER_ARB, leaf->mGeometry.size() * 6 * sizeof(Vector3), mData, GL_STATIC_DRAW_ARB); glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); DisableDrawArrays(); delete [] mData; mData = NULL; } cout << "\n******** created vertex buffer objects **********" << endl; } void GlRenderer::DeleteVbos() { glDeleteBuffersARB(1, &mVboId); } }