#include "stdafx.h" #include "ShadowGeometry.h" #include "Map/Map.h" #include "Map/Chunk.h" #include "LightManager.h" NiMaterial* CShadowGeometry::ms_pkShadowMaterial = NULL; NiMaterial* CShadowGeometry::ms_pkShadowSkinnedMaterial = NULL; NiTexture* CShadowGeometry::ms_pkStaticShadow = NULL; NiPropertyState* CShadowGeometry::ms_pkPropertyState = NULL; NiMaterialProperty* CShadowGeometry::ms_pkMaterialProperty = NULL; #define SHADOW_CAMERA_FAR (100.f) //--------------------------------------------------------------------------- CShadowGeometry::CShadowGeometry(ShadowType eShadowType) :m_eShadowType(eShadowType) ,m_kCastingObjects(4) ,m_kSkinnedCastingObjects(4) ,m_fRadius(0.5f) { // 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, 0.0f); // the direction of the infinite light casting the shadow. if(eShadowType == ST_DYNAMIC) { m_kLightDir = LIGHT_DIR; m_kLightDir.Unitize(); } else m_kLightDir = NiPoint3(0.f, 0.f, -1.f); //竖直向下 // Need to generate an orthonormal frame, with the X axis mapping to the // light direction. The other dimensions do not matter, so long as they // define a right-handed orthonormal frame NiPoint3 kYVec = -m_kLightDir.Perpendicular(); NiPoint3 kZVec = m_kLightDir.UnitCross(kYVec); // Rotate the camera based on the orthonormal frame NiMatrix3 kRotation(m_kLightDir, kYVec, kZVec); m_spCamera->SetRotate(kRotation); m_spCamera->Update(0.0f); NiFrustum kFrust = m_spCamera->GetViewFrustum(); kFrust.m_fFar = SHADOW_CAMERA_FAR; kFrust.m_fNear = 0.1f; //if(eShadowType == ST_DYNAMIC) // kFrust.m_bOrtho = false; //else // kFrust.m_bOrtho = false; m_spCamera->SetViewFrustum(kFrust); m_usMaxTriangleCount = SHADOW_TRIANGLE_COUNT; m_usMaxVertexCount = 3 * m_usMaxTriangleCount; // 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_usMaxVertexCount]; NIASSERT(pkVertex); NiPoint2* pkTexC = NiNew NiPoint2[m_usMaxVertexCount]; NIASSERT(pkTexC); unsigned short* pusConnect = NiAlloc(unsigned short, m_usMaxTriangleCount * 3); NIASSERT(pusConnect); NiTriShapeDynamicData* pkTriData = NiNew NiTriShapeDynamicData( m_usMaxVertexCount, pkVertex, 0, 0, pkTexC, 1, NiGeometryData::NBT_METHOD_NONE, m_usMaxTriangleCount, pusConnect); NIASSERT(pkTriData); m_spShadowGeometry = NiNew NiTriShape(pkTriData); NIASSERT(m_spShadowGeometry); 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(true); 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); } //---------------------------------------------------------------------------- CShadowGeometry* CShadowGeometry::Create(ShadowType eShadowType) { if( ms_pkMaterialProperty == NULL ) return NULL; CShadowGeometry* pkThis = NiNew CShadowGeometry(eShadowType); unsigned int uiTexWidth = 256; NiRenderer* pkRenderer = 0; if(eShadowType == ST_DYNAMIC) { pkRenderer = NiRenderer::GetRenderer(); NIASSERT(pkRenderer); 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; NiRenderedTexture* pkRenderedTexture = NiRenderedTexture::Create(uiTexWidth, uiTexWidth, pkRenderer, kPrefs); pkThis->m_spTexture = pkRenderedTexture; NiDepthStencilBuffer* pkDSB = NiDepthStencilBuffer::Create(uiTexWidth, uiTexWidth, pkRenderer, NiPixelFormat::STENCILDEPTH824); pkThis->m_spRenderTargetGroup = NiRenderTargetGroup::Create(pkRenderedTexture->GetBuffer(), pkRenderer, pkDSB); } else { pkThis->m_spTexture = ms_pkStaticShadow; pkThis->m_spRenderTargetGroup = NULL; } // 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); // 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(); return pkThis; } //---------------------------------------------------------------------------- CShadowGeometry::~CShadowGeometry() { } //--------------------------------------------------------------------------- void CShadowGeometry::ResetCaster(NiAVObject* pkCaster) { m_kCastingObjects.RemoveAll(); m_kSkinnedCastingObjects.RemoveAll(); m_spCaster = pkCaster; if (m_eShadowType == ST_DYNAMIC) RecursiveStoreCasterObjects(pkCaster); } //--------------------------------------------------------------------------- void CShadowGeometry::RecursiveStoreCasterObjects(NiAVObject* pkObject) { if(pkObject == NULL) return; 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; //透明物体没有阴影 NiZBufferProperty* pkZBufferProperty = (NiZBufferProperty*)pkGeometry->GetProperty(NiZBufferProperty::GetType()); if(pkZBufferProperty && !pkZBufferProperty->GetZBufferWrite()) return; NiAlphaProperty* pkAlphaProperty = (NiAlphaProperty*)pkGeometry->GetProperty(NiAlphaProperty::GetType()); if(pkAlphaProperty && pkAlphaProperty->GetAlphaBlending()) { if(pkAlphaProperty->GetSrcBlendMode() <= NiAlphaProperty::ALPHA_INVDESTCOLOR || pkAlphaProperty->GetDestBlendMode() <= NiAlphaProperty::ALPHA_INVDESTCOLOR) return; } //粒子系统没有阴影 if(NiIsKindOf(NiParticles, pkGeometry)) return; if (pkGeometry->GetSkinInstance() == NULL || pkGeometry->GetSkinInstance()->GetSkinPartition() == 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++) { RecursiveStoreCasterObjects(pkNode->GetAt(i)); } } } void CShadowGeometry::RenderTexture() { if (m_eShadowType == ST_DYNAMIC) { 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); pkRenderer->BeginUsingRenderTargetGroup(m_spRenderTargetGroup, NiRenderer::CLEAR_ALL); // renderer camera port settings pkRenderer->SetCameraData(m_spCamera); if(m_kCastingObjects.GetSize()) { // Render all leaf shadow caster geometry in two batches (skinned and // non-skinned), replacing their built-in renderstates with the // single shadow state. This will cause all of the shadow casters to \ // be rendered as dark gray, making them look like shadows. pkRenderer->BeginBatch(ms_pkPropertyState, NULL); NiTPrimitiveArray kMaterials(m_kCastingObjects.GetSize()); for (unsigned int i = 0; i < m_kCastingObjects.GetSize(); i++) { NiGeometry* pkGeom = NiSmartPointerCast(NiGeometry, m_kCastingObjects.GetAt(i)); kMaterials.Add(pkGeom->GetActiveMaterial()); if(NiIsKindOf(NiSwitchNode, pkGeom->GetParent())) { if( ((NiSwitchNode*)pkGeom->GetParent())->GetActiveChild() != pkGeom ) continue; } pkGeom->ApplyAndSetActiveMaterial(ms_pkShadowMaterial); if (NiIsKindOf(NiTriStrips, pkGeom)) { pkRenderer->BatchRenderStrips((NiTriStrips*)pkGeom); } else if (NiIsKindOf(NiTriShape, pkGeom)) { pkRenderer->BatchRenderShape((NiTriShape*)pkGeom); } } pkRenderer->EndBatch(); for(unsigned int i = 0; i < m_kCastingObjects.GetSize(); i++) { NiGeometry* pkGeom = NiSmartPointerCast(NiGeometry, m_kCastingObjects.GetAt(i)); pkGeom->SetActiveMaterial(kMaterials.GetAt(i)); } } if(m_kSkinnedCastingObjects.GetSize()) { pkRenderer->BeginBatch(ms_pkPropertyState, NULL); NiTPrimitiveArray kMaterials(m_kSkinnedCastingObjects.GetSize()); for (unsigned int i = 0; i < m_kSkinnedCastingObjects.GetSize(); i++) { NiGeometry* pkGeom = NiSmartPointerCast(NiGeometry, m_kSkinnedCastingObjects.GetAt(i)); kMaterials.Add(pkGeom->GetActiveMaterial()); if(NiIsKindOf(NiSwitchNode, pkGeom->GetParent())) { if( ((NiSwitchNode*)pkGeom->GetParent())->GetActiveChild() != pkGeom ) continue; } pkGeom->ApplyAndSetActiveMaterial(ms_pkShadowSkinnedMaterial); pkGeom->GetSkinInstance()->SetFrameID(0); if (NiIsKindOf(NiTriStrips, pkGeom)) { pkRenderer->BatchRenderStrips((NiTriStrips*)pkGeom); } else if (NiIsKindOf(NiTriShape, pkGeom)) { pkRenderer->BatchRenderShape((NiTriShape*)pkGeom); } } pkRenderer->EndBatch(); for(unsigned int i = 0; i < m_kSkinnedCastingObjects.GetSize(); i++) { NiGeometry* pkGeom = NiSmartPointerCast(NiGeometry, m_kSkinnedCastingObjects.GetAt(i)); pkGeom->SetActiveMaterial(kMaterials.GetAt(i)); } } pkRenderer->EndUsingRenderTargetGroup(); //{ // static int i = 0; // if(++i > 1000) // { // i = 0; // NiTexturingProperty* pkTextureProperty = (NiTexturingProperty*)m_spShadowGeometry->GetProperty(NiProperty::TEXTURING); // NiTexturingProperty::Map* pkBaseMap = pkTextureProperty->GetBaseMap(); // NiTexture* pkTexture = (NiTexture*)pkBaseMap->GetTexture(); // NiDX9TextureData* pkDX9TextureData = (NiDX9TextureData*)pkTexture->GetRendererData(); // LPDIRECT3DTEXTURE9 pkDXTexture = (LPDIRECT3DTEXTURE9)pkDX9TextureData->GetD3DTexture(); // D3DXSaveTextureToFileA("C:\\TestShadow.dds", D3DXIFF_DDS, pkDXTexture, NULL); // } // } } } //---------------------------------------------------------------------------- void CShadowGeometry::Click(const NiPoint3& kPos) { NIASSERT(m_spCaster && m_spTexture); m_kTranslate = kPos; // move the shadow camera (and thus the apparent light) UpdateShadowCamera(); // Determine which triangles fall inside of the shadow frustum. UpdateShadowGeometry(); } //--------------------------------------------------------------------------- void CShadowGeometry::UpdateShadowCamera() { // 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_eShadowType == ST_DYNAMIC) m_fRadius = 10.4f;//m_spCaster->GetWorldBound().GetRadius(); // translate the camera to a distant point such that the camera is looking // directly at the target point m_spCamera->SetTranslate(NiPoint3(m_kTranslate.x, m_kTranslate.y, m_kTranslate.z + m_fRadius) - (m_kLightDir * (SHADOW_CAMERA_FAR / 2.f))); float fFOV = (m_fRadius * 2.f / SHADOW_CAMERA_FAR)*0.95f; NiFrustum kFrust = m_spCamera->GetViewFrustum(); kFrust.m_fLeft = -fFOV; kFrust.m_fRight = fFOV; kFrust.m_fTop = fFOV; kFrust.m_fBottom = -fFOV; m_spCamera->SetViewFrustum(kFrust); m_spCamera->Update(0.f); m_kPlanes.Set(*m_spCamera); } //--------------------------------------------------------------------------- void CShadowGeometry::UpdateShadowGeometry() { m_spShadowGeometry->SetActiveVertexCount(0); m_spShadowGeometry->SetActiveTriangleCount(0); TraverseGroundGeometry(); m_spShadowGeometry->GetModelData()->MarkAsChanged( NiGeometryData::VERTEX_MASK | NiGeometryData::TEXTURE_MASK | NiTriBasedGeomData::TRIANGLE_INDEX_MASK | NiTriBasedGeomData::TRIANGLE_COUNT_MASK); UpdateShadowGeometryBound(); } void CShadowGeometry::TraverseGroundGeometry() { CMap* pkMap = CMap::Get(); NiPoint3 akTri[3]; int iCount = 7; NiPoint3 kTarget; kTarget.x = (float)ALIGN(m_kTranslate.x, UNIT_SIZE) - UNIT_SIZE*(iCount/2); kTarget.y = (float)ALIGN(m_kTranslate.y, UNIT_SIZE) - UNIT_SIZE*(iCount/2); //3 4 //*-----* //| | //| | //*-----* //1 2 //123组成一个三角形 //234组成一个三角形 float x, y; x = kTarget.x; for( int i = 0; i < iCount; i++, x += UNIT_SIZE ) { y = kTarget.y; for( int j = 0; j < iCount; j++, y += UNIT_SIZE ) { //Triangle 123 akTri[0].x = x; akTri[0].y = y; akTri[0].z = pkMap->GetHeight(akTri[0].x, akTri[0].y); akTri[1].x = x + UNIT_SIZE; akTri[1].y = y; akTri[1].z = pkMap->GetHeight(akTri[1].x, akTri[1].y); akTri[2].x = x; akTri[2].y = y + UNIT_SIZE; akTri[2].z = pkMap->GetHeight(akTri[2].x, akTri[2].y); if(IsDynamicShadow()) CullTriAgainstCameraFrustum(akTri); else AddShadowTriangle(akTri); //Triangle 324 akTri[0].x = x; akTri[0].y = y + UNIT_SIZE; akTri[0].z = pkMap->GetHeight(akTri[0].x, akTri[0].y); akTri[1].x = x + UNIT_SIZE; akTri[1].y = y; akTri[1].z = pkMap->GetHeight(akTri[1].x, akTri[1].y); akTri[2].x = x + UNIT_SIZE; akTri[2].y = y + UNIT_SIZE; akTri[2].z = pkMap->GetHeight(akTri[2].x, akTri[2].y); if(IsDynamicShadow()) CullTriAgainstCameraFrustum(akTri); else AddShadowTriangle(akTri); } } } //--------------------------------------------------------------------------- void CShadowGeometry::UpdateShadowGeometryBound() { // Manually set the bounding sphere of the shadow geometry to ensure // it is not culled by the engine NiBound kSphere; kSphere.SetCenterAndRadius(m_kTranslate, m_fRadius); m_spShadowGeometry->SetModelBound(kSphere); m_spShadowGeometry->Update(0.0f); } //--------------------------------------------------------------------------- bool CShadowGeometry::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 CShadowGeometry::CullTriAgainstCameraFrustum(const NiPoint3 akV[3]) { // This method does no clipping, but "extends" the reliance on // CLAMP_S_CLAMP_T texture wrapping modes // Cull triangle vertices against camera planes. If all three vertices // fall on the outside of any single plane, cull. Skip the near and far // planes (indices i = 0,1). unsigned int i = NiFrustumPlanes::LEFT_PLANE; for (; i < NiFrustumPlanes::MAX_PLANES; i++) { const NiPlane& kPlane = m_kPlanes.GetPlane(i); // vert 0 in? if (kPlane.Distance(akV[0]) >= 0.0f) continue; // vert 1 in? if (kPlane.Distance(akV[1]) >= 0.0f) continue; // vert 2 in? if (kPlane.Distance(akV[2]) >= 0.0f) continue; // all verts outside of a single plane - culled return; } float f = m_kLightDir.Dot(m_kTranslate); if( m_kLightDir.Dot(akV[0]) < f && m_kLightDir.Dot(akV[1]) < f && m_kLightDir.Dot(akV[2]) < f ) { return; } NiPlane kTriPlane(akV[0], akV[1], akV[2]); // we know that the ray center is the camera location NiPoint3 kCamRayPt = m_spCamera->GetWorldLocation(); // No intersection if the point is behind the triangle const float fDist = kTriPlane.Distance(kCamRayPt); if (fDist < 0.0f) return; const NiPoint3& kNorm = kTriPlane.m_kNormal; NiPoint3 akCamRayDirs[4]; NiPoint3 akCamRayIntersect[4]; NiPoint3 kE; // Next, compute the intersection points of the frustum corners with the // plane of the triangle. There will be four of them if the frustum // goes through the triangle. If not, there may be far fewer. However, // for now, if any view frustum edge doesn't intersect the plane of the // triangle, we give up and add the triangle. for (i = 0; i < 4; i++) { NiPoint3 kTemp; if (!GenerateCameraRay(i, kTemp, akCamRayDirs[i])) goto CannotCull; // compute the intersection of the plane of the triangle with the ray const float fNormDot = kNorm.Dot(akCamRayDirs[i]); // if the rays are in the same direction, no intersection (backfacing) if (fNormDot >= -1.0e-5f) goto CannotCull; akCamRayIntersect[i] = kCamRayPt - akCamRayDirs[i] * (fDist / fNormDot); } // now, test each of the frustum intersection points against each of the // triangle edges. If all 4 points are on the same side of a triangle // edge, cull the triangle // We will compute the cross product of the triangle edge and the vector // from one of the edge vertices to the frustum point, and then compute // the dot of that vector with the triangle normal. The sign of this dot // is the CCW/CW value kE = akV[1] - akV[0]; for (i = 0; i < 4; i++) { // if the dot triple product ((UxV)*W) is positive, then the frustum // point is on the inside of the given triangle edge, and we cannot // cull based on this edge. if (kE.Cross(akCamRayIntersect[i] - akV[1]).Dot(kNorm) >= 1.0e-5f) break; } // all points on outside of single edge - culled if (i == 4) return; kE = akV[2] - akV[1]; for (i = 0; i < 4; i++) { // if the dot triple product ((UxV)*W) is positive, then the frustum // point is on the inside of the given triangle edge, and we cannot // cull based on this edge. if (kE.Cross(akCamRayIntersect[i] - akV[2]).Dot(kNorm) >= 1.0e-5f) break; } // all points on outside of edge - culled if (i == 4) return; kE = akV[0] - akV[2]; for (i = 0; i < 4; i++) { // if the dot triple product ((UxV)*W) is positive, then the frustum // point is on the inside of the given triangle edge, and we cannot // cull based on this edge. if (kE.Cross(akCamRayIntersect[i] - akV[0]).Dot(kNorm) >= 1.0e-5f) break; } // all points on outside of edge - culled if (i == 4) return; CannotCull: AddShadowTriangle(akV); } //--------------------------------------------------------------------------- void CShadowGeometry::AddShadowTriangle(const NiPoint3 akV[3]) { unsigned short usVertCount = m_spShadowGeometry->GetActiveVertexCount(); unsigned short usTriCount = m_spShadowGeometry->GetActiveTriangleCount(); if ((usVertCount + 3) <= m_usMaxVertexCount && (usTriCount + 1) <= m_usMaxTriangleCount) { NiPoint2* pkTex = m_spShadowGeometry->GetTextures(); NIASSERT(pkTex); float fBx, fBy, fBz; for (unsigned int i = 0; i < 3; i++) { m_spCamera->WorldPtToScreenPt3(akV[i], fBx, fBy, fBz); pkTex[usVertCount + i].x = fBx / fBz; pkTex[usVertCount + i].y = 1.f - fBy / fBz; } NiPoint3* pkVerts = m_spShadowGeometry->GetVertices(); NIASSERT(pkVerts); pkVerts[usVertCount] = akV[0]; pkVerts[usVertCount + 1] = akV[1]; pkVerts[usVertCount + 2] = akV[2]; pkVerts[usVertCount].z += 0.01f; pkVerts[usVertCount + 1].z += 0.01f; pkVerts[usVertCount + 2].z += 0.01f; 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); } } void CShadowGeometry::SetShadowColor(const NiColor& kColor) { if(ms_pkMaterialProperty) ms_pkMaterialProperty->SetDiffuseColor(kColor); } bool CShadowGeometry::_SDMInit() { ms_pkShadowMaterial = NiSingleShaderMaterial::Create("Shadow"); ms_pkShadowMaterial->IncRefCount(); ms_pkShadowSkinnedMaterial = NiSingleShaderMaterial::Create("ShadowSkinned"); ms_pkShadowSkinnedMaterial->IncRefCount(); ms_pkStaticShadow = NiSourceTexture::Create("Data\\Textures\\Shadows\\ShadowBlob.dds"); ms_pkStaticShadow->IncRefCount(); ms_pkPropertyState = NiNew NiPropertyState; ms_pkPropertyState->IncRefCount(); NiShadeProperty* pkShade = NiNew NiShadeProperty; pkShade->SetSmooth(false); ms_pkPropertyState->SetProperty(pkShade); ms_pkMaterialProperty = NiNew NiMaterialProperty; ms_pkMaterialProperty->IncRefCount(); SetShadowColor(DEFAULT_SHADOW_COLOR); NiStencilProperty* pkStencilProperty = NiNew NiStencilProperty; pkStencilProperty->SetDrawMode(NiStencilProperty::DRAW_BOTH); ms_pkPropertyState->SetProperty(pkStencilProperty); ms_pkPropertyState->SetProperty(ms_pkMaterialProperty); return true; } void CShadowGeometry::_SDMShutdown() { if(ms_pkShadowMaterial) { ms_pkShadowMaterial->DecRefCount(); ms_pkShadowMaterial = NULL; } if(ms_pkShadowSkinnedMaterial) { ms_pkShadowSkinnedMaterial->DecRefCount(); ms_pkShadowSkinnedMaterial = NULL; } if(ms_pkStaticShadow) { ms_pkStaticShadow->DecRefCount(); ms_pkStaticShadow = NULL; } if(ms_pkMaterialProperty) { ms_pkMaterialProperty->DecRefCount(); ms_pkMaterialProperty = NULL; } if(ms_pkPropertyState) { ms_pkPropertyState->DecRefCount(); ms_pkPropertyState = NULL; } } //---------------------------------------------------------------------------