平面镜像的实现

在自然界中有许多物体的表面都非常光滑,可以像镜子一样反射周围的物体。本节介绍了如何在3D应用程序中模拟镜像效果。为简单起见,我们降低了任务难度,只在平面上实现镜像效果。例如,一辆光滑的汽车可以反射周围的物体;但是,车身是一个平滑曲面,而非平面。我们不选择这样的物体。我们将在光滑的大理石地板或挂在墙上的镜子中渲染物体的映像——换句话说,我们只实现平面上的镜像效果。

要在程序中实现镜像效果,必须解决两个问题。首先,我们必须知道如何在一个任意平面上反射物体,正确地绘制该物体的映像。其次,我们只能在镜子里面显示映像;也就是,我们必须以某种方式将一个表面“标记”为镜子,然后在渲染时只在镜子里面绘制物体映像。回顾图10.1,它最先引入了这一概念。

第一个问题可以很容易地通过解析几何来解决,具体请参见附录C。第二个问题可以通过模板缓冲区来解决。

1. 平面镜像实现原理及步骤

注意:当绘制映像时,我们还需要在镜子平面上反射光源。否则,映像中的光照会显得很不真实。


上图说明了要绘制一个物体的映像,我们必须在镜子平面上对它进行反射。不过,这会出现下图所示的问题。

即,物体映像(在本例中是头骨)会被渲染到镜子之外的区域(例如,墙面)。映像只应该显示在镜子里面。我们可以使用模板缓冲区来解决一问题,因为模板缓冲区可以阻止像素渲染到后台缓冲区的某些区域上。所以,我们可以使用模板缓冲区来控制头骨的映像,避免映像渲染到镜子之外的区域。下面给出了具体的实现步骤:

1.将地板、墙壁和头骨(不包括镜子)渲染到后台缓冲区。注意,这一步不修改模板缓冲区。

2.将模板缓冲区清为0。下图展示了此时的后台缓冲区和模板缓冲区。


(将场景渲染到后台缓冲区,并将模板缓冲区清为0(由浅灰色表示)。模板缓冲区上的黑色轮廓线用于说明后台缓冲区像素与模板缓冲区像素之间的对应关系——它们并不代表绘制在模板缓冲区上的任何数据。)

3.把镜子只渲染到模板缓冲区。我们可以通过设置以下混合状态禁止颜色写入到后台缓冲区中:

D3D11_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask = 0;

通过以下设置禁止写入到深度缓冲区中:

D3D11_DEPTH_STENCIL_DESC::DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;

在将镜子绘制到模板缓冲时,我们将模板测试函数设为D3D11_COMPARISON_ALWAYS(始终成功),并指定当模板测试成功时将模板缓冲区元素替换(D3D11_STENCIL_OP_REPLACE)为1(StencilRef)。将StencilDepthFailOp设为D3D11_STENCIL_OP_KEEP,当深度测试失败时不更新模板缓冲区(如果头骨挡住了镜子的某一部分,那么就会发生这种情况)。由于我们只将镜子绘制到模板缓冲区,所以在模板缓冲区中只有镜子的可视区域对应的像素为1,而其他像素均为0。下图展示了更新后的模板缓冲区。实际上,我们是给镜子在模板缓冲区中的可视区域做了标记。


(把镜子渲染到模板缓冲区,这个操作的实际上是在模板缓冲区中标记了镜子的可视区域。实心黑色区域的模板元素值为1。注意,被盒子挡住的区域不会设为1,因为这一部分根本无法通过深度测试(盒子挡住了镜子前面的这一部分)。)

4.现在我们将头骨映像渲染到后台缓冲区和模板缓冲区。但是要记住,只有通过了模板测试的像素片段才能渲染到后台缓冲区中。这次,我们要将模板测试函数设为D3D11_COMPARISON_EQUAL,使模板元素为1时测试成功。通过一方式,头骨映像只会渲染到模板元素为1的区域中。由于在模板缓冲区中只有镜子的可视区域的模板元素为 1,所以头骨映像只会被渲染到镜子里面。

