1.光照与材质的交互

局部光照模型(local illumination model):每个物体的光照皆独立于其他物体,我们也就可以在处理光照的过程中仅考虑光源直接发出的光,而忽略来自场景中其他物体所反弹来的

全局光照模型(global illumination model):除了要考虑由光源直接发出的光,还要顾及场景中其他物体所反弹来的间接光照。-- 全局光照模型的开销往往是实时游戏所负担不起的

2.法向量

平面法线(face normal)/曲面法线(surface normal)

对于光照计算来说,我们需要通过三角形网格曲面上每一点处的曲面法线来确定光线照到对应点上的角度。为了求出曲面法线,我们仅先指定位于网格顶点处的曲面法线(所以也称作顶点法线),接下来,为取得三角形网格曲面上每个点处的近似曲面法线,在三角形进行光栅化的过程中对这些法线进行插值计算

我们把对每个像素逐一进行法线插值并执行光照计算的方法称为逐像素光照/phong光照模型

逐顶点光照模型对每个顶点进行光照计算,并对三角形中的像素进行插值,这种开销低但是精度也低 -- 将运算作业从像素着色器转移至顶点着色器是一种常见的优化手段,在以质量为重但又允许结果存在少许偏差的情况下,这种优化方法极具吸引力

计算法向量:

XMVECTOR ComputeNormal(FXMVECTOR p0, FXMVECTOR p1, FXMVECTOR p2)
{XMVECTOR u = p1 - p0;XMVECTOR v = p2 - p0;return XMVector3Normalize(XMVector3Cross(u, v));
}

对于可微的光滑曲面而言,我们可以利用微积分方面的知识来求出曲面点处的法线。但问题在于,三角形网格运用一种被称为求顶点法线平均值的计算方法。

我们可以遍历所有三角形,并累加所涉及顶点的法线,最后归一化处理(简单求平均值)

切向量(tangent vector):正交于法向量

考虑这样的问题,之前,切向量u正交于法向量n,但对此应用一个非等比缩放变换A后,uA不再与nA继续保持正交性:

  =>  

证明过程:

 如果A是正交矩阵,那么B=A

当我们需要对经过非等比变换或剪切变换后的法向量进行变换时,则可使用逆转置矩阵

// MathHelper.h:求逆转置矩阵
static XMMATRIX InverseTranspose(CMMATRIX M)
{XMMATRIX A = M;A.r[3] = XMVectorSet(0.f, 0.f, 0.f, 1.f);XMVECTOR det = XMMatrixDeterminant(A);return XMMatrixTranspose(XMMatrixInverse(&det, A));
}

在这里我们将平移项清零,而只允许点类才有平移变换。若我们只把w设置为0,看似可以防止向量因平移操作而受影响。但问题在于,

3.参与光照计算的一些关键向量

光向量(light vector) L:单位向量,方向与在点P处光线的入射方向相反

反射向量r:入射光向量L关于表面法线n的镜像

观察向量(view vector) v E点是人眼(观察点) P是表面点

  反射向量公式的推导证明:

因为S是L在n方向的投影,根据投影公式,得到:

所以

-- 照片中没有使用光向量而是用入射向量I

4.朗伯余弦定理

我们可以将光看作是光子的集合,在空间中按特定的方向传播。每个光子都载有光能量。光源每秒发出的光能量称为辐射通量。而单位面积上的辐射通量密度(辐(射)照度)是一种很重要的概念。

(a)辐照度: (b)辐照度:

