阴影映射(Shadow Map)的研究(四)

阴影映射(Shadow Map)的研究(四)

上一篇文章粗略地介绍了要实现OpenGL ES 2.0的阴影映射所需的知识难点,现在简略地说明一下:1、FBO;2、着色器;3、float的分拆以及组合。上篇文章虽然说已经成功地移植了来自Java编写的Android下阴影映射的效果,但这边采用的很大程度上是OpenGL原生代码编写的内容,接下来的目标是采用自Qt 5起就逐渐采用的Qt对OpenGL的封装类,用面向对象的思维来处理OpenGL对象,这样让代码更加优雅。

1、FBO

首先说一下FBO。在Qt中有QOpenGLFramebufferObject这个类可以实现。这个类自Qt 5.0开始启用,事实上主要对OpenGL的FramebufferObject进行了面向对象的封装。只需要指定size以及可选指定FBO的格式,就可以启用了。在渲染目标至FBO的时候,只需要在最开始指定

bool QOpenGLFramebufferObject::bind( )

即可,当将FBO的内容作为纹理的渲染到默认的Framebuffer的时候,只需要指定

bool QOpenGLFramebufferObject::bindDefault( )

以及

GLuint QOpenGLFramebufferObject::texture( ) const

即可。使用起来非常方便。

2、着色器

接下来说一下着色器,要写基于OpenGL ES 2.0的程序,着色器是不可避免要写了。在书写OpenGL ES2.0的着色器时,要注意顶点着色器与片断着色器的差异性。目前在渲染阴影映射的时候,牵涉到2 pass的渲染,也就是说要对正常的场景渲染两遍。第一遍是在以光源为视角对整个场景进行渲染,产生深度图;第二遍是以正常摄像机为视角进行渲染,在其中引入第一遍产生的深度图,根据深度图中的深度信息以及该片断在光照视角下所产生的深度信息作比较,来最终得出该片断是否被遮挡。通常是深度的渲染信息大于改片断在光源视角下的深度信息,即表示该片断被遮挡(这里牵涉到深度缓存的相关知识,详见我之前写的文章《深度缓存(Z缓存)的研究》和《阴影映射(Shadow
Map)的研究(二)
》)。一旦确定被遮挡,那么使用较为深的颜色表示阴影。原理虽然简单,但是需要注意的一些陷阱还是很多的。下面是渲染深度图的顶点着色器以及片断着色器代码:

// Depth.vert
#ifdef GL_ES
precision highp float;
#endif

attribute vec3 position;

uniform mat4 modelMatrix;
uniform mat4 viewProjectionMatrix;

varying vec4 projectedPosition;

void main( void )
{
    vec3 finalPosition = position;

    projectedPosition =
            viewProjectionMatrix *
            modelMatrix *
            vec4( finalPosition, 1.0 );
    gl_Position = projectedPosition;
}

// Depth.frag
// 注意:这个着色器的是由
// http://www.codeproject.com/Articles/822380/Shadow-Mapping-with-Android-OpenGL-ES
// 改编过来的。
#ifdef GL_ES
precision highp float;
#endif

varying vec4 projectedPosition;

vec4 pack( float depth )
{
    const vec4 bitSh = vec4( 256.0 * 256.0 * 256.0,
                             256.0 * 256.0,
                             256.0,
                             1.0 );
    const vec4 bitMsk = vec4( 0.0,
                              1.0 / 256.0,
                              1.0 / 256.0,
                              1.0 / 256.0 );
    vec4 comp = fract( depth * bitSh );
    comp -= comp.xxyz * bitMsk;
    return comp;
}

void main( void )
{
    float normalizedZ = projectedPosition.z / projectedPosition.w;
    normalizedZ = ( normalizedZ + 1.0 ) / 2.0;
    gl_FragColor = pack( normalizedZ );
}

这里在片断着色器中,也可以采用gl_FragCoord.z来表示片断的深度(详见我的文章《有关GLSL中的gl_FragCoord》),但是在后来的测试中,我发现这样的精度会受到影响,而且久久得不出想要的阴影效果,于是我放弃了记录gl_FragCoord.z的方案,而是自己计算深度(注意:这里计算的方法和gl_FragCoord.z默认产生的方法不一样,具体来说,是透视除法计算的时机不同)。pack(
)函数采用的是普遍的一种将float打包成vec4的一种方案,下面的片断着色器也有解包的步骤。

下面是第二遍渲染的着色器代码:


// Common.vert

// 属性变量
attribute vec3 position;
attribute vec3 normal;
attribute vec2 texCoord;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 modelViewNormalMatrix;
uniform mat4 lightViewProjectionMatrix;

