SHADOWMAPPING与GLSL
SHADOWMAPPING与GLSL
当我开始学习openGL着色语言时,“橙皮书”是一个很好的资源,但我认为它缺少代码示例。这是阴影映射的一个适度的跨平台实现。
我的目标是在Windows,MacOS和Linux上提供一些易于编译的东西。这就是为什么整个源是ANSI C编码的.c文件(+2着色器)。通过GLUT执行跨平台窗口管理。我没有使用GLEW来确保Microsoft Windows的可移植性,这也是为了方便编译。
编辑2013年6月:本文中的一些材料发表在计算机图形学:原理与实践(第3版) :)!
资源
Win32和macOS X二进制文件:注意:由于MacOS X错误与GLUT(相对路径在应用程序启动时丢失设置为“/”),我无法分发二进制文件。在XCode中运行示例是很好的。 注2: Kris de Greve将代码移植到C#mono。源代码在这里
代码说明
这是阴影映射的最原始形式:
- 通过FBO进行渲染。没有回退使用
glCopyTexSubImage2D
,因为我假设支持GLSL的任何GPU也将支持FBO。 - 着色器中没有“隐藏”机制。用于查找的功能
texture2D
是手动完成W分割。我将使用shadow2DProj
内置的GLSL函数编写另一个示例。 - 光矩阵通过TEXTURE7矩阵传递。重新使用变量不是最佳做法,但是它使代码更短,而IMHO对于初学者来说更不可怕。
FBO创作
阴影映射通过OpenGL Framebuffer对象(FBO)在屏幕外部渲染。在渲染过程中只保存深度值,没有颜色纹理绑定到FBO,彩色写入被禁用glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
。
//保留用于轻POV渲染的帧缓冲区的id GLuint fboId; //当使用fboId帧缓冲区时,Z值将被渲染到此纹理 GLuint depthTextureId; void generateShadowFBO() { int shadowMapWidth = RENDER_WIDTH * SHADOW_MAP_RATIO; int shadowMapHeight = RENDER_HEIGHT * SHADOW_MAP_RATIO; ;;; //尝试使用纹理深度分量 glGenTextures(1,&depthTextureId); glBindTexture(GL_TEXTURE_2D,depthTextureId); // GL_LINEAR对深度纹理没有意义。但是,下一个教程将显示GL_LINEAR和PCF的用法 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); //删除阴影贴图边缘的工件 glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); //不需要强制GL_DEPTH_COMPONENT24,驱动程序通常会给你最大的精度(如果有的话) glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,shadowMapWidth,shadowMapHeight,0,GL_DEPTH_COMPONENT,GL_UNSIGNED_BYTE,0); glBindTexture(GL_TEXTURE_2D,0); //创建一个framebuffer对象 glGenFramebuffersEXT(1,&fboId); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fboId); //指示openGL,我们不会与当前绑定的FBO绑定颜色纹理 glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); //将纹理附加到FBO深度附件点 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_DEPTH_ATTACHMENT_EXT,GL_TEXTURE_2D,depthTextureId,0); //检查FBO状态 FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if(FBOstatus!= GL_FRAMEBUFFER_COMPLETE_EXT) printf(“GL_FRAMEBUFFER_COMPLETE_EXT failed,CAN NOT FBO \ n”); //切换回窗口系统提供的帧缓冲区 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0); }
轻POV转换
我已经阅读了很多教程,人们连接“反向相机变换”。我没有看到这样的效用,我更喜欢直接加载矩阵中的bias * projection * modelview GL_TEXTURE7
矩阵。
void setTextureMatrix(void) { static double modelView [16]; 静态双投影[16]; //从单位立方体[-1,1]移动到[0,1] const GLdouble bias [16] = { 0.5,0.0,0.0,0.0, 0.0,0.5,0.0,0.0, 0.0,0.0,0.5,0.0, 0.5,0.5,0.5,1.0}; //抓取模型视图和变换矩阵 glGetDoublev(GL_MODELVIEW_MATRIX,modelView); glGetDoublev(GL_PROJECTION_MATRIX,投影); glMatrixMode(GL_TEXTURE); glActiveTextureARB(GL_TEXTURE7); glLoadIdentity(); glLoadMatrixd(偏差); //将所有矩阵并入一个。 glMultMatrixd(投影); glMultMatrixd(modelView); //返回正常的矩阵模式 glMatrixMode(GL_MODELVIEW); }
顶点着色器
这里没有火箭科学,我们用相机矩阵,与光POV矩阵相同的顶点来转换顶点,我们得到片段颜色
//用于阴影查找 变化的vec4 ShadowCoord; void main() { ShadowCoord = gl_TextureMatrix [7] * gl_Vertex; gl_Position = ftransform(); gl_FrontColor = gl_Color; }
片段着色器
该shadow
变量保持阴影测试结果。如你所见,目标是比较渲染顶点的z值(轻POV)与渲染到阴影贴图的值。
均匀采样器 变化的vec4 ShadowCoord; void main() { vec4 shadowCoordinateWdivide = ShadowCoord / ShadowCoord.w; //用于降低莫尔图案和自我阴影 shadowCoordinateWdivide.z + = 0.0005; float distanceFromLight = texture2D(ShadowMap,shadowCoordinateWdivide.st).z; float shadow = 1.0; if(ShadowCoord.w> 0.0) shadow = distanceFromLight <shadowCoordinateWdivide.z?0.5:1.0; gl_FragColor = shadow * gl_Color; }
避免自我阴影和莫尔图案
由于深度缓冲器限制精度,会发生自我遮蔽。这也被称为Z战斗。这只影响面向光的多边形,因为它是渲染到阴影贴图的。
即使您使用最大可用精度(GL_DEPTH_COMPONENT24
),这不是一个可以用原始电源有效解决的问题:没有精度水平可以完全获得自我遮蔽。
减少它的一个很好的技术是在阴影映射渲染步骤中剔除前面的多边形,在第二步中使用glCullFace(GL_FRONT)
并切换回glCullFace(GL_BACK)
。结果如下:
你可以看到这种技术只能把自我遮蔽的问题移到不面向光的多边形上,shadowCoordinateWdivide.z += 0.0005
影子分辨率的重要性
无论您使用什么分辨率,取决于光线的位置,您将使用阴影实验混叠问题。
对于这个问题,原始处理能力可以有所帮助:您可以调整渲染阴影贴图的分辨率。
160x120 shadowmap:
640x480 shadowmap:
1280x960 shadowmap:
设置纹理过滤GL_LINEAR
不会有太大帮助。最好的方法是使用百分比过滤(PCF)。这个算法将为我们提供一个微小的半影,一步一步的阴影,这个阴影方法在我的下一篇文章中介绍。
避免在光线后面和两侧的人造物
当使用shadowmapping时,当您尝试从光栅中检索阴影信息时,会收到一些投影工件。在边界的两边和它后面。
在一边:
如左图所示,多维数据集的阴影再次投射,因为我们将阴影贴图信息超出纹理的[0,1]限制。
解决这个问题的方法是指定OpenGL在这种情况下应该采样的内容。这可以通过以下几行完成:glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP )
和glTexParameterf(
GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP )
。
在相机背后:
但仍然有一个人造物,在右上方可见:
这是当我们尝试在光的视图后面采样值。这是通过着色器线解决的:if (ShadowCoord.w > 0.0)
。
Windows XP / Vista特价
不幸的是,微软决定不支持v1.1以外的OpenGL扩展。因此,当程序启动时,我们需要检索FBO和GLSL所需功能的位置。这绝对是一个丑陋的代码段,我建议使用GLEW代替,它是手动完成的,以提供一个可编译的文件。
#ifdef _WIN32
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB;
// FrameBuffer(FBO)gen,bin和texturebind
PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT;
PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT;
PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT;
PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT;
void getOpenGLFunctionPointers(void)
{
glActiveTextureARB =(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress(“glActiveTextureARB”);
glGenFramebuffersEXT =(PFNGLGENFRAMEBUFFERSEXTPROC)wglGetProcAddress(“glGenFramebuffersEXT”);
glBindFramebufferEXT =(PFNGLBINDFRAMEBUFFEREXTPROC)wglGetProcAddress(“glBindFramebufferEXT”);
glFramebufferTexture2DEXT =(PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)wglGetProcAddress(“glFramebufferTexture2DEXT”);
glCheckFramebufferStatusEXT =(PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)wglGetProcAddress(“glCheckFramebufferStatusEXT”);
}
#万一