换句话说,面积A2内的辐照度就相当于将受垂直方向光照的面积A1内的辐照度按比例进行缩放。注意,这里的θ角度是光向量与法线的夹角,越垂直于平面,辐照度越强。 --  这就是朗伯余弦定理(Lambert's Cosine Law)

考虑到光线照射到表面另一侧的情况(点积为负),我们使用max函数来钳制缩放因子的取值范围:

5.漫反射光照 -- diffuse

当光线照射到物体表面上的某一点时,这些光一部分会进入物体内部,并与表面附近的物质相互作用。这些光会在物体内部四处反弹,其中一部分会被吸收,而余下部分则会向各个方向散射回表面 -- 这就是所谓的漫反射

在所用的这种光照与材质交互的近似模型之中,我们规定光线会在表面的所有方向上均匀散射 -- 因此,无需考虑观察点的具体位置,因为表面上的颜色在任何观察点上看来都是相同的

我们将漫反射的计算分为两个部分在第一个部分中,我们要指定光照颜色以及漫反射反照率(diffuse albedo)颜色。漫反射反照率表示的是根据表面的漫反射率而被反射的入射光亮(根据能量守恒定律,入射光不是被反射回表面就是被材质所吸收了)。要对它们进行处理就需用分量式颜色乘法。

例如,假设表面上一点会反射50%的入射红光、100%的绿光以及75%的蓝光,如果这时有一束强度为80%的白光袭来,那么此入射光的量值就可以表示为,又因为漫反射反照率为,所以此点的反射光亮为:

在第二个部分中,我们需要将朗伯余弦定理考虑在内。

L:光向量 n:表面法线 :入射光亮 :漫反射反照率

6.环境光照 -- ambient

其实在现实生活中我们看到的大多是间接光,但我们的光照模型中并没有考虑从其他物体反射来的间接光照。为了处理这种间接光照,我们给光照方程引进一个环境光项(ambient light)

所有的环境光都是以统一的亮度将物体稍稍照亮 -- 而没有按真实的物理效果进行计算

7.镜面光照

菲涅耳效应 Fresnel effect当光线到达两种不同折射率介质之间的界面时,一部分光会被反射,而剩下的光则发生折射

折射率:介质的一种物理性质,即光在真空中传播的速度与光在给定介质内的传播速度之比

我们之前用漫反射光照来模拟漫反射的过程,而第二种光的反射过程称为镜面反射(specular reflection),把被反射的光称为镜面(反射)光(specular light) -- 镜面光只发生在特定的角度

摄入观察者眼中的反射光实则为镜面反射和漫反射的组合效果

如果折射光沿折射向量从介质的另一侧射出,并进入观察者的眼中,则该物体看起来就像是透明的。在实时的图像处理中,一般用alpha混合技术或后期处理特效来模拟透明对象的折射过程。

1️⃣菲涅耳效应:

菲涅耳方程以数学方法描述了入射光线被反射的百分比,即

根据能量守恒定律,如果是反射光量,则为折射光量。的值是一个RGB向量,因为光的颜色反映了反射光亮。

反射的光量既依赖于介质(某些材质反射率相对更大),也与法向量n与光向量L之间的夹角有关。由于光照过程的复杂性,我们一般不会将完整的菲涅耳方程用于实时渲染,而是采用石里克近似(Schlick approximation)法来加以代替

可以看出,反射光量随着θi从0°到90°递增

我们可以将菲涅耳效应简洁地概括为:反射光亮取决于材质()以及法线与光向量之间的夹角

金属会吸收透射光,这意味着它们不具有本体反射率(体漫反射)。虽然如此,但金属并不会看上去表现为纯黑色,因为它们的较高,也就是说,就算是在接近于0°这样的极小入射角度上,它们也能反射可观的镜面光亮。

2️⃣表面粗糙度:

真实世界中的反射物体往往不是理想镜面,我们认为理想镜面粗糙度为0,它的微观表面法线都与宏观表面法线的方向相同。随着粗糙度的增加,微观表面法线的方向开始纷纷偏离宏观表面法线,由此反射光逐渐扩展为一个镜面瓣

为了用数学方法对粗糙度进行建模,我们采用了微平面模型。在此模型中,我们将微观表面模拟为由多个既微小又平滑的微平面所构成的集合,而微观表面法线正式这些微平面上的法线。

我们需要了解由L向v反射的所有微平面片段的分布情况,换言之,即法线h=normalize(L+v)这种微平面片段在所有微平面中所占的比例 -- 发生由L到v反射过程的微平面越多,则观察者在此角度上看到的镜面光越明亮

半程向量(halfway vector(中间向量)):由于向量h位列于向量L向量v[向量v是人眼位置与观察点p之间的向量,不是L关于法线对称的向量]间的中间位置

h = normalize(L+E)

还要引入夹角:半程向量h与宏观表面法线n之间的夹角

上图中,n是宏观表面法线,h是半程向量,我们找出以半程向量h为法线的微平面

我们定义归一化分布函数,用来表示微观表面法线h与宏观表面法线n之间夹角为的微表面的分布情况。从直观上讲,我们希望函数=0°时取得最大值。也就是说,我们希望微平面法线都平行于宏观表面法线,并盼望随着θh的增加,这些以向量h为法线的微平面片段逐渐减少。用来模拟以上讨论中期望模型ρ(θh)的一种较流行的可控函数为:

根据上图可以看到,m值越小,表面越粗糙

我们可以讲ρ(θh)与某一种归一化因子进行组合,从而得到基于粗糙度来模拟镜像反射光亮的新函数:

加上该归一化因子以使光能守恒。该归一化因子能够支配图中曲线的高度,因此随着变量m的变化使镜面瓣变宽或变窄,令光能达到守恒。比如对于较小的m值来说,表面粗糙,镜面瓣变宽,因此能量被广泛传播出去了,镜面高光因此会变暗,对应的图中曲线高度会下降。

⭐总结:菲涅耳反射+表面粗糙度:<高光反射>

注意:这里中的角度是微平面法线(半程向量)h与v或者L之间的夹角 -- 之前是宏观表面法线n

8.光照模型 -- Blinn-Phong

表面反射的光亮相当于环境反射光()、漫反射光()以及镜面反射光()的光亮总和

环境光:模拟经表面反射的间接光亮

漫反射光:对进入介质内部,又经过表面下吸收而最终散射出表面的光进行模拟。由于对表面下的散射光建模比较困难,我们便假设在表面下与介质相互作用后的光从进入表面处返回,并向各个方向均匀散射。

镜面光:模拟菲涅耳效应与表面粗糙度共同作用的表面反射光

⭐所以在shader中实现的光照方程为:

其中:

最后一项中(n·h)不能为负,因为其代表微表面的分布情况(或所占"比例")

:漫反射反照率 diffuse albedo

:FresnelR0

9.材质的实现

我们编写的材质结构体定义在d3dUtil.h文件中:

struct Material
{std::string Name;int MatCBIndex = -1; // 本材质的常量缓冲区索引int DiffuseSrvHeapIndex = -1; // 漫反射纹理在SRV堆中的索引int NumFramesDirty = gNumFrameResources;// 用于着色的材质常量缓冲区数据DirectX::XMFLOAT4 DiffuseAlbedo = {1.f, 1.f, 1.f, 1.f};DirectX::XMFLOAT3 FresnelR0 = {0.01f, 0.01f, 0.01f};float Roughness = 0.25f;DirectX::XMFLOAT4X4 MatTransform = MathHelper::Identity4x4();
};

为了模拟真实世界的材质,需要设置DiffuseAlbedo(漫反射反照率)FresnelR0(材质属性R_F(0°))对于金属而言,金属材质不会发生漫反射(DiffuseAlbedo=0),也就只有高光项。但我们往往不会按照100%物理学上的光学理论来建模,而作适当的调整。一种富有艺术性的更佳策略是为金属的DiffuseAlbedo设定一个非0的较小值,为艺术性留下一些空间,达到更佳的视觉效果。

Roughness粗糙度指定在归一化范围[0,1]内,粗糙度值越大越粗糙,我们在着色器代码中,利用粗糙度来推导所用的指数m。根据我们对粗糙度的定义,可以看出表面的光泽度(shiness)是与粗糙度相反的属性,shiness = 1 - roughness ∈[0,1]

那么我们该以什么粒度指定材质?解决方案之一是我们对每个顶点为基准来指定材质的具体数值,在光栅化阶段对材质属性进行插值处理。

创建材质,并列于一个表中(放在系统内存中):

void LitWavesApp::BuildMaterials()
{auto grass = std::make_unique<Material>();grass->Name = "grass";grass->MatCBIndex = 0;grass->DiffuseAlbedo = XMFLOAT4(0.2f, 0.6f, 0.2f, 1.0f);grass->FresnelR0 = XMFLOAT3(0.01f, 0.01f, 0.01f);grass->Roughness = 0.125f;// This is not a good water material definition, but we do not have all the rendering// tools we need (transparency, environment reflection), so we fake it for now.auto water = std::make_unique<Material>();water->Name = "water";water->MatCBIndex = 1;water->DiffuseAlbedo = XMFLOAT4(0.0f, 0.2f, 0.6f, 1.0f);water->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);water->Roughness = 0.0f;mMaterials["grass"] = std::move(grass);mMaterials["water"] = std::move(water);
}

为了让GPU能够访问到材质,放置到常量缓冲区中,常量缓冲区结构以及定义在帧资源中:

// 提前定义在d3dUtil.h中:
struct MaterialConstants
{DirectX::XMFLOAT4 DiffuseAlbedo = { 1.0f, 1.0f, 1.0f, 1.0f };DirectX::XMFLOAT3 FresnelR0 = { 0.01f, 0.01f, 0.01f };float Roughness = 0.25f;DirectX::XMFLOAT4X4 MatTransform = MathHelper::Identity4x4();
};// 更新我们定义的帧资源结构
struct FrameResource
{
public:FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount, UINT materialCount, UINT waveVertCount);FrameResource(const FrameResource& rhs) = delete;FrameResource& operator=(const FrameResource& rhs) = delete;~FrameResource();Microsoft::WRL::ComPtr<ID3D12CommandAllocator> CmdListAlloc;std::unique_ptr<UploadBuffer<PassConstants>> PassCB = nullptr;std::unique_ptr<UploadBuffer<MaterialConstants>> MaterialCB = nullptr; // 材质的常量缓冲区std::unique_ptr<UploadBuffer<ObjectConstants>> ObjectCB = nullptr;std::unique_ptr<UploadBuffer<Vertex>> WavesVB = nullptr;UINT64 Fence = 0;
};