// 转换到varying中的
varying vec3 viewSpacePosition;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec4 v_shadowCoord;

void main( void )
{
    viewSpacePosition = vec3( viewMatrix * modelMatrix * vec4( position, 1.0 ) );

    v_texCoord = texCoord;

    v_normal = modelViewNormalMatrix * normal;

    v_shadowCoord = lightViewProjectionMatrix *
            modelMatrix *
            vec4( position, 1.0 );

    gl_Position = projectionMatrix *
            viewMatrix *
            modelMatrix *
            vec4( position, 1.0 );
}
// Common.frag

uniform sampler2D texture;
uniform sampler2D shadowTexture;

uniform vec3 lightPosition;
uniform mat4 viewMatrix;

varying vec3 viewSpacePosition;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec4 v_shadowCoord;

float unpack (vec4 colour)
{
    const vec4 bitShifts = vec4(1.0 / (256.0 * 256.0 * 256.0),
                                1.0 / (256.0 * 256.0),
                                1.0 / 256.0,
                                1);
    return dot(colour , bitShifts);
}

float shadowSimple( )
{
    vec4 shadowMapPosition = v_shadowCoord / v_shadowCoord.w;

    shadowMapPosition = (shadowMapPosition + 1.0) /2.0;

    vec4 packedZValue = texture2D(shadowTexture, shadowMapPosition.st);

    float distanceFromLight = unpack(packedZValue);

    //add bias to reduce shadow acne (error margin)
    float bias = 0.0005;

    //1.0 = not in shadow (fragment is closer to light than the value stored in shadow map)
    //0.0 = in shadow
    return float(distanceFromLight > shadowMapPosition.z - bias);
}

void main( )
{
    vec3 viewSpaceLightPosition = vec3( viewMatrix * vec4( lightPosition, 1.0 ) );
    vec3 lightVector = viewSpaceLightPosition - viewSpacePosition;
    lightVector = normalize( lightVector );
    float NdotL = dot( v_normal, lightVector );

    float diffuse = max( 0.0, NdotL );
    float ambient = 0.3;

    float shadow = 1.0;
    if ( v_shadowCoord.w > 0.0 )
    {
        shadow = shadowSimple( );
        shadow = shadow * 0.8 + 0.2;
    }

    vec4 textureColor = texture2D( texture, v_texCoord );
    gl_FragColor = ( textureColor * ( diffuse + ambient ) * shadow );
}

这里的顶点着色器,核心的内容是将“光源的投影矩阵 * 摄像机的视图矩阵* 模型矩阵 * 模型的顶点数据”作为阴影的坐标加以储存起来,在片断处理阶段,它主要用来为深度图采样用。同样的unpack( )函数和上述提到的pack( )函数是完全相反的操作,这一对函数可以将float数据以几乎不失真的代价还原出来。

3、效果

我将第一版程序经过多次重构使用Qt的方式重写了一遍,使用QOpenGLWidget作为渲染窗体,至少能够在Linux、Windows和Mac上顺利地运行,至于能否在移动平台上运行,还没有确定,不过接下来的一次重构,将阴影映射使用Qt Quick重写,就能够在移动平台上顺利地运行了。

操作方法:鼠标左键旋转中间的立方体,右键显示深度图。下面是深度图:

程序源代码:这里

时间: 2024-10-22 17:00:34

阴影映射(Shadow Map)的研究(四)的相关文章

阴影映射(Shadow Map)的研究(三)

阴影映射(Shadow Map)的研究(三) 最近为了自己制作的项目可是吃了不少苦头,这其中关键的一点就是想要实现阴影映射(Shadow Map).为了实现目标,我参考了网络上很多相关的资料,也看了一些案例,最终花了我一个月的时间将这个效果实现了. 阴影映射这样的效果,其实在即将发布的Qt 3D中已经有相关的介绍,KDAB中有一篇文章<Shadow Mappingin Qt3D 2.0>就在Qt 3D的框架上实现了阴影映射.不过当时这个效果是假定目标机器支持OpenGL 3.0规范的,目前大部

阴影映射(Shadow Map)的研究(二)

阴影映射(Shadow Map)的研究(二) 上一篇文章介绍了我对Z缓存的较为详细的研究.这里之所以对Ze求导函数,是因为的我们需要寻找它的变化曲线,从而找到极值点,这样就能够确定Ze相对于zw的疏密分布情况.幸运的是,我们找到的导函数是双曲函数,并且我们关心的的右侧是单调递增的. 蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/44946763.欢迎同行前来探讨. 引出上一篇文章的结论,当 时,导函数取得最大值.但是在Zw∈

