glm::mat4 Camera::getPerspectiveProjection(const float &fov, const float &aspect, const float &zNear, const float &zFar) { glm::mat4 projMat(1.0f); float frustumScale = 1 / tan(0.5f * fov); projMat[0][0] = frustumScale / aspect; projMat[1][1] = frustumScale; projMat[2][2] = (zFar + zNear) / (zNear - zFar); projMat[3][2] = (2 * zFar * zNear) / (zNear - zFar); projMat[2][3] = -1.0f; return projMat; }
glm::mat4 Camera::getOrthoProjection(const float &left, const float &right, const float &bottom, const float &top, const float &zNear, const float &zFar) { glm::mat4 projMat(1.0f); projMat[0][0] = 2/(right - left); projMat[1][1] = 2/(top - bottom); projMat[2][2] = -2/(zFar - zNear); projMat[3][0] = -(right + left)/(right - left); projMat[3][1] = -(top + bottom)/(top - bottom); projMat[3][2] = -(zFar + zNear)/(zFar - zNear); return projMat; }
bool DecalManager::clipDecal( DecalInstance *decal, Vector<Point3F> *edgeVerts, const Point2F *clipDepth ) { PROFILE_SCOPE( DecalManager_clipDecal ); // Free old verts and indices. _freeBuffers( decal ); ClippedPolyList clipper; clipper.mNormal.set( Point3F( 0, 0, 0 ) ); clipper.mPlaneList.setSize(6); F32 halfSize = decal->mSize * 0.5f; // Ugly hack for ProjectedShadow! F32 halfSizeZ = clipDepth ? clipDepth->x : halfSize; F32 negHalfSize = clipDepth ? clipDepth->y : halfSize; Point3F decalHalfSize( halfSize, halfSize, halfSize ); Point3F decalHalfSizeZ( halfSizeZ, halfSizeZ, halfSizeZ ); MatrixF projMat( true ); decal->getWorldMatrix( &projMat ); const VectorF &crossVec = decal->mNormal; const Point3F &decalPos = decal->mPosition; VectorF newFwd, newRight; projMat.getColumn( 0, &newRight ); projMat.getColumn( 1, &newFwd ); VectorF objRight( 1.0f, 0, 0 ); VectorF objFwd( 0, 1.0f, 0 ); VectorF objUp( 0, 0, 1.0f ); // See above re: decalHalfSizeZ hack. clipper.mPlaneList[0].set( ( decalPos + ( -newRight * halfSize ) ), -newRight ); clipper.mPlaneList[1].set( ( decalPos + ( -newFwd * halfSize ) ), -newFwd ); clipper.mPlaneList[2].set( ( decalPos + ( -crossVec * decalHalfSizeZ ) ), -crossVec ); clipper.mPlaneList[3].set( ( decalPos + ( newRight * halfSize ) ), newRight ); clipper.mPlaneList[4].set( ( decalPos + ( newFwd * halfSize ) ), newFwd ); clipper.mPlaneList[5].set( ( decalPos + ( crossVec * negHalfSize ) ), crossVec ); clipper.mNormal = -decal->mNormal; Box3F box( -decalHalfSizeZ, decalHalfSizeZ ); projMat.mul( box ); DecalData *decalData = decal->mDataBlock; PROFILE_START( DecalManager_clipDecal_buildPolyList ); getContainer()->buildPolyList( box, decalData->clippingMasks, &clipper ); PROFILE_END(); clipper.cullUnusedVerts(); clipper.triangulate(); clipper.generateNormals(); if ( clipper.mVertexList.empty() ) return false; #ifdef DECALMANAGER_DEBUG mDebugPlanes.clear(); mDebugPlanes.merge( clipper.mPlaneList ); #endif decal->mVertCount = clipper.mVertexList.size(); decal->mIndxCount = clipper.mIndexList.size(); Vector<Point3F> tmpPoints; tmpPoints.push_back(( objFwd * decalHalfSize ) + ( objRight * decalHalfSize )); tmpPoints.push_back(( objFwd * decalHalfSize ) + ( -objRight * decalHalfSize )); tmpPoints.push_back(( -objFwd * decalHalfSize ) + ( -objRight * decalHalfSize )); Point3F lowerLeft(( -objFwd * decalHalfSize ) + ( objRight * decalHalfSize )); projMat.inverse(); _generateWindingOrder( lowerLeft, &tmpPoints ); BiQuadToSqr quadToSquare( Point2F( lowerLeft.x, lowerLeft.y ), Point2F( tmpPoints[0].x, tmpPoints[0].y ), Point2F( tmpPoints[1].x, tmpPoints[1].y ), Point2F( tmpPoints[2].x, tmpPoints[2].y ) ); Point2F uv( 0, 0 ); Point3F vecX(0.0f, 0.0f, 0.0f); // Allocate memory for vert and index arrays _allocBuffers( decal ); Point3F vertPoint( 0, 0, 0 ); for ( U32 i = 0; i < clipper.mVertexList.size(); i++ ) { const ClippedPolyList::Vertex &vert = clipper.mVertexList[i]; vertPoint = vert.point; // Transform this point to // object space to look up the // UV coordinate for this vertex. projMat.mulP( vertPoint ); // Clamp the point to be within the quad. vertPoint.x = mClampF( vertPoint.x, -decalHalfSize.x, decalHalfSize.x ); vertPoint.y = mClampF( vertPoint.y, -decalHalfSize.y, decalHalfSize.y ); // Get our UV. uv = quadToSquare.transform( Point2F( vertPoint.x, vertPoint.y ) ); const RectF &rect = decal->mDataBlock->texRect[decal->mTextureRectIdx]; uv *= rect.extent; uv += rect.point; // Set the world space vertex position. decal->mVerts[i].point = vert.point; decal->mVerts[i].texCoord.set( uv.x, uv.y ); decal->mVerts[i].normal = clipper.mNormalList[i]; decal->mVerts[i].normal.normalize(); if( mFabs( decal->mVerts[i].normal.z ) > 0.8f ) mCross( decal->mVerts[i].normal, Point3F( 1.0f, 0.0f, 0.0f ), &vecX ); else if ( mFabs( decal->mVerts[i].normal.x ) > 0.8f ) mCross( decal->mVerts[i].normal, Point3F( 0.0f, 1.0f, 0.0f ), &vecX ); else if ( mFabs( decal->mVerts[i].normal.y ) > 0.8f ) mCross( decal->mVerts[i].normal, Point3F( 0.0f, 0.0f, 1.0f ), &vecX ); decal->mVerts[i].tangent = mCross( decal->mVerts[i].normal, vecX ); } U32 curIdx = 0; for ( U32 j = 0; j < clipper.mPolyList.size(); j++ ) { // Write indices for each Poly ClippedPolyList::Poly *poly = &clipper.mPolyList[j]; AssertFatal( poly->vertexCount == 3, "Got non-triangle poly!" ); decal->mIndices[curIdx] = clipper.mIndexList[poly->vertexStart]; curIdx++; decal->mIndices[curIdx] = clipper.mIndexList[poly->vertexStart + 1]; curIdx++; decal->mIndices[curIdx] = clipper.mIndexList[poly->vertexStart + 2]; curIdx++; } if ( !edgeVerts ) return true; Point3F tmpHullPt( 0, 0, 0 ); Vector<Point3F> tmpHullPts; for ( U32 i = 0; i < clipper.mVertexList.size(); i++ ) { const ClippedPolyList::Vertex &vert = clipper.mVertexList[i]; tmpHullPt = vert.point; projMat.mulP( tmpHullPt ); tmpHullPts.push_back( tmpHullPt ); } edgeVerts->clear(); U32 verts = _generateConvexHull( tmpHullPts, edgeVerts ); edgeVerts->setSize( verts ); projMat.inverse(); for ( U32 i = 0; i < edgeVerts->size(); i++ ) projMat.mulP( (*edgeVerts)[i] ); return true; }
//! @brief render big software image of any size using either screen to render or offscreen texture ( can flicker ) bool renderToImage( IrrlichtDevice* device, IImage* dst, s32 nSamples, const SColor& clearColor, bool renderGUI, bool debugLog) { // print call params if (debugLog) printf( "renderToImage( nSamples=%i, clearColor(%i,%i,%i,%i)\n", nSamples, clearColor.getAlpha(), clearColor.getRed(), clearColor.getGreen(), clearColor.getBlue() ); // abort if (!device) { printf("No Irrlicht-Device found.\n"); return false; } // abort if (!dst) { printf("No destination image found.\n"); return false; } // local pointers IVideoDriver* driver = device->getVideoDriver(); gui::IGUIEnvironment* guienv = device->getGUIEnvironment(); scene::ISceneManager* smgr = device->getSceneManager(); scene::ICameraSceneNode* camera = smgr->getActiveCamera(); // abort if (!camera) { printf("No active camera found.\n"); return false; } //! decompose camera's projection-matrix in d3d semantics core::matrix4 projMat = camera->getProjectionMatrix(); bool IsOrthogonal = true; if ( core::equals( projMat(3,3), 0.f ) ) IsOrthogonal = false; if ( !core::equals( projMat(2,3), 0.f ) ) IsOrthogonal = false; f32 Left; f32 Right; f32 Bottom; f32 Top; f32 Near; f32 Far; if (IsOrthogonal) { Near = -projMat(3,2)/projMat(2,2); Far = -(projMat(3,2)-1.f)/projMat(2,2); Left = -(projMat(3,0)+1.f)/projMat(0,0); Right = -(projMat(3,0)-1.f)/projMat(0,0); Top = -(projMat(3,1)-1.f)/projMat(1,1); Bottom = -(projMat(3,1)+1.f)/projMat(1,1); } else { Near = -projMat(3,2)/projMat(2,2); Far = -projMat(3,2)/(projMat(2,2)-1.f); Left = -Near*(projMat(2,0)+1.f)/projMat(0,0); Right = -Near*(projMat(2,0)-1.f)/projMat(0,0); Top = -Near*(projMat(2,1)-1.f)/projMat(1,1); Bottom = -Near*(projMat(2,1)+1.f)/projMat(1,1); } Near = camera->getNearValue(); // take the values directly from Camera Far = camera->getFarValue(); // due to f32 rounding errors //! ---------------------------------------------------------------------- //! calculate Border / get max thickness used by all registered materials //! ---------------------------------------------------------------------- s32 Border = 0; f32 thickness = 0.0f; const core::list<scene::ISceneNode*>& children = smgr->getRootSceneNode()->getChildren(); core::list<scene::ISceneNode*>::ConstIterator it = children.begin(); while (it != children.end()) { scene::ISceneNode* node = (*it); if (node) { u32 matCount = node->getMaterialCount(); for (u32 m = 0; m < matCount; ++m) { if (thickness < node->getMaterial(m).Thickness) thickness = node->getMaterial(m).Thickness; } } it++; } Border = core::ceil32( thickness ); //! ------------------------------------------------------------------------------------------ //! choose render-mode: offscreen or onscreen depending on AA, nSamples and RTT capabilities //! ------------------------------------------------------------------------------------------ //! @var nSamples - AntiAliasing depth > 1 make bigger rtts or downscale render-result by factor nSamples while having filter-methods enabled //! the framebuffer will always be downscaled by fast software bilinear filtering using colorkey/transparency to blend better into final image //! hardware-scaling: bilinear, trilinear or anisotropic texture-filtering available with irrlicht-engine //! software scaling: nearest, bilinear, bicubic downscaling available: //! bilinear has best compromise for downscaling ( there is much more information-loss due to resize than interpolation) //! default: 1 (<2) - no AA/downscaling done //! @var clearColor - argb32 color, the final image will filled with //! before writing into it and be used as colorkey for AA/software bilinear downscaling operation //! @var DoOffscreen - default=false, onscreen (framebuffer) //! false - onscreen-rendering using framebuffer/display. //! doublebuffer is possible, but will not prevent the showing of each rendered tile-texture //! true - offscreen-rendering using render-target-textures bool DoOffscreen = false; //! ---------------------------------------------------------------- //! collect video-driver infos //! ---------------------------------------------------------------- const core::dimension2du ScreenSize = driver->getScreenSize(); const ECOLOR_FORMAT ScreenFormat = driver->getColorFormat(); const u32 ScreenBits = IImage::getBitsPerPixelFromFormat( ScreenFormat ); const io::IAttributes& info = driver->getDriverAttributes(); const core::dimension2du MaxRTTSize = driver->getMaxTextureSize(); const u32 MaxAA = info.getAttributeAsInt( "AntiAlias" ); const u32 MaxAF = info.getAttributeAsInt( "MaxAnisotropy" ); const bool HasNPOT = driver->queryFeature( EVDF_TEXTURE_NPOT ); const bool HasNSQR = driver->queryFeature( EVDF_TEXTURE_NSQUARE ); const bool HasRTT = driver->queryFeature( EVDF_RENDER_TO_TARGET); const bool HasMTT = driver->queryFeature( EVDF_MULTITEXTURE); const bool HasMRT = driver->queryFeature( EVDF_MULTIPLE_RENDER_TARGETS); const bool HasATC = driver->queryFeature( EVDF_ALPHA_TO_COVERAGE); const bool HasBTF = driver->queryFeature( EVDF_BILINEAR_FILTER); const bool HasCMK = driver->queryFeature( EVDF_COLOR_MASK); const bool HasMMP = driver->queryFeature( EVDF_MIP_MAP); const bool HasMMA = driver->queryFeature( EVDF_MIP_MAP_AUTO_UPDATE); const bool HasOCC = driver->queryFeature( EVDF_OCCLUSION_QUERY); const bool HasPOF = driver->queryFeature( EVDF_POLYGON_OFFSET); if (debugLog) { printf("ScreenSize = %i x %i x %i\n", ScreenSize.Width, ScreenSize.Height, ScreenBits); printf("MaxRTTSize = %i x %i\n", MaxRTTSize.Width, MaxRTTSize.Height); printf("MaxAA = %i\n", MaxAA); printf("MaxAF = %i\n", MaxAF); printf("HasNPOT = %s\n", HasNPOT?"true":"false"); printf("HasNSQR = %s\n", HasNSQR?"true":"false"); printf("HasRTT = %s\n", HasRTT?"true":"false"); printf("HasMTT = %s\n", HasMTT?"true":"false"); printf("HasMRT = %s\n", HasMRT?"true":"false"); printf("HasATC = %s\n", HasATC?"true":"false"); printf("HasBTF = %s\n", HasBTF?"true":"false"); printf("HasCMK = %s\n", HasCMK?"true":"false"); printf("HasMMP = %s\n", HasMMP?"true":"false"); printf("HasMMA = %s\n", HasMMA?"true":"false"); printf("HasOCC = %s\n", HasOCC?"true":"false"); printf("HasPOF = %s\n", HasPOF?"true":"false"); } s32 RTTWidth = ScreenSize.Width; // equals the actual rtt size before down scaling factor nSamples AA and writing to final Image s32 RTTHeight = ScreenSize.Height; // the render-target is always a texture (offscreen) or the framebuffer (onscreen) s32 RTTWidthNB = RTTWidth - 2*Border; // the border equals the overlapping during rendering due to material-thickness > 0 s32 RTTHeightNB = RTTHeight - 2*Border; // the area that gets written to unique places, while the border regions can overlapp ( same pos, but writing/adding different image-content ) // so the position increment for the final writing is always RTTWidthNB or RTTHeightNB const s32 ImageWidth = (s32)dst->getDimension().Width; // width of the final image const s32 ImageHeight = (s32)dst->getDimension().Height; // height of the final image const ECOLOR_FORMAT ImageFormat = dst->getColorFormat(); // color-format of the final image, i.e. ECF_A8R8G8B8, ECF_R8G8B8, ECF_R16 for heightmaps ( bilinear, anisotropic ) const s32 CountX = core::ceil32( (f32)ImageWidth / (f32)RTTWidthNB ); const s32 CountY = core::ceil32( (f32)ImageHeight / (f32)RTTHeightNB ); //! ------------------------------------------------------------------------------------------ //! start render loop //! ------------------------------------------------------------------------------------------ s32 IndexX = 0; s32 IndexY = 0; s32 dstX = -Border; s32 dstY = -Border; for ( IndexY = 0; IndexY < CountY; ++IndexY) { // reset row dstX = -Border; for ( IndexX = 0; IndexX < CountX; ++IndexX) { const f32 fLeft = Left + (Right - Left) * (f32)( IndexX * RTTWidthNB - Border )/(f32)ImageWidth; const f32 fRight = fLeft + (Right - Left) * (f32)RTTWidth / (f32)ImageWidth; const f32 fTop = Top - (Top - Bottom) * (f32)( IndexY * RTTHeightNB - Border )/(f32)ImageHeight; const f32 fBottom = fTop - (Top - Bottom) * (f32)RTTHeight / (f32)ImageHeight; const f32 fNear = Near; const f32 fFar = Far; //! ------------------------------------------------------------------------------------------ //! build and set current projection-matrix //! ------------------------------------------------------------------------------------------ core::matrix4 tmpProj = core::IdentityMatrix; if (IsOrthogonal) { tmpProj(0,0) = 2.f / (fRight - fLeft); tmpProj(1,1) = 2.f / (fTop - fBottom); tmpProj(2,2) = 1.f / (fFar - fNear); tmpProj(3,3) = 1.f; tmpProj(3,0) = (fLeft + fRight) / (fLeft - fRight); tmpProj(3,1) = (fTop + fBottom) / (fBottom - fTop); tmpProj(3,2) = fNear / (fNear - fFar); } else { tmpProj(0,0) = 2.f*fNear / (fRight - fLeft); tmpProj(1,1) = 2.f*fNear / (fTop - fBottom); tmpProj(2,0) = (fLeft + fRight) / (fLeft - fRight); tmpProj(2,1) = (fTop + fBottom) / (fBottom - fTop); tmpProj(2,2) = fFar / (fFar - fNear); tmpProj(2,3) = 1.f; tmpProj(3,2) = fNear*fFar / (fNear - fFar); tmpProj(3,3) = 0.f; } camera->setProjectionMatrix(tmpProj); //! ------------------------------------------------------------------------------------------ //! now render all to current render-target //! ------------------------------------------------------------------------------------------ driver->beginScene( true, true, clearColor ); // SColor(255,200,200,255) smgr->drawAll(); if (renderGUI) guienv->drawAll(); driver->endScene(); //! ------------------------------------------------------------------------------------------ //! write current render-target to final image //! ------------------------------------------------------------------------------------------ IImage* src = driver->createScreenShot( ImageFormat, ERT_FRAME_BUFFER); if (src) { s32 x2 = src->getDimension().Width; s32 y2 = src->getDimension().Height; blitImageToImage( dst, core::position2di(dstX, dstY), src, core::recti(Border, Border, x2-Border, y2-Border), debugLog ); src->drop(); } dstX += RTTWidthNB; } dstY += RTTHeightNB; } //! ------------------------------------------------------------------------------------------ //! reset camera's projection-matrix //! ------------------------------------------------------------------------------------------ camera->setProjectionMatrix( projMat, IsOrthogonal); return true; }