5.最后,我们将镜子绘制到后台缓冲区。但是,为了为了能显示镜子之后的头骨镜像,我们需要使用透明混合绘制镜子,如果不这样做,那么镜子就会挡住位于它后面的头骨镜像。要实现这个效果,我们只需定义一个镜子用的材质实例,将漫反射分量的alpha通道设置为0.5,这样镜子表示镜子是半透明的,然后就可以像9.5.4节那样绘制半透明的镜子了:

mMirrorMat.Ambient  = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
mMirrorMat.Diffuse  = XMFLOAT4(1.0f, 1.0f, 1.0f, 0.5f);
mMirrorMat.Specular = XMFLOAT4(0.4f, 0.4f, 0.4f, 16.0f);

上述设置给出以下混合方程:

C = 0.5•Csrc + 0.5•Cdst

假设我们已将头骨镜像的像素发送到后台缓冲区中,则50%的颜色来自于镜子(源),50%的颜色来自于头骨(目标)。

2. 定义深度/模板状态代码示例

要实现上述算法,我们必须定义两个深度/模板状态。一个用于在模板缓冲区上绘制镜子,标记镜子的可视区域。另一个用于绘制头骨映像,使映像只显示在镜子里面。

//
// 标记镜子的深度模板描述
//D3D11_DEPTH_STENCIL_DESC mirrorDesc;
mirrorDesc.DepthEnable      = true;
mirrorDesc.DepthWriteMask   = D3D11_DEPTH_WRITE_MASK_ZERO;
mirrorDesc.DepthFunc        = D3D11_COMPARISON_LESS;
mirrorDesc.StencilEnable    = true;
mirrorDesc.StencilReadMask  = 0xff;
mirrorDesc.StencilWriteMask = 0xff;mirrorDesc.FrontFace.StencilFailOp      = D3D11_STENCIL_OP_KEEP;
mirrorDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
mirrorDesc.FrontFace.StencilPassOp      = D3D11_STENCIL_OP_REPLACE;
mirrorDesc.FrontFace.StencilFunc        = D3D11_COMPARISON_ALWAYS;// 我们不渲染背面多边形,所以这些设置并不重要
mirrorDesc.BackFace.StencilFailOp       = D3D11_STENCIL_OP_KEEP;
mirrorDesc.BackFace.StencilDepthFailOp  = D3D11_STENCIL_OP_KEEP;
mirrorDesc.BackFace.StencilPassOp       = D3D11_STENCIL_OP_REPLACE;
mirrorDesc.BackFace.StencilFunc         = D3D11_COMPARISON_ALWAYS;HR(device->CreateDepthStencilState(&mirrorDesc, &MarkMirrorDSS));//
// 绘制反射的深度模板描述
//D3D11_DEPTH_STENCIL_DESC drawReflectionDesc;
drawReflectionDesc.DepthEnable      = true;
drawReflectionDesc.DepthWriteMask   = D3D11_DEPTH_WRITE_MASK_ALL;
drawReflectionDesc.DepthFunc        = D3D11_COMPARISON_LESS;
drawReflectionDesc.StencilEnable    = true;
drawReflectionDesc.StencilReadMask  = 0xff;
drawReflectionDesc.StencilWriteMask = 0xff;drawReflectionDesc.FrontFace.StencilFailOp      = D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.FrontFace.StencilFunc   = D3D11_COMPARISON_EQUAL;// 我们不渲染背面多边形,所以这些设置并不重要
drawReflectionDesc.BackFace.StencilFailOp      = D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.BackFace.StencilFunc   = D3D11_COMPARISON_EQUAL;HR(device->CreateDepthStencilState(&drawReflectionDesc, &DrawReflectionDSS));

3. 绘制代码示例

下面是draw方法的代码。为了突出重点,我们略去了不相关的细节,比如设置常量缓冲区的值(完整的细节请到DirectX11 龙书官网下载完整项目源代码)。