阴影映射(Shadow Map)的研究(五)

阴影映射(Shadow Map)的研究(五) 我成功地将别人的例子加以改进,使用QOpenGLWidget作为渲染窗口,将阴影映射渲染了出来.目前可以确定的是,使用OpenGL ES 2.0作为渲染的接口要求,能够让目前绝大多数机器都能够顺利兼容,但是囿于渲染窗口,可能在某些平台上表现不好.如果移植到Qt Quick 2,这样能够支持的平台就更多了.现在我将这些接口统统使用Qt的方式实现了,移植到Qt Quick 2也很简单. 这里主要参考的是OpenGLUnderQML这个例子,自定义了一个Q

阴影映射(Shadow Map)的研究(六)

阴影映射(Shadow Map)的研究(六) 成功地将阴影映射与Qt Quick 2整合之后,接下来可以将阴影映射的效果变得更漂亮一些.如果你成功地运行过我制作的演示程序,那么就会发现,阴影映射的效果并不是那么理想,可能有噪点(粉刺)的出现.这个是和阴影的产生相关,主要还是由于阴影映射这个算法它要求产生的阴影精度是有限的.很多改进的算法都是围绕着如何让阴影更加自然进行研究的.这里我也尝试模仿了一个稍微简单的算法:PCF算法. PCF算法的理念也比较简单,简言之就是让产生的阴影更加模糊.它主要在阴

Shadow Map阴影贴图技术之探 【转】

这两天勉勉强强把一个shadowmap的demo做出来了.参考资料多,苦头可不少.Shadow Map技术是目前与Shadow Volume技术并行的传统阴影渲染技术,而且在游戏领域可谓占很大优势.本篇是第一辑.——ZwqXin.comShadow Map的原理很简单,但是实现起来到处是雷.当然这只是我的体会.恩,不过就是“从光源处看场景,那些看不见的区域全部都该是阴影”.很容易看出,与针对 特定模型的Shadow Volume不同,Shadow Map是针对场景的.这就是说,对一个光源应用一次

[OpenGL] shadow mapping(实时阴影映射)

source:原文地址 code:点击可以直接下载源代码 1978年,Lance Williams在其发表的论文<Casting curved shadows on curved surfaces>中提出了Shadow mapping算法,从那以后,该算法在离线渲染和实时渲染两个领域都得到了广泛的应用.皮尔斯动画工作室的Renderman渲染器.以及一些知名电影如<玩具总动员>都使用了shadow mapping技术. 在众多图形应用的阴影技术中,shadow mapping只是产

Shadow Map阴影贴图技术之探

这两天勉勉强强把一个shadowmap的demo做出来了.参考资料多,苦头可不少.Shadow Map技术是目前与Shadow Volume技术并行的传统阴影渲染技术,而且在游戏领域可谓占很大优势.本篇是第一辑.--ZwqXin.comShadow Map的原理很简单,但是实现起来到处是雷.当然这只是我的体会.恩,不过就是"从光源处看场景,那些看不见的区域全部都该是阴影".很容易看出,与针对特定模型的Shadow Volume不同,Shadow Map是针对场景的.这就是说,对一个光源

OpenGL 阴影之Shadow Mapping和Shadow Volumes

先说下开发环境.VS2013,C++空项目,引用glut,glew.glut包含基本窗口操作,免去我们自己新建win32窗口一些操作.glew使我们能使用最新opengl的API,因winodw本身只包含opengl 1.1版本的API,根本是不能用的. 其中矩阵计算采用gitHub项目openvr中的三份文件, Vectors.h ,Matrices.h, Matrices.cpp,分别是矢量与点类,矩阵类,我们需要的一些操作,矢量的叉乘和点乘,矩阵转置,矩阵的逆,矩阵与矢量相剩等. 这里主要

OpenGL阴影,Shadow Mapping(附源程序)

实验平台:Win7,VS2010 先上结果截图(文章最后下载程序,解压后直接运行BIN文件夹下的EXE程序): 本文描述图形学的两个最常用的阴影技术之一,Shadow Mapping方法(另一种是Shadow Volumes方法).在讲解Shadow Mapping基本原理及其基本算法的OpenGL实现之后,将继续深入分析解决几个实际问题,包括如何处理全方向点光源.多个光源.平行光.最近还有可能写一篇Shadow Volumes的博文(目前已经将基本理论弄清楚了),在那里,将对Shadow Ma