Direct9学习之--------------------------实时阴影的另一种实现ShadowMap

本文主要是介绍Direct9学习之--------------------------实时阴影的另一种实现ShadowMap,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ShadowMap

1、原理

阴影实时渲染是计算机图形学的高级技术。它能提高场景的真实感。两种通用的阴影渲染技术分别是地图阴影(shadow map)和体积阴影(shadow volumes)。地图阴影的优势在于效率很高,因为地图阴影只需要渲染场景两次(一般来说)。并且不需要进行几何处理和产生额外的mesh。无论多复杂的场景,使用地图阴影总能保持很好的性能。

地图阴影的概念很直观。首先,从光线的方向把整个场景渲染到一张纹理上。通过特定的顶点和像素渲染器渲染得到的这张纹理就被叫做地图阴影(shadow map)。这张纹理的特点是其上每一个像素记录的不是该像素的颜色,而是像素的深度。记某个像素点的深度为Di。

接下来再一次渲染场景,本次渲染过程中,计算每一个与地图阴影对应的每个像素点到光源的距离,记作Dj。然后把Di与Dj进行比较,如果两者匹配,该像素不在阴影中。如果Di小于Dj,该像素在阴影中。

最后,根据比较结果,像素渲染器更新每一个像素的颜色。渲染地图阴影最适合的灯光是聚光灯(spotlight),因为地图阴影通过投影场景到其上面而被渲染。

程序初始化阶段,范例创建一张D3DFMT_R32F格式的纹理来承载shadowmap,选择这种格式的纹理是因为地图阴影只需要一个通道(记录深度),32位能够提供足够的精度。渲染由两个部分组成:

1、创建阴影地图。

2、通过阴影地图渲染场景。

2、创建地图阴影(ShadowMap)

地图阴影首先将用于承载地图阴影的纹理作为渲染目标。

LPDIRECT3DSURFACE9 pOldRT = NULL;

    V( pd3dDevice->GetRenderTarget( 0, &pOldRT ) );

    LPDIRECT3DSURFACE9 pShadowSurf;

    if( SUCCEEDED( g_pShadowMap->GetSurfaceLevel( 0, &pShadowSurf ) ) )

    {

        pd3dDevice->SetRenderTarget( 0, pShadowSurf );

        SAFE_RELEASE( pShadowSurf );

    }

创建地图阴影的两个渲染器分别是VertShader和PixShader。VertShader将输入的坐标转化到灯光的投影空间:

void VertShadow( float4 Pos : POSITION, float3 Normal : NORMAL,

                 out float4 oPos : POSITION, out float2 Depth : TEXCOORD0 )

{

    oPos = mul( Pos, g_mWorldView );

    oPos = mul( oPos, g_mProj );

}

g_mProj就是灯光投影空间,它的定义是:

D3DXMatrixPerspectiveFovLH( &g_mShadowProj, g_fLightFov, 1, 0.01f, 100.0f);

转化到这个投影空间就意味着摄像机从位于灯光的位置,且朝着灯光的方向望去。然后,VertexShader又传递纹理坐标投影的zw到像素渲染器:

Depth.xy = oPos.zw;

此时,像素渲染器每个像素都有了独一无二的z和w值。像素渲染器输出z/w值到渲染目标。

void PixShadow( float2 Depth : TEXCOORD0, out float4 Color : COLOR )

{

    Color = Depth.x / Depth.y;

}

这个值代表了场景中每个像素的深度,它的取值范围在0~1之间。在近剪裁面处,它的值是0,在远剪裁面处,他的值是1。渲染完成后,阴影地图就包含了每个像素的深度值。

3、渲染场景

带阴影的场景通过VertScene和PixScene两个渲染器渲染得到。VertScene将场景中的顶点转化到到投影坐标系,传递纹理坐标到像素渲染器。另外,它还输出:各顶点和法线在视(view space)内的坐标vPosvNormal。再另外,它还输出:各顶点在灯光投影空间内的坐标vPosLight

void VertScene( float4 iPos : POSITION,

float3 iNormal : NORMAL,

                float2 iTex : TEXCOORD0,

                out float4 oPos : POSITION,

                out float2 Tex : TEXCOORD0,

                out float4 vPos : TEXCOORD1,

                out float3 vNormal : TEXCOORD2,

                out float4 vPosLight : TEXCOORD3 )

{

  

    vPos = mul( iPos, g_mWorldView );

    oPos = mul( vPos, g_mProj );

    vNormal = mul( iNormal, (float3x3)g_mWorldView );

    Tex = iTex;

    vPosLight = mul( vPos, g_mViewToLightProj );

}

此时的g_mProj是g_VCamera.GetProjMatrix()。也就是说是观察者摄像机的投影方向(注意区别于1中g_mProj)。其中,vPosvNormal是在后续程序中用于光线计算。vPosLight是shadowmap的纹理坐标。vPosLight这个坐标是如何获得的呢?如果观察者相机位于灯光处且观察方向与灯光方向相同,通过将世界坐标变换到灯光视坐标,再进行灯光投影变换得到!

const D3DXMATRIX *pmView =

g_bCameraPerspective ? g_VCamera.GetViewMatrix() : &mLightView;

......

D3DXMATRIXA16 mViewToLightProj;

mViewToLightProj = *pmView;

D3DXMatrixInverse( &mViewToLightProj, NULL, &mViewToLightProj );

D3DXMatrixMultiply( &mViewToLightProj, &mViewToLightProj, &mLightView );

D3DXMatrixMultiply( &mViewToLightProj, &mViewToLightProj, &g_mShadowProj );