//
// 镜子只绘制到模板缓冲
//activeTech->GetDesc( &techDesc );
for(UINT p = 0; p < techDesc.Passes; ++p)
{ID3DX11EffectPass* pass = activeTech->GetPassByIndex( p );md3dImmediateContext->IASetVertexBuffers(0, 1, &mRoomVB, &stride, &offset);// Set per object constants.XMMATRIX world = XMLoadFloat4x4(&mRoomWorld);XMMATRIX worldInvTranspose = MathHelper::InverseTranspose(world);XMMATRIX worldViewProj = world*view*proj;Effects::BasicFX->SetWorld(world);Effects::BasicFX->SetWorldInvTranspose(worldInvTranspose);Effects::BasicFX->SetWorldViewProj(worldViewProj);Effects::BasicFX->SetTexTransform(XMMatrixIdentity());// 不写入渲染目标md3dImmediateContext->OMSetBlendState(RenderStates::NoRenderTargetWritesBS, blendFactor, 0xffffffff);// 将镜子可见部分的像素绘制到模板缓冲中。// 但不要将镜子的深度信息写入深度缓冲中,否则会遮挡之后的头骨镜像。md3dImmediateContext->OMSetDepthStencilState(RenderStates::MarkMirrorDSS, 1);pass->Apply(0, md3dImmediateContext);md3dImmediateContext->Draw(6, 24);// 恢复之前的状态md3dImmediateContext->OMSetDepthStencilState(0, 0);md3dImmediateContext->OMSetBlendState(0, blendFactor, 0xffffffff);
}//
// 绘制头骨镜像
//
activeSkullTech->GetDesc( &techDesc );
for(UINT p = 0; p < techDesc.Passes; ++p)
{ID3DX11EffectPass* pass = activeSkullTech->GetPassByIndex( p );md3dImmediateContext->IASetVertexBuffers(0, 1, &mSkullVB, &stride, &offset);md3dImmediateContext->IASetIndexBuffer(mSkullIB, DXGI_FORMAT_R32_UINT, 0);XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy planeXMMATRIX R = XMMatrixReflect(mirrorPlane);XMMATRIX world = XMLoadFloat4x4(&mSkullWorld) * R;XMMATRIX worldInvTranspose = MathHelper::InverseTranspose(world);XMMATRIX worldViewProj = world*view*proj;Effects::BasicFX->SetWorld(world);Effects::BasicFX->SetWorldInvTranspose(worldInvTranspose);Effects::BasicFX->SetWorldViewProj(worldViewProj);Effects::BasicFX->SetMaterial(mSkullMat);// 保存之前的光照方向,然后反射光照方向XMFLOAT3 oldLightDirections[3];for(int i = 0; i < 3; ++i){oldLightDirections[i] = mDirLights[i].Direction;XMVECTOR lightDir = XMLoadFloat3(&mDirLights[i].Direction);XMVECTOR reflectedLightDir = XMVector3TransformNormal(lightDir, R);XMStoreFloat3(&mDirLights[i].Direction, reflectedLightDir);}Effects::BasicFX->SetDirLights(mDirLights);// 在反射中剔除顺时针绕行的三角形md3dImmediateContext->RSSetState(RenderStates::CullClockwiseRS);// 根据模板标记绘制镜子可见部分中的头骨镜像md3dImmediateContext->OMSetDepthStencilState(RenderStates::DrawReflectionDSS, 1);pass->Apply(0, md3dImmediateContext);md3dImmediateContext->DrawIndexed(mSkullIndexCount, 0, 0);// 恢复默认状态。md3dImmediateContext->RSSetState(0);md3dImmediateContext->OMSetDepthStencilState(0, 0); // 恢复光照方向for(int i = 0; i < 3; ++i){mDirLights[i].Direction = oldLightDirections[i];}Effects::BasicFX->SetDirLights(mDirLights);
}//
// 将镜子绘制到后台缓冲区,需要镜像透明混合,
// 这样才能显示镜子后面的镜像
//activeTech->GetDesc( &techDesc );
for(UINT p = 0; p < techDesc.Passes; ++p)
{ID3DX11EffectPass* pass = activeTech->GetPassByIndex( p );md3dImmediateContext->IASetVertexBuffers(0, 1, &mRoomVB, &stride, &offset);// Set per object constants.XMMATRIX world = XMLoadFloat4x4(&mRoomWorld);XMMATRIX worldInvTranspose = MathHelper::InverseTranspose(world);XMMATRIX worldViewProj = world*view*proj;Effects::BasicFX->SetWorld(world);Effects::BasicFX->SetWorldInvTranspose(worldInvTranspose);Effects::BasicFX->SetWorldViewProj(worldViewProj);Effects::BasicFX->SetTexTransform(XMMatrixIdentity());Effects::BasicFX->SetMaterial(mMirrorMat);Effects::BasicFX->SetDiffuseMap(mMirrorDiffuseMapSRV);// 镜子md3dImmediateContext->OMSetBlendState(RenderStates::TransparentBS, blendFactor, 0xffffffff);pass->Apply(0, md3dImmediateContext);md3dImmediateContext->Draw(6, 24);
}