更新材质的常量缓冲区:

void LitWavesApp::UpdateMaterialCBs(const GameTimer& gt)
{auto currMaterialCB = mCurrFrameResource->MaterialCB.get();for(auto& e : mMaterials){Material* mat = e.second.get();if(mat->NumFramesDirty > 0){XMMATRIX matTransform = XMLoadFloat4x4(&mat->MatTransform);MaterialConstants matConstants;matConstants.DiffuseAlbedo = mat->DiffuseAlbedo;matConstants.FresnelR0 = mat->FresnelR0;matConstants.Roughness = mat->Roughness;currMaterialCB->CopyData(mat->MatCBIndex, matConstants);mat->NumFramesDirty--;}}
}

设置根参数省略,材质存放在材质缓冲区中,需要使用哪种材质只需要获取其在材质缓冲区的索引,并计算出其地址即可设置根描述符:

void LitWavesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems)
{UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));UINT matCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialConstants));auto objectCB = mCurrFrameResource->ObjectCB->Resource();auto matCB = mCurrFrameResource->MaterialCB->Resource();for(size_t i = 0; i < ritems.size(); ++i){auto ri = ritems[i];cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());cmdList->IASetPrimitiveTopology(ri->PrimitiveType);D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress() + ri->ObjCBIndex*objCBByteSize;D3D12_GPU_VIRTUAL_ADDRESS matCBAddress = matCB->GetGPUVirtualAddress() + ri->Mat->MatCBIndex*matCBByteSize; // 计算材质在材质缓冲区中的地址cmdList->SetGraphicsRootConstantBufferView(0, objCBAddress);cmdList->SetGraphicsRootConstantBufferView(1, matCBAddress); // 设置材质缓冲区CBVcmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);}
}

