Ambient Occlusion

时间:2022-08-22 19:47:46

一般在光照模型中,ambient light的计算方法为:A = l * m,其中l表示表面接收到的来自光源的ambient light的总量,而m表示表面接收到ambient light后,反射和吸收的量。出于性能考虑,在计算光照时,我们是不考虑那些从场景中其他物体反弹过来的光的,因为通常我们认为这些光在场景中被发散和弹射许许多多次以至于最后从各个方向照射到物体上的量是相同的。所以ambient light所做的就是提亮物体,它没有任何真实的物理光照计算,所以它最终的渲染效果就是一个常量颜色:

Ambient Occlusion

而这里要介绍的Ambient Occlusion技术的作用就是改善ambient light的效果,使物体看起来饱满有层次感。

前面提到,在普通的ambient light计算中,我们认为所有的光线从各个角度照射到物体上时是等量的:

Ambient Occlusion

而在Ambient Occlusion技术中,我们将遮挡考虑进去,也就是说,表面从各个方向接受到的光的总量不再是等量的,而是取决于从表面的上半球体照射到表面的光线被遮挡了多少(这里为什么只考虑表面的上半球体呢,是因为从表面下半部分照射到表面的光是不会照亮表面的,所以不需要考虑):

Ambient Occlusion

那么在程序中我们该如何模拟这种遮挡呢?具体来说,就是我们从顶点随机生成围绕表面上半球体的射线,然后检测这些射线是否和网格相交:

Ambient Occlusion

根据上图,我们发射了7条射线,其中有5条是和网格相交的,那么对于这个顶点p,他的遮挡值occlusion = 5 / 7。所以,我们对于遮挡的定义如下:对于顶点p,如果我们发射了N条射线,其中有h条和网格相交,那么顶点p的遮挡值就是occlusion = h / N。但在计算时我们还有个需要注意的地方是需要定义一个距离distance,只有当顶点到射线和网格的交点的距离小于distance时,我们才认为顶点是被遮挡的,原因是如果距离太远,尽管射线和网格相交了,但是我们认为这个网格其实是遮挡不住顶点的。

好了,至此,我们已经了解了Ambient Occlusion的基本原理,可以开始动手实现了,基本的程序流程是这样的:

对于每个三角面,我们计算每个顶点的遮挡值,但是这个顶点可能被多个三角面共享,因此,我们的处理方式是加权平均,假设顶点v被2个三角面共享,对于三角面1,我们计算出来他的遮挡值是0.7,而对于三角面2,我们计算出他的遮挡值是0.5,那么他最终的遮挡值就是:(0.7 + 0.5) / 2 = 0.6。下面展示我写的计算代码片段,其中,对于每个三角面,我会发射32条射线用于做相交性检测:

         UINT uTriangleCount = vIndices.size() / ;
std::vector<UINT> vVertexSharedCount( uVertexCount ); // Used to count how many triangles contain the same vertex for ( UINT triangleIndex = ; triangleIndex < uTriangleCount; ++triangleIndex )
{
UINT index_0 = vIndices[triangleIndex * ];
UINT index_1 = vIndices[triangleIndex * + ];
UINT index_2 = vIndices[triangleIndex * + ]; XMVECTOR vertex_0 = XMLoadFloat3( &vVertices[index_0].v3Position );
XMVECTOR vertex_1 = XMLoadFloat3( &vVertices[index_1].v3Position );
XMVECTOR vertex_2 = XMLoadFloat3( &vVertices[index_2].v3Position ); // Calculate normal and centroid of this triangle
XMVECTOR edge_0 = vertex_1 - vertex_0;
XMVECTOR edge_1 = vertex_2 - vertex_0;
XMVECTOR normal = XMVector3Normalize( XMVector3Cross(edge_0, edge_1) ); XMVECTOR centroid = (vertex_0 + vertex_1 + vertex_2) / 3.0f;
centroid += 0.001f * normal; // Offset to avoid self intersection //
UINT UnoccludedCount = ;
static const UINT SAMPLE_RAY_COUNT = ;
for ( UINT index = ; index < SAMPLE_RAY_COUNT; ++index )
{
XMVECTOR vRandomDir = CUtils::RandHemisphereUnitVector3( normal ); if ( !g_pOctree->RayOctreeIntersect(centroid, vRandomDir) )
{
++UnoccludedCount;
}
} FLOAT fAmbientAccess = static_cast<FLOAT>(UnoccludedCount) / static_cast<FLOAT>(SAMPLE_RAY_COUNT); // Average with vertices that share this triangle
vVertexAmbientAccesses[index_0] += fAmbientAccess;
vVertexAmbientAccesses[index_1] += fAmbientAccess;
vVertexAmbientAccesses[index_2] += fAmbientAccess; ++vVertexSharedCount[index_0];
++vVertexSharedCount[index_1];
++vVertexSharedCount[index_2];
} for ( UINT vertexIndex = ; vertexIndex < uVertexCount; ++vertexIndex )
{
vVertexAmbientAccesses[vertexIndex] /= vVertexSharedCount[vertexIndex];
}

