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

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount