纹理阴影实现笔记
纹理阴影实现的三种方法
ogre包含两种阴影实现技术,阴影体与阴影纹理.
阴影体需要主cpu计算出物体的轮廓,同时生成更多的多边形,会降低帧率
并且在骨骼蒙皮时,会存在问题(如果采用硬蒙皮,无法计算轮廓,软蒙皮则降低帧率)
纹理阴影只需要将几何体按不同的视角绘制(往显卡数据传输量增加),对帧率影响较小.
纹理阴影实现的原理.
假设场影中有一个点光源,首先以点光源的位置为视点(Vl),将场景绘制一遍,
得到的绘制结果保存在一个纹理之中,我们记做shadowmap
(可以以拷贝的形体得到此纹理glCopyTexSubImage2D,
或者将渲染输出绑定到纹理glBindFramebufferEXT)
此时将得到一个深度缓冲,记录以光源为视点,观察得到的场影投影中,每个像素的深度值
然后切换到观察者的视点(Ve),绘制阴影.
此时需要做处理,在绘制每个像素时,需要将它们转换至光源为视点时(Ve)对应的纹理位置,
并得出相应的深度,如果深度小到shadowmap中的值,表明不处在该光源的阴影中,反之,则处在
阴影之中
(通过替换变换矩阵来实现相应的纹理坐标转换,以及深度转换,但每个顶点的变换矩阵仍然
是当前观察点的,呵呵,拗口吧.)
实际就是顶点的位置由当前的观察视点变换矩阵来计算,但纹理映射与深度则由光源的变换矩阵来计算
实现的方式,可以通过纹理矩阵,投影纹理,或者以顶点着色器,像素着色器来实现.
相应的由着色器的方式来实现,代码比较好理解.(ogre里提供的相应的glsl以及hlsl脚本代码)
这里说明一下以投影纹理 或者纹理矩阵来实现(原理一样,但代码不是很好理解)
用投影纹理来实现
首先是将以光源为视点渲染得到的场影缓存拷贝至纹理(glCopyTexSubImage2D)
这样就得到了以光源为视点的深度缓存
然后绘制阴影
要提供纹理的变换矩阵
MATRIX4X4 textureMatrix=biasMatrix*lightProjectionMatrix*lightViewMatrix;
lightProjectionMatrix 表示光源为视点的投影矩阵
lightViewMatrix 表示光源为视点的变换矩阵
然后是纹理投影坐标的计算方法
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_S, GL_EYE_PLANE, textureMatrix.GetRow(0));
glEnable(GL_TEXTURE_GEN_S);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_T, GL_EYE_PLANE, textureMatrix.GetRow(1));
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_R, GL_EYE_PLANE, textureMatrix.GetRow(2));
glEnable(GL_TEXTURE_GEN_R);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_Q, GL_EYE_PLANE, textureMatrix.GetRow(3));
glEnable(GL_TEXTURE_GEN_Q);
其中 s与t分别对应纹理映射, r就表示该点的深度
然后就有以下代码,这时需要对纹理深度比较,判断哪些是阴影,哪些不是
//Enable shadow comparison
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);
//Shadow comparison should be true (ie not in shadow) if r<=texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
//Shadow comparison should generate an INTENSITY result
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);
然后,没有然后了,
这是投影阴影纹理的网上能找到的流行实现方式,完整,相对好理解
========================== 我是分割线 =============================
用纹理矩阵来实现
ogre的纹理阴影又是如何做的呢?
笔者反复读了代码,判断出ogre可能是采用纹理矩阵来实现的(ogre代码太绕...)
在ogre的render大循环中
step1: prepareShadowTextures此函数负责绘制以光源为视点的场影,并将绘制结果输出至缓存
(GLFrameBufferObject::bind-->glBindFramebufferEXT)
step2: 然后调用renderTextureShadowCasterQueueGroupObjects,将所有的物体画一遍
step3: 然后会调用这个函数renderModulativeTextureShadowedQueueGroupObjects画阴影
(我们先关注调制模式,原理其实一样)
七转八绕我们会关注到Pass这个类
Pass包含了光源的位置信息(也就是可以得到光源的投影矩阵与变换矩阵)
Pass包含了shadowmap,它是由prepareShadowTextures阶段绘制得到的.
然后我们可以看到SceneManager::_setPass这个函数,
(在step3 renderModulativeTextureShadowedQueueGroupObjects 函数中,会调用到)
SceneManager::_setPass-->RenderSystem::_setTextureUnitSettings--->GLRenderSystem::_setTextureCoordCalculation
--->会有这样一个switch case分支
case TEXCALC_PROJECTIVE_TEXTURE:
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_S, GL_EYE_PLANE, eyePlaneS);
glTexGenfv(GL_T, GL_EYE_PLANE, eyePlaneT);
glTexGenfv(GL_R, GL_EYE_PLANE, eyePlaneR);
glTexGenfv(GL_Q, GL_EYE_PLANE, eyePlaneQ);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glEnable(GL_TEXTURE_GEN_Q);
mUseAutoTextureMatrix = true;
// Set scale and translation matrix for projective textures
projectionBias = Matrix4::CLIPSPACE2DTOIMAGESPACE;
projectionBias = projectionBias * frustum->getProjectionMatrix();
projectionBias = projectionBias * frustum->getViewMatrix();
projectionBias = projectionBias * mWorldMatrix;
makeGLMatrix(mAutoTextureMatrix, projectionBias);
break;
我们会发现eyePlaneS eyePlaneT eyePlaneR eyePlaneQ其实没有做任何处理,
frustum保存的光源的信息,即可以得到光源的投影与变换矩阵
真正的信息存在mAutoTextureMatrix这位大佬里面
之后会调用GLRenderSystem::_setTextureMatrix设置纹理矩阵
这样就实现替换成光源的投影与变换矩阵.
比较时,只要开启深度测试即可.
(这是笔者存疑的一个地方,但在ogre中找不到glTexParameteri相应的调用参数,只能是这么认为了)
========================== 我是分割线 =============================
用着色器脚本来实现
ogre提供了相应的脚本
media\materials\programs目录下面
DepthShadowmap.hlsl Dx的脚本
DepthShadowmapxxxx.glsl opengl的脚本
脚本分为阴影产生脚本,阴影接受脚本.
阴影产生caster就是生成shadowmap的脚本代码
阴影接受receiver就是画阴影的代码
castere脚本做的事情,是把深度值保存下来
receiver则做矩阵变换,查找对应shadowmap的深度值,来判断是否处理阴影当中
代码相对与投影纹理,纹理矩阵实现方式要容易理解。不详述了。
ogre包含两种阴影实现技术,阴影体与阴影纹理.
阴影体需要主cpu计算出物体的轮廓,同时生成更多的多边形,会降低帧率
并且在骨骼蒙皮时,会存在问题(如果采用硬蒙皮,无法计算轮廓,软蒙皮则降低帧率)
纹理阴影只需要将几何体按不同的视角绘制(往显卡数据传输量增加),对帧率影响较小.
纹理阴影实现的原理.
假设场影中有一个点光源,首先以点光源的位置为视点(Vl),将场景绘制一遍,
得到的绘制结果保存在一个纹理之中,我们记做shadowmap
(可以以拷贝的形体得到此纹理glCopyTexSubImage2D,
或者将渲染输出绑定到纹理glBindFramebufferEXT)
此时将得到一个深度缓冲,记录以光源为视点,观察得到的场影投影中,每个像素的深度值
然后切换到观察者的视点(Ve),绘制阴影.
此时需要做处理,在绘制每个像素时,需要将它们转换至光源为视点时(Ve)对应的纹理位置,
并得出相应的深度,如果深度小到shadowmap中的值,表明不处在该光源的阴影中,反之,则处在
阴影之中
(通过替换变换矩阵来实现相应的纹理坐标转换,以及深度转换,但每个顶点的变换矩阵仍然
是当前观察点的,呵呵,拗口吧.)
实际就是顶点的位置由当前的观察视点变换矩阵来计算,但纹理映射与深度则由光源的变换矩阵来计算
实现的方式,可以通过纹理矩阵,投影纹理,或者以顶点着色器,像素着色器来实现.
相应的由着色器的方式来实现,代码比较好理解.(ogre里提供的相应的glsl以及hlsl脚本代码)
这里说明一下以投影纹理 或者纹理矩阵来实现(原理一样,但代码不是很好理解)
用投影纹理来实现
首先是将以光源为视点渲染得到的场影缓存拷贝至纹理(glCopyTexSubImage2D)
这样就得到了以光源为视点的深度缓存
然后绘制阴影
要提供纹理的变换矩阵
MATRIX4X4 textureMatrix=biasMatrix*lightProjectionMatrix*lightViewMatrix;
lightProjectionMatrix 表示光源为视点的投影矩阵
lightViewMatrix 表示光源为视点的变换矩阵
然后是纹理投影坐标的计算方法
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_S, GL_EYE_PLANE, textureMatrix.GetRow(0));
glEnable(GL_TEXTURE_GEN_S);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_T, GL_EYE_PLANE, textureMatrix.GetRow(1));
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_R, GL_EYE_PLANE, textureMatrix.GetRow(2));
glEnable(GL_TEXTURE_GEN_R);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_Q, GL_EYE_PLANE, textureMatrix.GetRow(3));
glEnable(GL_TEXTURE_GEN_Q);
其中 s与t分别对应纹理映射, r就表示该点的深度
然后就有以下代码,这时需要对纹理深度比较,判断哪些是阴影,哪些不是
//Enable shadow comparison
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);
//Shadow comparison should be true (ie not in shadow) if r<=texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
//Shadow comparison should generate an INTENSITY result
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);
然后,没有然后了,
这是投影阴影纹理的网上能找到的流行实现方式,完整,相对好理解
========================== 我是分割线 =============================
用纹理矩阵来实现
ogre的纹理阴影又是如何做的呢?
笔者反复读了代码,判断出ogre可能是采用纹理矩阵来实现的(ogre代码太绕...)
在ogre的render大循环中
step1: prepareShadowTextures此函数负责绘制以光源为视点的场影,并将绘制结果输出至缓存
(GLFrameBufferObject::bind-->glBindFramebufferEXT)
step2: 然后调用renderTextureShadowCasterQueueGroupObjects,将所有的物体画一遍
step3: 然后会调用这个函数renderModulativeTextureShadowedQueueGroupObjects画阴影
(我们先关注调制模式,原理其实一样)
七转八绕我们会关注到Pass这个类
Pass包含了光源的位置信息(也就是可以得到光源的投影矩阵与变换矩阵)
Pass包含了shadowmap,它是由prepareShadowTextures阶段绘制得到的.
然后我们可以看到SceneManager::_setPass这个函数,
(在step3 renderModulativeTextureShadowedQueueGroupObjects 函数中,会调用到)
SceneManager::_setPass-->RenderSystem::_setTextureUnitSettings--->GLRenderSystem::_setTextureCoordCalculation
--->会有这样一个switch case分支
case TEXCALC_PROJECTIVE_TEXTURE:
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_S, GL_EYE_PLANE, eyePlaneS);
glTexGenfv(GL_T, GL_EYE_PLANE, eyePlaneT);
glTexGenfv(GL_R, GL_EYE_PLANE, eyePlaneR);
glTexGenfv(GL_Q, GL_EYE_PLANE, eyePlaneQ);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glEnable(GL_TEXTURE_GEN_Q);
mUseAutoTextureMatrix = true;
// Set scale and translation matrix for projective textures
projectionBias = Matrix4::CLIPSPACE2DTOIMAGESPACE;
projectionBias = projectionBias * frustum->getProjectionMatrix();
projectionBias = projectionBias * frustum->getViewMatrix();
projectionBias = projectionBias * mWorldMatrix;
makeGLMatrix(mAutoTextureMatrix, projectionBias);
break;
我们会发现eyePlaneS eyePlaneT eyePlaneR eyePlaneQ其实没有做任何处理,
frustum保存的光源的信息,即可以得到光源的投影与变换矩阵
真正的信息存在mAutoTextureMatrix这位大佬里面
之后会调用GLRenderSystem::_setTextureMatrix设置纹理矩阵
这样就实现替换成光源的投影与变换矩阵.
比较时,只要开启深度测试即可.
(这是笔者存疑的一个地方,但在ogre中找不到glTexParameteri相应的调用参数,只能是这么认为了)
========================== 我是分割线 =============================
用着色器脚本来实现
ogre提供了相应的脚本
media\materials\programs目录下面
DepthShadowmap.hlsl Dx的脚本
DepthShadowmapxxxx.glsl opengl的脚本
脚本分为阴影产生脚本,阴影接受脚本.
阴影产生caster就是生成shadowmap的脚本代码
阴影接受receiver就是画阴影的代码
castere脚本做的事情,是把深度值保存下来
receiver则做矩阵变换,查找对应shadowmap的深度值,来判断是否处理阴影当中
代码相对与投影纹理,纹理矩阵实现方式要容易理解。不详述了。