4. 环绕顺序和反射

下面这段话是书上原文:

当三角形被反射到平面上时,它的环绕顺序没有改变,因此平面法线也不会翻转。所以,在反射之后,原来向外的法线会变成向内的法线(参见下图)。为了纠正一错误,我们必须告诉Direct3D将逆时针环绕的三角形视为朝前的三角形,将顺时针环绕的三角形视为朝后的三角形(与我们在5.10.2节的约定恰好相反)。这样可以有效地翻转法线方向,使它们在反射之后仍然向外。


(多边形法线不会随着映像而翻转,在反射之后,原来向外的法线会变成向内的法线。)

但是!!本人经过验证后,发现龙书这里似乎有误,当三角形被反射到平面,环绕顺序没有改变,但是平面法线经过镜像变换后也是为朝外。但是我们也要将朝前的三角形顺序改为逆时针,将朝后的三角形顺序改为顺时针,这是因为避免背面消隐方向错误。

我们通过设置如下光栅器状态来翻转环绕顺序:

//
// CullClockwiseRS
//// 注意:把朝前的三角形定义为逆时针方向绕行使我们仍然可以进行背面剔除。
// 如果我们关闭背面剔除,就不得不要考虑D3D11_DEPTH_STENCIL_DESC中的BackFace属性了。
D3D11_RASTERIZER_DESC cullClockwiseDesc;
ZeroMemory(&cullClockwiseDesc, sizeof(D3D11_RASTERIZER_DESC));
cullClockwiseDesc.FillMode = D3D11_FILL_SOLID;
cullClockwiseDesc.CullMode = D3D11_CULL_BACK;
cullClockwiseDesc.FrontCounterClockwise = true;
cullClockwiseDesc.DepthClipEnable = true;HR(device->CreateRasterizerState(&cullClockwiseDesc, &CullClockwiseRS));

5. 程序运行结果截图

完整项目源代码请自行到DirectX11龙书官网下载。

