计算用于阴影剔除的包围体(shadow culling volume)
2011-08-18 16:37
267 查看
我们在做剔除的时候一般要用到视锥,用视锥的6个平面作为裁剪平面。但是对于渲染shadow map的时候
是不能直接使用视锥做剔除的,因为视锥外的物体是有可能将阴影投射到视锥里的。这时我们需要一个专门
用于阴影剔除的包围体(culling volume)
那么如何构造这个包围体呢。我们将视锥向光源方向投影,可以得到一个凸多边形的轮廓。
轮廓上的每条边都对应于视锥上的一条边,我们将视锥从这些边处切成两半,将上半部沿光源方向无限拉长,
构成一个新的,被拉长的包围体。再将上半部分这个“盖子”去掉,最终得到的一个非封闭的,
长筒状的东西就是我们需要的包围体了。任何可能将阴影投进视锥的物体都不会被包围体裁掉。
这个过程有点类似shadow volume的计算,只是shadow volume没有去掉“盖子”的过程。
代码
view plaincopy to clipboardprint?
// 判断两个平面的交线是否是轮廓线
int TestSilhouette(const Plane& p0, const Plane& p1, const Vector3& dir)
{
float t0 = p0.m_Normal.Dot(dir);
float t1 = p1.m_Normal.Dot(dir);
if (t0 * t1 > 0.0f)
{
return 0;
}
return t0 > 0.0f ? 1 : 2;
}
// 算法:根据光源方向计算视锥体的轮廓线,由轮廓线和光源方向构造包围平面
// 包围平面过轮廓线且与光源方向平行
void CreateLightCullingVolume(Camera* pCamera, const Vector3& lightDir, /*out*/vector<Plane>& cullingPlanes)
{
// 12条棱的索引,排列顺序为:近平面顺时针4条棱,
// 远平面顺时针4条棱,远近平面之间顺时针4条棱
static unsigned short index[] =
{
1, 0, 0, 2, 2, 3, 3, 1,
5, 4, 4, 6, 6, 7, 7, 5,
1, 5, 0, 4, 2, 6, 3, 7,
};
// 每条棱都是由两个平面相交构成的,以下是与棱对应的12对平面,
// 每条棱在每对平面上的第一个平面上是顺序的,在第二个平面上是逆序的
// 例如第一条棱(0,1)(近平面上面的那条棱)在第一个平面FP_Near上是顺序的,(右手系拇指朝向法线),
// 在第二个平面FP_Top上是逆序的
static EFrustumPlane planePair[][2] =
{
{FP_Near, FP_Top}, {FP_Near, FP_Right}, {FP_Near, FP_Bottom}, {FP_Near, FP_Left},
{FP_Top, FP_Far}, {FP_Right, FP_Far}, {FP_Bottom, FP_Far}, {FP_Left, FP_Far},
{FP_Top, FP_Left}, {FP_Right, FP_Top}, {FP_Bottom, FP_Right}, {FP_Left, FP_Bottom},
};
const Vector3* pCorner = pCamera->GetFrustumCorners();
// 依次测试12条棱是否为轮廓线
for (int i = 0; i < 12; i++)
{
int testSilhouette = TestSilhouette(pCamera->GetPlane(planePair[i][0]), pCamera->GetPlane(planePair[i][1]), lightDir);
// 是轮廓线
if (testSilhouette > 0)
{
// 如果是第一个平面,边是顺着的,如果是第二个平面,边是逆着的
Vector3 edgeDir;
if (testSilhouette == 1)
{
edgeDir = pCorner[index[i * 2 + 1]] - pCorner[index[i * 2 + 0]];
}
else
{
edgeDir = pCorner[index[i * 2 + 0]] - pCorner[index[i * 2 + 1]];
}
// 由轮廓线构造剔除平面,法线朝里
Vector3 normal = lightDir.Cross(edgeDir);
normal.Normalize();
// 将法线保存起来用于剔除
m_LightPlanes.push_back(Plane(normal, pCorner[index[i * 2 + 0]]));
}
}
// 视锥平面中朝向光源的也都是剔除平面
for (int i = 0; i < 6; i++)
{
const Plane& plane = pCamera->GetPlane((EFrustumPlane)i);
if (plane.m_Normal.Dot(lightDir) > 0.0f)
{
vector<Plane>.push_back(plane);
}
}
}
是不能直接使用视锥做剔除的,因为视锥外的物体是有可能将阴影投射到视锥里的。这时我们需要一个专门
用于阴影剔除的包围体(culling volume)
那么如何构造这个包围体呢。我们将视锥向光源方向投影,可以得到一个凸多边形的轮廓。
轮廓上的每条边都对应于视锥上的一条边,我们将视锥从这些边处切成两半,将上半部沿光源方向无限拉长,
构成一个新的,被拉长的包围体。再将上半部分这个“盖子”去掉,最终得到的一个非封闭的,
长筒状的东西就是我们需要的包围体了。任何可能将阴影投进视锥的物体都不会被包围体裁掉。
这个过程有点类似shadow volume的计算,只是shadow volume没有去掉“盖子”的过程。
代码
view plaincopy to clipboardprint?
// 判断两个平面的交线是否是轮廓线
int TestSilhouette(const Plane& p0, const Plane& p1, const Vector3& dir)
{
float t0 = p0.m_Normal.Dot(dir);
float t1 = p1.m_Normal.Dot(dir);
if (t0 * t1 > 0.0f)
{
return 0;
}
return t0 > 0.0f ? 1 : 2;
}
// 算法:根据光源方向计算视锥体的轮廓线,由轮廓线和光源方向构造包围平面
// 包围平面过轮廓线且与光源方向平行
void CreateLightCullingVolume(Camera* pCamera, const Vector3& lightDir, /*out*/vector<Plane>& cullingPlanes)
{
// 12条棱的索引,排列顺序为:近平面顺时针4条棱,
// 远平面顺时针4条棱,远近平面之间顺时针4条棱
static unsigned short index[] =
{
1, 0, 0, 2, 2, 3, 3, 1,
5, 4, 4, 6, 6, 7, 7, 5,
1, 5, 0, 4, 2, 6, 3, 7,
};
// 每条棱都是由两个平面相交构成的,以下是与棱对应的12对平面,
// 每条棱在每对平面上的第一个平面上是顺序的,在第二个平面上是逆序的
// 例如第一条棱(0,1)(近平面上面的那条棱)在第一个平面FP_Near上是顺序的,(右手系拇指朝向法线),
// 在第二个平面FP_Top上是逆序的
static EFrustumPlane planePair[][2] =
{
{FP_Near, FP_Top}, {FP_Near, FP_Right}, {FP_Near, FP_Bottom}, {FP_Near, FP_Left},
{FP_Top, FP_Far}, {FP_Right, FP_Far}, {FP_Bottom, FP_Far}, {FP_Left, FP_Far},
{FP_Top, FP_Left}, {FP_Right, FP_Top}, {FP_Bottom, FP_Right}, {FP_Left, FP_Bottom},
};
const Vector3* pCorner = pCamera->GetFrustumCorners();
// 依次测试12条棱是否为轮廓线
for (int i = 0; i < 12; i++)
{
int testSilhouette = TestSilhouette(pCamera->GetPlane(planePair[i][0]), pCamera->GetPlane(planePair[i][1]), lightDir);
// 是轮廓线
if (testSilhouette > 0)
{
// 如果是第一个平面,边是顺着的,如果是第二个平面,边是逆着的
Vector3 edgeDir;
if (testSilhouette == 1)
{
edgeDir = pCorner[index[i * 2 + 1]] - pCorner[index[i * 2 + 0]];
}
else
{
edgeDir = pCorner[index[i * 2 + 0]] - pCorner[index[i * 2 + 1]];
}
// 由轮廓线构造剔除平面,法线朝里
Vector3 normal = lightDir.Cross(edgeDir);
normal.Normalize();
// 将法线保存起来用于剔除
m_LightPlanes.push_back(Plane(normal, pCorner[index[i * 2 + 0]]));
}
}
// 视锥平面中朝向光源的也都是剔除平面
for (int i = 0; i < 6; i++)
{
const Plane& plane = pCamera->GetPlane((EFrustumPlane)i);
if (plane.m_Normal.Dot(lightDir) > 0.0f)
{
vector<Plane>.push_back(plane);
}
}
}
相关文章推荐
- 计算用于阴影剔除的包围体(shadow culling volume)
- 引擎--渲染器,顶点变换和光照,剔除(culling)系统和LOD系统,阴影贴图,粒子系统,着色器,骨骼动画
- Fast Shadow Receiver - 快速阴影投射计算插件
- 【转】阴影锥(shadow volume)原理与展望---真实的游戏效果的实现
- (转)阴影锥(Shadow Volume)
- 阴影之, Shadow Volume - 完
- 阴影锥(Shadow Volume)
- 阴影锥(shadow volume)原理与展望
- 阴影锥(shadow volume)原理与展望---真实的游戏效果的实现
- java生成一年中假日表(包括周末和法定假期),用于计算一年中的工作日
- CSS3阴影 box-shadow的使用和技巧总结
- 用于水和水蒸汽物性计算的Python模块——iapws
- CSS3文字与字体:text-overflow 与 word-wrap、@font-face、文本阴影text-shadow
- 阴影 box-shadow(二)
- eval函数用于计算代码串,而不引用任何特定
- CSS3简明教程-2.3.CSS3边框之 盒元素阴影box-shadow
- CSS3阴影 box-shadow的使用和技巧总结
- CSS3的文字阴影—text-shadow
- java 用于计算时间的工具类
- [Unity3d] Directional Shadow Details 平行光阴影细节