好了,至此我们已经讲解完Ambient Occlusion技术了,这里还要补充的是,Ambient Occlusion的计算开销其实是非常大的,在我写的Demo中,有32000多个顶点,60000多个三角面,对于每个三角面发射32条射线,在我使用了八叉树进行优化的情况下,仍然需要5分钟左右的时间才能计算完毕,因此,我们通常会事先计算完遮挡值,存在文件中,然后运行时直接读取而不再计算,所以这个技术通常只能用于静态网格模型,因为对于动态网格模型他不可能实时运算。

在我的Demo中,我将每个顶点的遮挡值存在一张纹理中,其中每个像素对应一个顶点的遮挡值:

Ambient Occlusion

Demo最终的效果如下:

Ambient Occlusion

Ambient Occlusion的更多相关文章

  1. GLSL实现Ambient Occlusion 【转】

    http://blog.csdn.net/a3070173/archive/2008/11/04/3221181.aspx 相信使用OpenGl或DirectX3D的朋友都知道到固定功能管线在光照处理 ...

  2. TSSAO Temporal Screen-Space Ambient Occlusion &lpar;Unity3d 5 示例实现&rpar;

    前提 环境光(ambient occlusion)是一种GI,其简化形式SSAO可以用“微量高效”来形容,消耗得很少,得到的效果很好.环 境光遮蔽(ambient occlusion)的本质是计算在一 ...

  3. &lbrack;帖子收集&rsqb;环境光遮蔽(Ambient Occlusion)

    环境光遮蔽,效果示例图 图片左边是一条龙的简单模型,呈现在一个均匀照明的环境中.尽管模型中有一些明暗不同的区域,但大部分光照都是均匀的.虽然模型有着相当复杂的几何形状,但看上去比较光滑平坦,没有明显的 ...

  4. Dynamic Ambient Occlusion and Indirect Lighting

    This sample was presented on the Nvida witesite, which detail a new idea to calculate the ambient oc ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十一章:环境光遮蔽(AMBIENT OCCLUSION)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十一章:环境光遮蔽(AMBIENT OCCLUSION) 学习目标 ...

  6. &lbrack;译&rsqb;基于GPU的体渲染高级技术之raycasting算法

    [译]基于GPU的体渲染高级技术之raycasting算法 PS:我决定翻译一下<Advanced Illumination Techniques for GPU-Based Volume Ra ...

  7. Gamma校正与线性空间

    基础知识部分 为了方便理解,首先会对(Luminance)的相关概念做一个简单介绍.如果已经了解就跳到后面吧. 我们用Radiant energy(辐射能量)来描述光照的能量,单位是焦耳(J),因为光 ...

  8. &lbrack;转&rsqb;显卡帝揭秘3D游戏画质特效

    显卡帝揭秘3D游戏画质特效 近几年来,大量采用最新技术制作的大型3D游戏让大部分玩家都享受到了前所未有的游戏画质体验,同时在显卡硬件方面的技术革新也日新月异.对于经常玩游戏的玩家来说,可能对游戏画质提 ...

  9. Computer Graphics Research Software

    Computer Graphics Research Software Helping you avoid re-inventing the wheel since 2009! Last update ...

随机推荐

  1. JS定时器

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. P1514 引水入城

    概述 首先,这是一道好题,这道题既考查了图论的dfs知识,又考察了区间贪心问题中很典型的区间覆盖问题,着实是一道好题. 大概思路说明 我们观察到,只有第一行可以放水库,而第一行在哪里放水库的结果就是直 ...

  3. MFC窗口和控件大小等比例变化

    第一步:OnInitDialog里保存对话框及其所有子窗体的Rect区域 CRect rect; GetWindowRect(&rect); listRect.AddTail(rect);// ...

  4. go语言基础知识笔记(二)之数组和切片

    数组和切片知识用的也是比较多,的给我们工作带来很大的便利 (一) 数组 定义:在golang中数组的长度是不可变,数组存放要求是同一种数据类型 //golang中数组定义的四种方法1.先声明,后赋值 ...

  5. FileInputStream文件字节输入流程序

    第一种:.read() 一次读一个字节,返回值类型是int,方法读取硬盘访问次数太频繁.缺点:效率低,伤硬盘 import java.io.FileInputStream; import java.i ...

  6. DotNetBar的一个MDIView不正常显示的问题

    现象,使用tabStrip MDIView后,子窗体会被遮挡一部分,两种解决办法 1.tabStrip的 MdiAutoHide=False 2.tabStrip 设置MultilineWithNav ...

  7. &lbrack;Mysql&rsqb;——通过例子理解事务的4种隔离级别

    SQL标准定义了4种隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的. 低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销. 首先,我们使用 test 数据库, ...

  8. CentOS 7源码安装zabbix

    一.Zabbix简介 zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案.zabbix能监视各种网络参数,保证服务器系统的安全运营:并提供灵活的通知机制以让系统 ...

  9. Windows10下python3&period;5的sklearn库安装

    具体安装方法参考https://blog.csdn.net/HYDMonster/article/details/79766086 但是注意的是,http://www.lfd.uci.edu/~goh ...

  10. Revit API创建标高,单位转换

    一业内朋友让我写个快速创建标高的插件. ;             ; i <= iNum; i++)             {                 Level level = d ...