// EMERGENT GAME TECHNOLOGIES PROPRIETARY INFORMATION // // This software is supplied under the terms of a license agreement or // nondisclosure agreement with Emergent Game Technologies and may not // be copied or disclosed except in accordance with the terms of that // agreement. // // Copyright (c) 1996-2007 Emergent Game Technologies. // All Rights Reserved. // // Emergent Game Technologies, Chapel Hill, North Carolina 27517 // http://www.emergent.net #include "StdAfx.h" #include "Utility.h" #ifndef CODE_INGAME #include "ShadowGeometry.h" //#include "TerrainManager.h" //#include "WorldManager.h" #include #include #include #include #include #include #include #include #include #include #include #include "Terrain.h" #include // 模拟平行光摄象机到目标的距离。必须是一个很大的值 #define CAM_DISTANCE 200 //--------------------------------------------------------------------------- ShadowGeometry::ShadowGeometry(CTerrain* pkTerrain, unsigned int uiMaxTriangleCount, NiPoint3 kLightDir, int iChunkID, float fDensity) : m_kCastingObjects(20), m_kSkinnedCastingObjects(4), m_bForceRender(false), m_kVisible(8, 8), m_kCuller(&m_kVisible), m_pkTerrain(pkTerrain), m_iChunkID(iChunkID) { // the direction of the infinite light casting the shadow. m_kLightDir = kLightDir; m_kCenterP.x = 32, m_kCenterP.y = 32, m_kCenterP.z = 0; // the camera used to project the shadow caster to create the shadow mask // this camera is a distant perspective camera with a narrow FOV, to // approximate a parallel camera. m_spCamera = NiNew NiCamera; m_spCamera->SetTranslate(0.0f, 0.0f, CAM_DISTANCE); NiPoint3 kTarget = m_kCenterP;//m_spCaster->GetWorldBound().GetCenter(); float fRadius = 32 * 1.414f; m_kBound.SetCenter(m_kCenterP); m_kBound.SetRadius(fRadius); UpdateShadowCamera(0.0f); // set the field of view of the camera to enclose the bounding sphere of // the caster object. NiFrustum kFrust = m_spCamera->GetViewFrustum(); kFrust.m_fLeft = -fRadius; kFrust.m_fRight = fRadius; kFrust.m_fTop = fRadius; kFrust.m_fBottom = -fRadius; kFrust.m_fNear = 1; kFrust.m_fFar = CAM_DISTANCE * 2; kFrust.m_bOrtho = true; m_spCamera->SetViewFrustum(kFrust); m_spCamera->Update(0.0f); // The following (master) properties are used to batch render the shadow // casting objects. These properties ensure that the shadow casters // appear opaque and flat, the color of the desired shadow. If these // were not used, the shadow could contain surface colors of the casters' // textures, which would look incorrect. m_spPropertyState = NiNew NiPropertyState; NiVertexColorProperty* pkVC = NiNew NiVertexColorProperty; pkVC->SetSourceMode(NiVertexColorProperty::SOURCE_IGNORE); pkVC->SetLightingMode(NiVertexColorProperty::LIGHTING_E); m_spPropertyState->SetProperty(pkVC); NiMaterialProperty* pkMat = NiNew NiMaterialProperty; float fColor = min(0.99, max(0.01f, 1.0f-fDensity)); pkMat->SetEmittance(NiColor(fColor, fColor, fColor)); pkMat->SetAlpha(0.6f); m_spPropertyState->SetProperty(pkMat); NiTexturingProperty* pkTex = NiNew NiTexturingProperty; m_spPropertyState->SetProperty(pkTex); NiShadeProperty* pkShade = NiNew NiShadeProperty; pkShade->SetSmooth(false); m_spPropertyState->SetProperty(pkShade); if (uiMaxTriangleCount == 0) m_uiMaxTriangleCount = TRIANGLE_COUNT; else m_uiMaxTriangleCount = uiMaxTriangleCount; m_uiMaxVertexCount = 3 * m_uiMaxTriangleCount; // Create the shadow geometry - this is a dynamic trishape that will be // refilled each frame to include ground (recipient) triangles that fall // within the shadow frustum. It must be marked as dynamic, as both the // geometry and the vertex/triangle counts will change each frame NiPoint3* pkVertex = NiNew NiPoint3[m_uiMaxVertexCount]; NIASSERT(pkVertex); NiPoint2* pkTexC = NiNew NiPoint2[m_uiMaxVertexCount]; NIASSERT(pkTexC); unsigned short* pusConnect = NiAlloc(unsigned short, m_uiMaxTriangleCount * 3); NIASSERT(pusConnect); NiTriShapeDynamicData* pkTriData = NiNew NiTriShapeDynamicData( m_uiMaxVertexCount, pkVertex, 0, 0, pkTexC, 1, NiGeometryData::NBT_METHOD_NONE, m_uiMaxTriangleCount, pusConnect); NIASSERT(pkTriData); m_spShadowGeometry = NiNew NiTriShape(pkTriData); NIASSERT(m_spShadowGeometry); m_spShadowGeometry->SetName("ShadowGeometry"); m_spShadowGeometry->SetActiveVertexCount(0); m_spShadowGeometry->SetActiveTriangleCount(0); // The shadow is alpha blended, so it must not write the ZBuffer. NiZBufferProperty* pkZ = NiNew NiZBufferProperty; NIASSERT(pkZ); pkZ->SetZBufferTest(false); pkZ->SetZBufferWrite(false); m_spShadowGeometry->AttachProperty(pkZ); // The shadow is a darkmap, so is multiplies the framebuffer color //NiAlphaProperty* pAlpha = NiNew NiAlphaProperty; //pAlpha->SetAlphaBlending(true); //pAlpha->SetSrcBlendMode(NiAlphaProperty::ALPHA_ZERO); //pAlpha->SetDestBlendMode(NiAlphaProperty::ALPHA_SRCCOLOR); //m_spShadowGeometry->AttachProperty(pAlpha); } //---------------------------------------------------------------------------- ShadowGeometry* ShadowGeometry::Create(CTerrain* pkTerrain, NiPoint3 kLightDir, int iChunkID, float fDensity, unsigned int uiDetail, unsigned int uiMaxTriangleCount, const char* pcFileName) { ShadowGeometry* pkThis = NiNew ShadowGeometry(pkTerrain, uiMaxTriangleCount, kLightDir, iChunkID, fDensity); unsigned int uiTexWidth; NiRenderer* pkRenderer = 0; if (!pcFileName) { // No texture file specified, so we must create a rendered texture // that will be update each frame pkRenderer = NiRenderer::GetRenderer(); NIASSERT(pkRenderer); // We match the texture to the depth of the backbuffer for optimal // performance NiTexture::FormatPrefs kPrefs; const NiRenderTargetGroup* pkRTGroup = NiRenderer::GetRenderer()->GetDefaultRenderTargetGroup(); const NiPixelFormat* pkPixelFormat = pkRTGroup->GetPixelFormat(0); if (pkPixelFormat->GetBitsPerPixel() == 16) kPrefs.m_ePixelLayout = NiTexture::FormatPrefs::HIGH_COLOR_16; else kPrefs.m_ePixelLayout = NiTexture::FormatPrefs::TRUE_COLOR_32; //uiTexWidth = 1 << uiDetail; uiTexWidth = 1024; NiRenderedTexture* pkRenderedTexture = NiRenderedTexture::Create( uiTexWidth, uiTexWidth, pkRenderer, kPrefs); pkThis->m_spTexture = pkRenderedTexture; pkThis->m_spRenderTargetGroup = NiRenderTargetGroup::Create( pkRenderedTexture->GetBuffer(), pkRenderer, true, true); pkThis->m_bRendered = true; } else { // A shadow texture filename was specified - just load the shadow // texture and use it each frame. Note that since there is no way of // specifying a specific orientation of the shadow texture as it // moves to match the caster, the shadow texture should be // rotationally symmetric. NiTexture::FormatPrefs kPrefs; kPrefs.m_eMipMapped = NiTexture::FormatPrefs::NO; pkThis->m_spTexture = NiSourceTexture::Create(pcFileName, kPrefs); NIASSERT(pkThis->m_spTexture); pkThis->m_spRenderTargetGroup = NULL; pkThis->m_bRendered = false; } if (!pkThis->m_spTexture) { // WorldManager::Log("Cannot create shadow texture\n"); NiDelete pkThis; return NULL; } // Since we may generate texture coordinates outside of the range [0,1], // we must use clamping to avoid strange wrapping artifacts. NiTexturingProperty::Map* pkMap = NiNew NiTexturingProperty::Map( pkThis->m_spTexture, 0, NiTexturingProperty::CLAMP_S_CLAMP_T, NiTexturingProperty::FILTER_BILERP); NIASSERT(pkMap); NiTexturingProperty* pkTex = NiNew NiTexturingProperty(); NIASSERT(pkTex); pkTex->SetBaseMap(pkMap); pkTex->SetApplyMode(NiTexturingProperty::APPLY_REPLACE); pkThis->m_spShadowGeometry->AttachProperty(pkTex); pkThis->m_spShadowGeometry->UpdateProperties(); // Clear shadow texture if (pkThis->m_bRendered) { pkRenderer->BeginOffScreenFrame(); pkRenderer->BeginUsingRenderTargetGroup(pkThis->m_spRenderTargetGroup, NiRenderer::CLEAR_ALL); pkRenderer->EndUsingRenderTargetGroup(); pkRenderer->EndOffScreenFrame(); // Set camera port so that outer texels are never drawn to. float fFraction = 2.0f / ((float)uiTexWidth); NiRect kPort(fFraction, 1.0f - fFraction, 1.0f - fFraction, fFraction); pkThis->m_spCamera->SetViewPort(kPort); } //pkThis->AddCaster(pkTerrain->GetTerrainRootNode()); return pkThis; } //---------------------------------------------------------------------------- ShadowGeometry::~ShadowGeometry() { } //--------------------------------------------------------------------------- void ShadowGeometry::AddCaster(NiAVObject* pkCaster) { //m_spCaster = pkCaster; // Traverse the new caster to find all of the leaf geometry objects. // Note that this will not clear out the list of caster geometry, and // will thus _add_ caster geometry to the shadow system. However, only // the most recently-set caster will affect the centerpoint and bounding // sphere of the shadow //if (m_bRendered) if (pkCaster != NULL) { RecursiveStoreCasterObjects(pkCaster); //m_kBound.Merge(&(pkCaster->GetWorldBound())); } } //--------------------------------------------------------------------------- void ShadowGeometry::RecursiveStoreCasterObjects(NiAVObject* pkObject) { if (pkObject->GetAppCulled()) { return; } // Find all leaf geometry objects and add them to the caster geometry // list, to be rendered as batch if (NiIsKindOf(NiGeometry, pkObject)) { NiGeometry* pkGeometry = (NiGeometry*)pkObject; //if (pkGeometry->GetSkinInstance() == NULL) m_kCastingObjects.Add(pkObject); //else // m_kSkinnedCastingObjects.Add(pkObject); } else if (NiIsKindOf(NiNode, pkObject)) { NiNode* pkNode = (NiNode*)pkObject; for (unsigned int i = 0; i < pkNode->GetArrayCount(); i++) { NiAVObject* pkChild = pkNode->GetAt(i); if (pkChild) RecursiveStoreCasterObjects(pkChild); } } } //---------------------------------------------------------------------------- void ShadowGeometry::Click(float fTime, NiCamera* pkSceneCamera, bool bVisible, bool bUpdateImage) { // NIASSERT(m_spCaster && m_spTexture); NIASSERT(m_spTexture); // If the visible flag is false, clear out the shadow geometry and skip // rendering the shadow, as it won't matter to the final, rendered image. if (!bVisible) { m_spShadowGeometry->SetActiveVertexCount(0); m_spShadowGeometry->SetActiveTriangleCount(0); return; } // Cull shadow geometry against the scene to avoid updating if (m_bForceRender) { m_bForceRender = false; } //else if (pkSceneCamera && m_spShadowGeometry->GetActiveVertexCount()) //{ // const NiFrustumPlanes& kPlanes = m_kCuller.GetFrustumPlanes(); // // for (unsigned int i = NiFrustumPlanes::LEFT_PLANE; // i < NiFrustumPlanes::MAX_PLANES; i++) // { // NiPlane kPlane = kPlanes.GetPlane(i); // int iSide = // m_spShadowGeometry->GetWorldBound().WhichSide(kPlane); // if (iSide == NiPlane::NEGATIVE_SIDE) // { // // Update bounds regardless to ensure proper culling // UpdateShadowGeometryBound(); // return; // } // } //} // move the shadow camera (and thus the apparent light) UpdateShadowCamera(fTime); if (m_bRendered && bUpdateImage) { NiRenderer* pkRenderer = NiRenderer::GetRenderer(); NIASSERT(pkRenderer); // update the background color to the renderer -- note it clears alpha // channel to completely transparent value to get an alpha rendered // texture pkRenderer->SetBackgroundColor(NiColor::WHITE); // 先渲染地形,将地形的深度信息写入 z buffer NiVisibleArray kVisibleArray; NiCullingProcess kCullProcess(&kVisibleArray); NiEntityRenderingContext kRenderingContext; kRenderingContext.m_pkCamera = m_spCamera; kRenderingContext.m_pkCullingProcess = &kCullProcess; kRenderingContext.m_pkRenderer = pkRenderer; pkRenderer->BeginUsingRenderTargetGroup(m_spRenderTargetGroup, NiRenderer::CLEAR_ALL); pkRenderer->SetCameraData(m_spCamera); NiCullScene(m_spCamera, m_pkTerrain->GetTerrainRootNode(), kCullProcess, kVisibleArray, true); NiDrawVisibleArray(m_spCamera, kVisibleArray); pkRenderer->EndUsingRenderTargetGroup(); pkRenderer->BeginUsingRenderTargetGroup(m_spRenderTargetGroup, NiRenderer::CLEAR_BACKBUFFER); //CLEAR_NONE // renderer camera port settings pkRenderer->SetCameraData(m_spCamera); kVisibleArray.RemoveAll(); for (unsigned int i = 0; i < m_kCastingObjects.GetSize(); i++) { NiGeometry* pkGeom = NiSmartPointerCast(NiGeometry, m_kCastingObjects.GetAt(i)); //pkRenderer->SetCameraData(m_spCamera); NiCullScene(m_spCamera, pkGeom, kCullProcess, kVisibleArray, false); } NiDrawVisibleArray(m_spCamera, kVisibleArray); pkRenderer->EndUsingRenderTargetGroup(); } // Determine which triangles fall inside of the shadow frustum. UpdateShadowGeometry(); // Debug 代码,记得注释掉. //SaveTextureToDDS(m_spTexture, "e:\\ShadowGeometryTex.dds"); //m_spShadowGeometry->RemoveProperty(NiTexturingProperty::GetType()); //NiStream kStream; //kStream.InsertObject(m_spShadowGeometry); //kStream.Save("shadowGeom.nif"); } //--------------------------------------------------------------------------- void ShadowGeometry::UpdateShadowCamera(float fTime) { // this function moves the shadow camera so that it appears to view the // target (caster) from infinity, facing a fixed direction. This is done // by moving the camera so the the desired fixed direction vector is // always coincident with the line through the caster's bounding volume // center and the camera location if (!m_spCamera) return; //m_kBound = m_spShadowGeometry->GetWorldBound(); // get the "look at" point // NiPoint3 kTarget = m_kBound.GetCenter();//m_spCaster->GetWorldBound().GetCenter(); int iNumX = m_iChunkID % m_pkTerrain->GetChunkNumX(); int iNumY = m_iChunkID / m_pkTerrain->GetChunkNumX(); NiPoint3 kTarget((iNumX+0.5f) * GRIDINCHUNK, (iNumY+0.5f) * GRIDINCHUNK, 0.0f); m_kBound.SetCenter(kTarget); // translate the camera to a distant point such that the camera is looking // directly at the target point NiPoint3 kTranslate = kTarget - (m_kLightDir * CAM_DISTANCE); NiMatrix3 kRotation; NiPoint3 kUpAxis = NiPoint3::UNIT_Z; if (kTranslate.Cross(kUpAxis).Length() != 0.0f) { kRotation = NiViewMath::LookAt(kTarget, kTranslate, kUpAxis); } else { NiPoint3 kNewUpAxis; if ((kTranslate.Dot(NiPoint3::UNIT_Z) < NiViewMath::PARALLEL_THRESHOLD) && (kTranslate.Dot(NiPoint3::UNIT_Z) > -NiViewMath::PARALLEL_THRESHOLD)) { kNewUpAxis = NiPoint3::UNIT_Z; } else if ((kTranslate.Dot(NiPoint3::UNIT_Y) < NiViewMath::PARALLEL_THRESHOLD) && (kTranslate.Dot(NiPoint3::UNIT_Y) > -NiViewMath::PARALLEL_THRESHOLD)) { kNewUpAxis = NiPoint3::UNIT_Y; } else { kNewUpAxis = NiPoint3::UNIT_X; } kRotation = NiViewMath::LookAt(kTarget, kTranslate, kNewUpAxis); } m_spCamera->SetTranslate(kTranslate); m_spCamera->SetRotate(kRotation); m_spCamera->Update(0.0f); } //--------------------------------------------------------------------------- void ShadowGeometry::UpdateShadowGeometry() { m_spShadowGeometry->SetActiveVertexCount(0); m_spShadowGeometry->SetActiveTriangleCount(0); // Find the projection of the camera location onto the terrain -- it // represents the center of our shadow geometry -- this world space // point is used during texture coordinate generation in AddShadowTriangle // 求出灯光摄像机与地形的交点 NiPick kPick; kPick.SetTarget(m_pkTerrain->GetTerrainRootNode()); kPick.SetPickType(NiPick::FIND_FIRST); kPick.SetIntersectType(NiPick::TRIANGLE_INTERSECT); kPick.SetFrontOnly(true); if (kPick.PickObjects(m_spCamera->GetWorldLocation(), m_spCamera->GetWorldDirection())) { NiPick::Results& results = kPick.GetResults(); results.FindClosest(); NiPick::Record* pRecord = results.GetAt(0); m_kCenterP = pRecord->GetIntersection(); } // Calculate an approximate width for the shadow geometry -- used // in texture coordinate generation float fRadius = m_kBound.GetRadius(); //m_spCaster->GetWorldBound().GetRadius(); m_fOOWidth = 1.0f / (fRadius * 2.0f); // Traverse the ground object using frustum culling for quick outs // The ground geometry should ideally be diced into a quad-tree // or some other data structure to allow maximum benefits from // frustum culling. TraverseGroundGeometry(m_pkTerrain->GetTerrainRootNode()); m_spShadowGeometry->GetModelData()->MarkAsChanged( NiGeometryData::VERTEX_MASK | NiGeometryData::TEXTURE_MASK | NiTriBasedGeomData::TRIANGLE_INDEX_MASK | NiTriBasedGeomData::TRIANGLE_COUNT_MASK); UpdateShadowGeometryBound(); } //--------------------------------------------------------------------------- void ShadowGeometry::UpdateShadowGeometryBound() { // Manually set the bounding sphere of the shadow geometry to ensure // it is not culled by the engine //float fRadius = m_spCaster->GetWorldBound().GetRadius(); float fRadius = m_kBound.GetRadius(); NiBound kSphere; kSphere.SetCenterAndRadius(m_kBound.GetCenter(), //m_spCaster->GetWorldBound().GetCenter(), fRadius);// * 1.05f m_spShadowGeometry->SetModelBound(kSphere); m_spShadowGeometry->Update(0.0f); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- void ShadowGeometry::TraverseGroundGeometry(NiAVObject* pkObject) { NiAVObject* pkChunkGeom = ((NiNode*)pkObject)->GetAt(m_iChunkID); AddToShadowGeometry(NiDynamicCast(NiTriBasedGeom, pkChunkGeom)); } //--------------------------------------------------------------------------- void ShadowGeometry::AddToShadowGeometry(NiTriBasedGeom* pkTriGeom) { NIASSERT(pkTriGeom); // This code assumes that model space vertices ARE in world space NiPoint3* pkWorldVerts = pkTriGeom->GetVertices(); NIASSERT(pkWorldVerts); NiTriBasedGeomData* pkTriData = (NiTriBasedGeomData*)pkTriGeom->GetModelData(); NIASSERT(pkTriData); unsigned short s0; unsigned short s1; unsigned short s2; NiPoint3* pkP0; NiPoint3* pkP1; NiPoint3* pkP2; unsigned short usTris = pkTriData->GetTriangleCount(); for (unsigned short i = 0; i < usTris; i++) { // Clip each world triangle to the camera's frustum pkTriData->GetTriangleIndices(i, s0, s1, s2); pkP0 = &pkWorldVerts[s0]; pkP1 = &pkWorldVerts[s1]; pkP2 = &pkWorldVerts[s2]; CullTriAgainstCameraFrustum(*pkP0, *pkP1, *pkP2); } } //--------------------------------------------------------------------------- bool ShadowGeometry::GenerateCameraRay(unsigned int uiIndex, NiPoint3& kPt, NiPoint3& kDir) { const NiFrustum& kFrust = m_spCamera->GetViewFrustum(); float fVx = (uiIndex & 0x1) ? kFrust.m_fRight : kFrust.m_fLeft; float fVy = (uiIndex & 0x2) ? kFrust.m_fTop : kFrust.m_fBottom; // convert world view plane coordinates to ray with kDir and kOrigin // kDir: camera world location to view plane coordinate // kOrigin: camera world location m_spCamera->ViewPointToRay(fVx, fVy, kPt, kDir); return true; } //--------------------------------------------------------------------------- void ShadowGeometry::CullTriAgainstCameraFrustum(NiPoint3& kV0, NiPoint3& kV1, NiPoint3& kV2) { NiPoint3 akTri[3]; akTri[0] = kV0; akTri[1] = kV1; akTri[2] = kV2; AddShadowTriangle(akTri); } //--------------------------------------------------------------------------- void ShadowGeometry::AddShadowTriangle(NiPoint3 akV[3]) { unsigned short usVertCount = m_spShadowGeometry->GetActiveVertexCount(); unsigned short usTriCount = m_spShadowGeometry->GetActiveTriangleCount(); if ((unsigned int)(usVertCount + 3) <= m_uiMaxVertexCount && (unsigned int)(usTriCount + 1) <= m_uiMaxTriangleCount) { // For texture coordinates, generate s and t based on point Q's // distance along the RIGHT(R) and UP(U) relative to the // projection of the camera's location(P) (assuming non-skewed // frustum) onto the ground geometry. // // s = (R * (Q - P)) / width - 0.5 // t = (U * (Q - P)) / height - 0.5 NiPoint2* pkTex = m_spShadowGeometry->GetTextures(); NIASSERT(pkTex); for (unsigned int i = 0; i < 3; i++) { NiPoint3 kDiff = akV[i] - m_kCenterP; pkTex[usVertCount + i].x = (m_spCamera->GetWorldRightVector() * kDiff) * m_fOOWidth + 0.5f; // Negated direction is due to rendered texture being inverted pkTex[usVertCount + i].y = (-m_spCamera->GetWorldUpVector() * kDiff) * m_fOOWidth + 0.5f; } // We know z-axis is up and in the case of MOUT, the // units are in inches, so we offset upward in z by a few inches /* const float fZOffset = 0.04f; akV[0].z += fZOffset; akV[1].z += fZOffset; akV[2].z += fZOffset;*/ NiPoint3* pkVerts = m_spShadowGeometry->GetVertices(); NIASSERT(pkVerts); pkVerts[usVertCount] = akV[0]; pkVerts[usVertCount + 1] = akV[1]; pkVerts[usVertCount + 2] = akV[2]; unsigned short usBase = usTriCount * 3; unsigned short* pusConnect = m_spShadowGeometry->GetTriList(); NIASSERT(pusConnect); pusConnect[usBase] = usVertCount; pusConnect[usBase + 1] = usVertCount + 1; pusConnect[usBase + 2] = usVertCount + 2; m_spShadowGeometry->SetActiveVertexCount(usVertCount + 3); m_spShadowGeometry->SetActiveTriangleCount(usTriCount + 1); } } //--------------------------------------------------------------------------- #endif