V( g_pEffect->SetMatrix( "g_mViewToLightProj", &mViewToLightProj ) );

像素着色器测试每个像素是否在阴影内。首先,像素着色其检测该点是否位于光圈中(聚光灯形成的那个亮的区域)。

if( dot( vLight, g_vLightDir ) > g_fCosTheta )

    其中,g_vLightDir是灯光的方向,vLight是某像素到灯光的方向。

float3 vLight = normalize( float3( vPos - g_vLightPos ) );

如果位于光圈内,检查它是否位于阴影内。这是通过把vPosLight(灯光位置在灯光投影空间内的坐标)转化到0到1的范围内,

float2 ShadowTexC = 0.5 * vPosLight.xy / vPosLight.w + float2( 0.5, 0.5 );

    目的是为了将它与shadowmap的贴图匹配。

由于在贴图空间内,坐标轴y是向下的,与投影坐标系内正好相反,所以要把投影坐标系里的y值反向。

ShadowTexC.y = 1.0f - ShadowTexC.y;

接下来,通过该坐标对shadowmap进行检查。

首先获得shadowmap上某点的颜色值,此时的颜色值代表的是深度(前面处理过的)。

tex2D(g_samShadow, ShadowTexC )

然后把它和当前渲染的图片对应shadowmap上的“某点”的像素值与灯泡的距离

vPosLight.z / vPosLight.w

进行比较,如果shadowmap的深度大,则该点没有在阴影中。

没在阴影中的话,它的‘源值’(sourcevals = source values),意思应该就是说,光就强。设其源值为1.0.否则为0,表示光线弱.

float sourcevals;

sourcevals = ((tex2D( g_samShadow, ShadowTexC ) + SHADOW_EPSILON) < (vPosLight.z / vPosLight.w))? 0.0f: 1.0f; 

最后,由这个‘源值’来控制该点的Diffuss。从而渲染出带阴影的场景。

Diffuse = (sourcevals * ( 1 - g_vLightAmbient ) + g_vLightAmbient ) * g_vMaterial;

其中SDK的例子中还使用了2×2邻近点过滤的办法什么意思呢?就是说shadowmap上的像素点和当前渲染的图片的像素点不是一一对应的。它是从shadowmap上取4个点

tex2D( g_samShadow, ShadowTexC )

tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 0) )

tex2D( g_samShadow, ShadowTexC + float2(0, 1.0/SMAP_SIZE) )

tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 1.0/SMAP_SIZE) )

仔细一看就知道,这四点分别是对应点,对应点左边,下边和左下边的点。比较完以后就存入

float sourcevals[4];

中。然后用‘邻近点过滤’的办法来综合处理这四个点来得到一个‘光总量’:

float LightAmount

然后再用‘光总量’来控制Diffuss,从而渲染出带阴影的场景。光总量的获得也很简单,lerp是个HLSL的函数,原型为ret lerp(x,y,s),含义是:ret = x + s(y - x)。leprs是是texelpos的小数部分, SMAP_SIZE * ShadowTexC的结果带有小数部分,小数部分表示shadowmap和场景渲染对应点之间错开的一小段距离。此时邻近点过滤的方法认为,该点的对错开的距离与权重成反比。

float2 texelpos = SMAP_SIZE * ShadowTexC;

float2 lerps = frac( texelpos );

......

float LightAmount = lerp( lerp( sourcevals[0], sourcevals[1], lerps.x ), lerp( sourcevals[2], sourcevals[3], lerps.x ), lerps.y );


这篇关于Direct9学习之--------------------------实时阴影的另一种实现ShadowMap的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1099300

相关文章

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

Python脚本实现自动删除C盘临时文件夹

《Python脚本实现自动删除C盘临时文件夹》在日常使用电脑的过程中,临时文件夹往往会积累大量的无用数据,占用宝贵的磁盘空间,下面我们就来看看Python如何通过脚本实现自动删除C盘临时文件夹吧... 目录一、准备工作二、python脚本编写三、脚本解析四、运行脚本五、案例演示六、注意事项七、总结在日常使用

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

使用Python实现在Word中添加或删除超链接

《使用Python实现在Word中添加或删除超链接》在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能,本文将为大家介绍一下Python如何实现在Word中添加或... 在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超

windos server2022里的DFS配置的实现

《windosserver2022里的DFS配置的实现》DFS是WindowsServer操作系统提供的一种功能,用于在多台服务器上集中管理共享文件夹和文件的分布式存储解决方案,本文就来介绍一下wi... 目录什么是DFS?优势:应用场景:DFS配置步骤什么是DFS?DFS指的是分布式文件系统(Distr

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服

C#使用yield关键字实现提升迭代性能与效率

《C#使用yield关键字实现提升迭代性能与效率》yield关键字在C#中简化了数据迭代的方式,实现了按需生成数据,自动维护迭代状态,本文主要来聊聊如何使用yield关键字实现提升迭代性能与效率,感兴... 目录前言传统迭代和yield迭代方式对比yield延迟加载按需获取数据yield break显式示迭

Python实现高效地读写大型文件

《Python实现高效地读写大型文件》Python如何读写的是大型文件,有没有什么方法来提高效率呢,这篇文章就来和大家聊聊如何在Python中高效地读写大型文件,需要的可以了解下... 目录一、逐行读取大型文件二、分块读取大型文件三、使用 mmap 模块进行内存映射文件操作(适用于大文件)四、使用 pand

python实现pdf转word和excel的示例代码

《python实现pdf转word和excel的示例代码》本文主要介绍了python实现pdf转word和excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、引言二、python编程1,PDF转Word2,PDF转Excel三、前端页面效果展示总结一