10.三种光源 -- 平行光源、点光源和聚光灯光源

①平行光源:parallel light 或叫 方向光源 directional light -- 太阳光☀

平行光是一种距离目标物体极远的光源,我们把这种光源发出的所有入射光看作是平行的光线,且因为距离十分遥远,忽略距离所带来的影响,仅指定照射到场景中光线的光强(light intensity)

用向量来定义平行光源,借此指定光线传播的方向

②点光源:point light -- 灯泡

dx12 龙书第八章学习笔记 -- 光照相关推荐

  1. dx12 龙书第二十章学习笔记 -- 阴影贴图

    对于龙书这本入门级别的书籍来说,我们仅关注于基本的阴影贴图算法.而像级联阴影贴图(cascading shadow map)[Engel06]这种效果更佳却也更为复杂的阴影技术,实则都是由这基本的阴影 ...

  2. dx12 龙书第九章学习笔记 -- 纹理贴图

    1.纹理与资源的回顾 我们其实很早就接触过纹理了,之前的深度缓冲区与后台缓冲区,它们都是通过ID3D12Resource接口表示,并以D3D12_RESOURCE_DESC::Dimension成员中 ...

  3. dx12 龙书附录C学习笔记 -- 解析几何学选讲

    本附录中,我们将以向量与点为基石来构建出更为复杂的几何图形. 1.射线.直线以及线段 一条直线可以用线上一点以及与此线平行的向量u来表示,所以,此直线的向量直线方程(vector line equat ...

  4. DirectX 9.0c游戏开发手记之“龙书”第二版学习笔记之1: 开场白

    在开场白之前的说明: 这是"DirectX 9.0c游戏开发手记"的第一部分,叫做"'龙书'第二版学习笔记",讲的是我做"龙书"第二版(原名 ...

  5. DirectX12龙书--第五章笔记

    需要龙书电子版的可联系我,部分图片太大,csdn导入不了,可访问我的github图床github或者在书里面查看. DirectX12龙书第五章笔记 第五章 5.1 3D视觉即错觉? 5.2 模型的表 ...

  6. OpenGL蓝宝书第九章学习笔记:片段着色器和帧缓存

    前言 本篇在讲什么 OpenGL蓝宝书第九章学习笔记之片段着色器和帧缓存 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C++语法有简单认知 对OpenGL有简单认知 最好是有OpenGL ...

  7. dx12 龙书第三章学习笔记 -- 变换

    1.线性变换:   函数的输入和输出都是3D向量,我们称为线性变换 矩阵表示法: ⭐所以已知一个线性变换,只要将i,j,z也就是标准基向量代入线性变换,就能构造一个变换矩阵 A:线性变换的矩阵表示法 ...

  8. 鸟哥的Linux私房菜(基础班)第八章学习笔记

    第八章 Linux磁盘与文件系统管理 学习笔记 认识EXT2文件系统 硬盘物理组成 分区 文件系统 Linux的EXT2文件系统(inode) EXT2/EXT3文件的访问与日志文件系统的功能 Lin ...

  9. python基础知识笔记简书_Python基础学习笔记

    Python貌似有点火热,上手还是比较简单的,自己找了个教程也偷偷的学习一下,扒了一下网上的图片和数据,感觉并不是很难呀(不过之前换电脑,代码丢了,有点可惜,不过网上教程一抓一大把,随便看看也能扒一些 ...

最新文章

  1. html类名的作用,bootstrap类名及作用(部分)
  2. [ASP.NET MVC] 利用自定义的AuthenticationFilter实现Basic认证
  3. How can ifm help the SME WELL?
  4. 【JVM】GC Roots 根可达
  5. linux 中select()函数的使用
  6. python实现ncm转mp3_网易云音乐ncm格式分析以及ncm与mp3格式转换
  7. JDBC - 宋红康 - 核心技术
  8. 国外android大神博客,Android手机浏览器(国外篇)横向对比评测
  9. c语言程序中a表示什么区别,C语言中if(!a)表示什么意思?
  10. java代码如何整合_Java如何合并两个PPT文档?
  11. 初级算法题->有效的数独--弄清哈希表的本质
  12. 都2022年了,PPT这些酷炫操作我不允许你不知道
  13. SpringBoot:Whitelabel Error Page 404
  14. 再牛的键盘也敲不出我的孤单
  15. mysql char 50_MySQL中数据类型varchar(50)和char(50)是完全相同的。
  16. 今日金融词汇---基本面分析
  17. 深入分析中小型千兆网吧解决方案(转)
  18. ClickHouse插入频繁报错优化
  19. 动态壁纸安卓_梦象动态壁纸下载
  20. matlab图像拼接融合(四种方法)

热门文章

  1. LeetCode SQL 刷题
  2. 书论23 欧阳询《用笔论》
  3. BigDecimal 计算工具类
  4. 深红色晶体meso-5,10,15,20-四(对氨基苯基)钴卟啉(Co-TAPP);meso-5,10,15,20-四(对氨基苯基)钴卟啉-NO配合物(TAPP-Co-NO)齐岳供应
  5. Linux shuf, 数据采样
  6. tiktok中文叫什么?能做什么?
  7. Oracle查看表空间大小以及修改表空间大小
  8. 支付宝一年新增四亿行代码!他们的支付模块是如何设计开发的?
  9. Predicate Information
  10. 创建时间Time类,并实例化对象访问测试