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

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

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

PCF算法的理念也比较简单,简言之就是让产生的阴影更加模糊。它主要在阴影映射的第二遍渲染中做文章。在第二遍渲染的着色器中,执行一个循环,对每一个阴影FBO纹理的从(x-1.5,y-1.5)到(x+1.5,y+1.5)范围内做4 *4=16的采样,采样出来的数据最后做一次平均,最终得到一个平均的阴影。这样,能够较好地减少shadow acne(阴影粉刺)的不和谐的效果。

为了方便以后查阅,将改进后的片断着色器贴出来,其中包含了PCF阴影:

// Common.frag

#ifdef GL_ES
precision highp float;
#endif
const float c_NoShadow      = 0.0;
const float c_SimpleShadow  = 1.0;
const float c_SimpleShadowDynamicBias = 2.0;
const float c_PCFShadow     = 3.0;

const float c_NoLight       = 0.0;
const float c_SimpleLight   = 1.0;

uniform sampler2D texture;
uniform sampler2D shadowTexture;

uniform vec3 lightPosition;
uniform mat4 viewMatrix;
uniform float lightType;
uniform float shadowType;

// PCF专用
uniform float pixelOffsetX;
uniform float pixelOffsetY;

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

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 simpleShadow( float bias )
{
    vec4 shadowMapPosition = lightProjectedPosition / lightProjectedPosition.w;
    shadowMapPosition = ( shadowMapPosition + 1.0 ) / 2.0;

    vec4 packedZValue = texture2DProj( shadowTexture, shadowMapPosition );

    float distanceFromLight = unpack( packedZValue );
    float shadowZ = unpack( packedZValue );

    return float( shadowZ > shadowMapPosition.z - bias );
}

float calcBias( )
{
    float bias;

    vec3 n = normalize( v_normal );
    // Direction of the light (from the fragment to the light)
    vec3 l = normalize( lightPosition );

    // Cosine of the angle between the normal and the light direction,
    // clamped above 0
    //  - light is at the vertical of the triangle -> 1
    //  - light is perpendiular to the triangle -> 0
    //  - light is behind the triangle -> 0
    float cosTheta = clamp( dot( n, l ), 0.0, 1.0 );

    bias = 0.0001 * tan( acos( cosTheta ) );
    bias = clamp( bias, 0.0, 0.01 );

    return bias;
}

float lookup( vec2 offSet, float bias )
{
    vec4 shadowMapPosition = lightProjectedPosition / lightProjectedPosition.w;
    shadowMapPosition = ( shadowMapPosition + 1.0 ) / 2.0;

    vec4 coordOffset = vec4( offSet.x * pixelOffsetX, offSet.y * pixelOffsetY, 0.05, 0.0 );
    vec4 packedZValue = texture2DProj( shadowTexture, shadowMapPosition + coordOffset );
    float distanceFromLight = unpack( packedZValue );
    float shadowZ = unpack( packedZValue );
    return float( shadowZ > shadowMapPosition.z - bias );
}

float PCFShadow( float bias )// PCF阴影
{
    float shadow = 0.0;

    for ( float y = -1.5; y <= 1.5; y = y + 1.0 )
    {
        for ( float x = -1.5; x <= 1.5; x = x + 1.0 )
        {
            shadow += lookup( vec2( x, y ), bias );
        }
    }

    shadow /= 16.0;
    return shadow;
}

void main( )
{
    // 计算纹理
    vec4 textureColor = texture2D( texture, v_texCoord );

    // 计算光照
    vec4 lightColor = vec4( 1.0 );
    if ( lightType <= c_NoLight )
    {
        // 没有光照,没有任何操作
    }
    else if ( lightType <= c_SimpleLight )
    {
        vec4 viewSpaceLightPosition = viewMatrix * vec4( lightPosition, 1.0 );
        vec4 lightVector = viewSpaceLightPosition - viewSpacePosition;
        lightVector = normalize( lightVector );
        float NdotL = dot( v_normal, vec3( lightVector ) );

        float diffuse = max( 0.0, NdotL );
        float ambient = 0.3;
        lightColor = vec4( ambient + diffuse );
    }

    // 计算阴影
    float shadow = 1.0;
    if ( shadowType <= c_NoShadow )
    {
        // 没有阴影
    }
    else
    {
        if ( lightProjectedPosition.w > 0.0 )
        {
            if ( shadowType <= c_SimpleShadow )
            {
                shadow = simpleShadow( 0.0005 );
            }
            else if ( shadowType <= c_SimpleShadowDynamicBias )
            {
                shadow = simpleShadow( calcBias( ) );
            }
            else if ( shadowType <= c_PCFShadow )
            {
                shadow = PCFShadow( calcBias( ) );
            }
            shadow = shadow * 0.8 + 0.2;
        }
    }
    gl_FragColor = textureColor * lightColor * shadow;
}

下面是PCF阴影以及普通阴影的比较:

这次的程序,除了Windows等桌面平台,在Android以及Windows Phone均可以运行。

有关PCF的详细原理,可以参考NVIDIA的资料:这里

时间: 2024-12-29 07:05:21

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

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

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

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是针对场景的.这就是说,对一个光源

[ZZ] Shadow Map

Shadow Map 如何能够高效的产生更接近真实的阴影一直是视频游戏的一个很有挑战的工作,本文介绍目前所为人熟知的两种阴影技术之一的ShadowMap(阴影图)技术.     ShadowMap技术的概念应该说是最早应用在视频游戏中的阴影实现技术,有着非常高效和快速的特点,在实现阴影的同时只需要相对很小的计算负担.     ShadowMap绘制阴影主要是通过一张额外的阴影贴图来实现的,在早期的3D游戏中人物等动态运动的物体通常不绘制阴影,而场景内遮蔽关系相对确定的静态物体的阴影通常是在建立模

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,分别是矢量与点类,矩阵类,我们需要的一些操作,矢量的叉乘和点乘,矩阵转置,矩阵的逆,矩阵与矢量相剩等. 这里主要