DirectX11 平面镜像的实现相关推荐

  1. Linux内核镜像格式

      版权声明:*本文章参考了<Linux内核官方文档>.未经作者允许,严禁用于商业出版**,否则追究法律责任.网络转载请注明出处,这是对原创者的起码的尊重!!!* 1 Linux内核镜像格 ...

  2. 学习opengl入门

    当然,这些只是我7天来业余时间的学习,我觉得这个网址不错,大家如果也想学习opengl,并且具有一定的C语言C++基础,入门课程推荐大家去学习这个网址http://www.cnblogs.com/cr ...

  3. 容器编排技术 -- kubeadm 实现细节

    容器编排技术 -- kubeadm 实现细节 1 核心设计原则 2 常量和众所周知的值和路径 3 kubeadm init 工作流程内部设计 3.1 预检检查 3.2 生成必要的证书 3.3 为控制平 ...

  4. k8s——kubeadm工具使用

    一.kubeadm使用 Kubeadm 是一个工具,它提供了 kubeadm init 以及 kubeadm join 这两个命令作为快速创建kubernetes 集群的最佳实践. 1.kubeadm ...

  5. 【流程向】模型复原与Unity渲染

    项目简述 简单记录下学校里的一个项目,涉及到对/何家村遗宝/的模型复原,记录一下模型制作的全流程,同时涉及到Unity中一些优化画面的技术点.项目中渲染效果优先,没有怎么考虑性能. 流程:Blende ...

  6. angr入门之CLE

    什么是angr,我该如何使用它? angr 是一个支持多处理器架构的二进制分析工具包,它具有对二进制程序执行动态符号化执行(像MAyhem,KLEE等工具一样)以及多种静态分析的能力.如果想要学习如何 ...

  7. kubernetes 入门实践-搭建集群

    ㅤㅤㅤ ㅤㅤㅤ ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ(一个人的真正伟大之处就在于他能够认识到自己的渺小 -- 保罗) ㅤㅤㅤ ㅤㅤㅤ ㅤㅤㅤㅤㅤㅤㅤㅤㅤ 上一篇:kubernetes 入门实践-核心概念 下 ...

  8. kubernetes 入门实践

    ㅤㅤㅤ ㅤㅤㅤ ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ(一个人的真正伟大之处就在于他能够认识到自己的渺小 -- 保罗) ㅤㅤㅤ ㅤㅤㅤ ㅤㅤㅤㅤㅤㅤㅤㅤㅤ 学习完本教程能给你带来什么 k8s的基本概念和核心知 ...

  9. 体验 正式发布 的OSM v1.0.0 版本

    2021年10月份发布了OSM 1.0 RC[1],在过去的几个月里,OSM 的贡献者一直在努力为 v1.0.0 版本的发布做准备.2022年2月1日,OSM 团队正式发布 1.0.0 版本[2].O ...

  10. c语言编程齿轮模数选择,如何画齿轮,一看就懂

    马上注册,结交更多好友,享用更多功能! 您需要 登录 才可以下载或查看,没有帐号?立即注册 x 在网上找到的,昨天看了一下,就画出来了 齿轮的画法 一.预备知识: 画一个M=4,Z=10,厚为44的外 ...

最新文章

  1. php 前台生成多维数组 后台批量添加
  2. ActiveMQ — 单节点 — 安装与配置
  3. 如果你也想做实时数仓…
  4. php fpm 测试,zabbix4.2 监控PHP-FPM运行状态的数据
  5. libevent源码深度剖析十
  6. Apache并发处理模块
  7. 聚类算法学习指南(二)
  8. php sql语句过滤,php如何做sql过滤
  9. 大学计算机课程第六章答案,大学计算机网络课程第六章.ppt
  10. php与mysql对接_PHP与MySql建立连接
  11. 浅谈python可视化编程之tkinter(一)
  12. 抖音快手vbs表白代码大全(操作方法)
  13. win7计算机usb解除禁用,win7
  14. PIC24HJ单片机的UART
  15. JAVA高级架构师视频课程
  16. go每日新闻--2021-01-10
  17. 【CVE-2021-1675】Windows Print Spooler RCE
  18. The certificate used to sign “XXX“ has either expired or has been revoked.
  19. 5 款程序员必备的 Chrome 插件!
  20. 腾讯云服务器php设置,Windows 腾讯云服务器的 PHP 配置

热门文章

  1. uniapp 动态插槽 slot 兼容微信小程序 h5 APP
  2. gc overhead limit exceeded解决方案
  3. java jimi_Java开源工具Jimi处理图片大小及格式转换
  4. Echarts柱状图,实现不同系列,柱体之间的部分重叠效果
  5. 学员管理系统(面向对象版)
  6. [经典模型] 4. 图与网络模型及方法
  7. ros 控制xbox_从提示框:在Windows中控制Xbox控制器,在夏天保持计算机凉爽以及DIY图书扫描装置...
  8. 按照python后没有菜单栏 知乎_在线按汉字偏旁部首查字
  9. el-upload手动上传图片并限制图片数量、大小和格式
  10. CentOS6.9+ChinaDNS+Supervisor+DNSMasq+TCP/UDP协议特殊端口开放 部署实验