#include "Mesh.h" #include "GlRenderer.h" #include "ViewCellsManager.h" #include "SceneGraph.h" #include "Pvs.h" #include "Viewcell.h" #include "Beam.h" #include #include #include static CGcontext sCgContext = NULL; static CGprogram sCgFragmentProgram = NULL; static CGparameter sCgKdParam = NULL; static CGparameter sCgModelViewProjParam = NULL; static CGprofile sCgFragmentProfile = CG_PROFILE_FP30; GLuint depthMap; const int depthMapSize = 512; static vector sQueries; GlRendererWidget *rendererWidget = NULL; #ifdef _WIN32 PFNGLGENOCCLUSIONQUERIESNVPROC glGenOcclusionQueriesNV; PFNGLBEGINOCCLUSIONQUERYNVPROC glBeginOcclusionQueryNV; PFNGLENDOCCLUSIONQUERYNVPROC glEndOcclusionQueryNV; PFNGLGETOCCLUSIONQUERYUIVNVPROC glGetOcclusionQueryuivNV; #endif GlRenderer::GlRenderer(SceneGraph *sceneGraph, ViewCellsManager *viewCellsManager): Renderer(sceneGraph, viewCellsManager) { mSceneGraph->CollectObjects(&mObjects); mViewPoint = mSceneGraph->GetBox().Center(); mViewDirection = Vector3(0,0,1); // timerId = startTimer(10); mFrame = 0; mWireFrame = false; } GlRenderer::~GlRenderer() { } void GlRenderer::RenderIntersectable(Intersectable *object) { SetupFalseColor(object->mId); switch (object->Type()) { case Intersectable::MESH_INSTANCE: RenderMeshInstance((MeshInstance *)object); break; default: cerr<<"Rendering this object not yet implemented\n"; break; } } void GlRenderer::RenderMeshInstance(MeshInstance *mi) { RenderMesh(mi->GetMesh()); } void GlRenderer::SetupFalseColor(const int id) { // swap bits of the color glColor3ub(id&255, (id>>8)&255, (id>>16)&255); } void GlRenderer::SetupMaterial(Material *m) { if (m) glColor3fv(&(m->mDiffuseColor.r)); else glColor3f(1.0f, 1.0f, 1.0f); } void GlRenderer::RenderMesh(Mesh *mesh) { int i = 0; if (!mUseFalseColors) 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]; for (int j = 0; j < face->mVertexIndices.size(); j++) { glVertex3fv(&mesh->mVertices[face->mVertexIndices[j]].x); } glEnd(); } } void GlRenderer::InitGL() { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glEnable(GL_CULL_FACE); glShadeModel(GL_FLAT); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glGenOcclusionQueriesNV = (PFNGLGENOCCLUSIONQUERIESNVPROC) wglGetProcAddress("glGenOcclusionQueriesNV"); glBeginOcclusionQueryNV = (PFNGLBEGINOCCLUSIONQUERYNVPROC) wglGetProcAddress("glBeginOcclusionQueryNV"); glEndOcclusionQueryNV = (PFNGLENDOCCLUSIONQUERYNVPROC) wglGetProcAddress("glEndOcclusionQueryNV"); glGetOcclusionQueryuivNV = (PFNGLGETOCCLUSIONQUERYUIVNVPROC) wglGetProcAddress("glGetOcclusionQueryuivNV"); // initialise second depth buffer texture glGenTextures(1, &depthMap); glBindTexture(GL_TEXTURE_2D, depthMap); 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); // cg initialization sCgContext = cgCreateContext(); sCgFragmentProgram = cgCreateProgramFromFile(sCgContext, CG_SOURCE, "dual_depth.cg", sCgFragmentProfile, NULL, NULL); } void GlRenderer::SetupProjection(const int w, const int h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(70.0, 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); glLoadIdentity(); gluLookAt(mViewPoint.x, mViewPoint.y, mViewPoint.z, target.x, target.y, target.z, up.x, up.y, up.z); } void GlRenderer::RandomViewPoint() { Vector3 pVector = Vector3(halton.GetNumber(1), halton.GetNumber(2), halton.GetNumber(3)); Vector3 dVector = Vector3(2*M_PI*halton.GetNumber(4), M_PI*halton.GetNumber(5), 0.0f); mViewPoint = mSceneGraph->GetBox().GetPoint(pVector); mViewDirection = Normalize(Vector3(sin(dVector.x), // cos(dVector.y), 0.0f, cos(dVector.x))); halton.GenerateNext(); } float GlRenderer::GetPixelError() { float pErrorPixels = -1.0f; glReadBuffer(GL_BACK); mUseFalseColors = true; SetupCamera(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable( GL_CULL_FACE ); RenderScene(); // now check whether any backfacing polygon would pass the depth test static int query = -1; if (query == -1) glGenOcclusionQueriesNV(1, (unsigned int *)&query); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glDepthMask(GL_FALSE); glDisable( GL_CULL_FACE ); glBeginOcclusionQueryNV(query); RenderScene(); glEndOcclusionQueryNV(); // 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 ); unsigned int pixelCount; // reenable other state glGetOcclusionQueryuivNV(query, GL_PIXEL_COUNT_NV, &pixelCount); if (pixelCount > 0) return -1.0f; // backfacing polygon found -> not a valid viewspace sample ViewCell *viewcell = mViewCellsManager->GetViewCell(mViewPoint); if (viewcell) { SetupCamera(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Render PVS std::map, LtSample >::const_iterator it = viewcell->GetPvs().mEntries.begin(); for (; it != viewcell->GetPvs().mEntries.end(); ++ it) { Intersectable *object = (*it).first; RenderIntersectable(object); } glBeginOcclusionQueryNV(query); SetupCamera(); RenderScene(); glEndOcclusionQueryNV(); unsigned int pixelCount; // reenable other state glGetOcclusionQueryuivNV(query, GL_PIXEL_COUNT_NV, &pixelCount); pErrorPixels = (100.f*pixelCount)/(GetWidth()*GetHeight()); } return pErrorPixels; } float GlRendererWidget::RenderErrors() { float pErrorPixels = -1.0f; glReadBuffer(GL_BACK); mUseFalseColors = true; SetupCamera(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable( GL_CULL_FACE ); ObjectContainer::const_iterator oi = mObjects.begin(); for (; oi != mObjects.end(); oi++) RenderIntersectable(*oi); ViewCell *viewcell = mViewCellsManager->GetViewCell(mViewPoint); QImage im1, im2; QImage diff; if (viewcell) { // read back the texture im1 = grabFrameBuffer(true); SetupCamera(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); std::map, LtSample >::const_iterator it = viewcell->GetPvs().mEntries.begin(); for (; it != viewcell->GetPvs().mEntries.end(); ++ it) { Intersectable *object = (*it).first; RenderIntersectable(object); } // read back the texture im2 = grabFrameBuffer(true); diff = im1; int x, y; int errorPixels = 0; for (y = 0; y < im1.height(); y++) for (x = 0; x < im1.width(); x++) if (im1.pixel(x, y) == im2.pixel(x, y)) diff.setPixel(x, y, qRgba(0,0,0,0)); else { diff.setPixel(x, y, qRgba(255,128,128,255)); errorPixels++; } pErrorPixels = (100.f*errorPixels)/(im1.height()*im1.width()); } // now render the pvs again SetupCamera(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mUseFalseColors = false; oi = mObjects.begin(); for (; oi != mObjects.end(); oi++) RenderIntersectable(*oi); // now render im1 if (viewcell) { if (mTopView) { mWireFrame = true; RenderMeshInstance(viewcell); mWireFrame = false; } // init ortographic projection glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, 1.0f, 0, 1.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); bindTexture(diff); glPushAttrib(GL_ENABLE_BIT); glEnable( GL_ALPHA_TEST ); glDisable( GL_CULL_FACE ); glAlphaFunc( GL_GREATER, 0.5 ); glEnable( GL_TEXTURE_2D ); glBegin(GL_QUADS); glTexCoord2f(0,0); glVertex3f(0,0,0); glTexCoord2f(1,0); glVertex3f( 1, 0, 0); glTexCoord2f(1,1); glVertex3f( 1, 1, 0); glTexCoord2f(0,1); glVertex3f(0, 1, 0); glEnd(); glPopAttrib(); // restore the projection matrix glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } return pErrorPixels; } bool GlRenderer::RenderScene() { static int glList = -1; if (glList != -1) { glCallList(glList); } else { glList = glGenLists(1); glNewList(glList, GL_COMPILE_AND_EXECUTE); ObjectContainer::const_iterator oi = mObjects.begin(); for (; oi != mObjects.end(); oi++) RenderIntersectable(*oi); glEndList(); } return true; } void GlRendererBuffer::ClearErrorBuffer() { for (int i=0; i < mPvsStatFrames; i++) { mPvsErrorBuffer[i] = 1.0f; } } void GlRendererBuffer::EvalPvsStat() { mPvsStat.Reset(); halton.Reset(); makeCurrent(); SetupProjection(GetWidth(), GetHeight()); for (int i=0; i < mPvsStatFrames; i++) { float err; RandomViewPoint(); if (mPvsErrorBuffer[i] > 0.0f) { mPvsErrorBuffer[i] = GetPixelError(); cout<<"("<= 0.0f) { if (err > mPvsStat.maxError) mPvsStat.maxError = err; mPvsStat.sumError += err; if (err == 0.0f) mPvsStat.errorFreeFrames++; mPvsStat.frames++; } } doneCurrent(); cout<pos().x(); int y = e->pos().y(); mousePoint.x = x; mousePoint.y = y; } void GlRendererWidget::mouseMoveEvent(QMouseEvent *e) { float MOVE_SENSITIVITY = Magnitude(mSceneGraph->GetBox().Diagonal())*1e-3; float TURN_SENSITIVITY=0.1f; float TILT_SENSITIVITY=32.0 ; float TURN_ANGLE= M_PI/36.0 ; int x = e->pos().x(); int y = e->pos().y(); if (e->modifiers() & Qt::ControlModifier) { mViewPoint.y += (y-mousePoint.y)*MOVE_SENSITIVITY/2.0; mViewPoint.x += (x-mousePoint.x)*MOVE_SENSITIVITY/2.0; } else { mViewPoint += mViewDirection*((mousePoint.y - y)*MOVE_SENSITIVITY); float adiff = TURN_ANGLE*(x - mousePoint.x)*-TURN_SENSITIVITY; float angle = atan2(mViewDirection.x, mViewDirection.z); mViewDirection.x = sin(angle+adiff); mViewDirection.z = cos(angle+adiff); } mousePoint.x = x; mousePoint.y = y; updateGL(); } void GlRendererWidget::mouseReleaseEvent(QMouseEvent *) { } void GlRendererWidget::resizeGL(int w, int h) { SetupProjection(w, h); updateGL(); } void GlRendererWidget::paintGL() { RenderErrors(); mFrame++; } void GlRendererWidget::SetupCamera() { if (!mTopView) GlRenderer::SetupCamera(); else { float dist = Magnitude(mSceneGraph->GetBox().Diagonal())*0.05; Vector3 pos = mViewPoint - dist*Vector3(mViewDirection.x, -1, mViewDirection.y); Vector3 target = mViewPoint + dist*mViewDirection; Vector3 up(0,1,0); glLoadIdentity(); gluLookAt(pos.x, pos.y, pos.z, target.x, target.y, target.z, up.x, up.y, up.z); } } void GlRendererWidget::keyPressEvent ( QKeyEvent * e ) { switch (e->key()) { case Qt::Key_T: mTopView = !mTopView; updateGL(); break; default: e->ignore(); break; } } void GlRendererBuffer::SampleBeamContributions(Intersectable *sourceObject, Beam &beam, const int desiredSamples, BeamSampleStatistics &stat) { // TODO: should not be done every time here // only back faces are interesting for the depth pass glShadeModel(GL_FLAT); glDisable(GL_LIGHTING); // 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 vipoint space.... int viewPointSamples = sqrt((float)desiredSamples); // the number of direction samples per pass is given by the number of viewpoints int directionalSamples = desiredSamples/viewPointSamples; 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, 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.... } void GlRendererBuffer::SampleViewpointContributions(Intersectable *sourceObject, Beam &beam, const int desiredSamples, BeamSampleStatistics &stat) { // 1. setup the view port to match the desired samples // 2. setup the projection matrix and view matrix to match the viewpoint + beam.mDirBox // 3. reset z-buffer to 0 and render the source object for the beam // with glCullFace(Enabled) and glFrontFace(GL_CW) // save result to depth map glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glEnable(GL_CULL_FACE); glEnable(GL_STENCIL_TEST); glCullFace(GL_FRONT); glColorMask(0, 0, 0, 0); // stencil is increased where the source object is located glStencilFunc(GL_ALWAYS, 0x1, 0x1); glStencilOp(GL_INCR, GL_INCR, GL_INCR); #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 // 4. set back to normal rendering and clear the ray termination depth buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glColorMask(1, 1, 1, 1); // 5. render all objects inside the beam using id based false color // (objects can be compiled to a gl list now so that subsequent rendering for // this beam is fast - the same hold for step 3) // list of objects intersected by the frustum #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) RenderIntersectable(*it); glEndList(); } glCallList(glObjList); #else ObjectContainer::const_iterator it, it_end = beam.mObjects.end(); for (it = beam.mObjects.begin(); it != it_end; ++ it) RenderIntersectable(*it); #endif // bind ray origin depth buffer glBindTexture(GL_TEXTURE_2D, depthMap); glEnable(GL_TEXTURE_2D); //Read the depth buffer into the shadow map texture glBindTexture(GL_TEXTURE_2D, depthMap); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, depthMapSize, depthMapSize); // 6. Now we have a two depth buffers defining the ray origin and termination // only rays which have non-zero entry in the origin buffer are valid since // they realy start on the object surface (this can also be tagged by setting a // stencil buffer bit at step 3) glStencilFunc(GL_EQUAL, 0x1, 0x1); // compare with second depth buffer: done in pixel shader cgGLBindProgram(sCgFragmentProgram); cgGLEnableProfile(sCgFragmentProfile); // 7. 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 GenQueries((int)beam.mViewCells.size()); // now check whether any backfacing polygon would pass the depth test 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) { glBeginOcclusionQueryNV(sQueries[queryIdx ++]); RenderIntersectable(*vit); glEndOcclusionQueryNV(); } // 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); // 8. The number of visible pixels is the number of sample rays which see the source // object from the corresponding viewcell -> rember these values for later update // of the viewcell pvs - or update immediatelly? // In general it is not neccessary to rember 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 threashold ... cgGLDisableProfile(sCgFragmentProfile); glDisable(GL_TEXTURE_2D); } void GlRendererBuffer::GenQueries(const int numQueries) { if ((int)sQueries.size() < numQueries) { const int n = numQueries - (int)sQueries.size(); int *newQueries = new int[n]; glGenOcclusionQueriesNV(n, (unsigned int *)&newQueries); for (int i = 0; i < n; ++ i) { sQueries.push_back(newQueries[i]); } delete newQueries; } }