原文:DirectX11 With Windows SDK--07 添加光照与常用几何模型、光栅化状态

前言

对于3D游戏来说,合理的光照可以让游戏显得更加真实。接下来会介绍光照的各种分量,以及常见的光照模型。除此之外,该项目还用到了多个常量缓冲区,因此还会提及HLSL的常量缓冲区打包规则以及如何设置多个常量缓冲区。

DirectX11 With Windows SDK完整目录

Github项目源码

除此之外你还需要了解下面内容:

章节内容
深入理解HLSL常量缓冲区打包规则

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

颜色向量

一个4D的颜色向量,通常情况下会表示为(red, green, blue, alpha),每个分量的取值范围为[0.0f, 1.0f]。对于红绿蓝分量,用0.0f表示该没有该分量的颜色,用1.0f表示该分量的颜色达到饱和;对于alpha分量,用0.0f表示该分量完全透明,用1.0f表示该分量完全不透明。

对于8位色来说,每种分量的颜色亮度可以表达出256种,但使用浮点数会大大浪费存储空间。在内存要求苛刻的情况下,我们可以使用32位的数据类型,其中rgba各占8位,若需要映射到浮点数向量,则对应关系为f(x) = x / 255.0f,其中x为整数存储法,表示范围为[0,255]f(x)为浮点存储法,表示范围为[0.0f, 1.0f]

在头文件DirectXColors.h中,我们可以看到定义了一些4D的颜色向量,位于名称空间DirectX::Colors。在这里贴出来供大家参考:

// Standard colors (Red/Green/Blue/Alpha)
XMGLOBALCONST XMVECTORF32 AliceBlue = { { { 0.941176534f, 0.972549081f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 AntiqueWhite = { { { 0.980392218f, 0.921568692f, 0.843137324f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Aqua = { { { 0.000000000f, 1.000000000f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Aquamarine = { { { 0.498039246f, 1.000000000f, 0.831372619f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Azure = { { { 0.941176534f, 1.000000000f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Beige = { { { 0.960784376f, 0.960784376f, 0.862745166f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Bisque = { { { 1.000000000f, 0.894117713f, 0.768627524f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Black = { { { 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 BlanchedAlmond = { { { 1.000000000f, 0.921568692f, 0.803921640f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Blue = { { { 0.000000000f, 0.000000000f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 BlueViolet = { { { 0.541176498f, 0.168627456f, 0.886274576f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Brown = { { { 0.647058845f, 0.164705887f, 0.164705887f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 BurlyWood = { { { 0.870588303f, 0.721568644f, 0.529411793f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 CadetBlue = { { { 0.372549027f, 0.619607866f, 0.627451003f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Chartreuse = { { { 0.498039246f, 1.000000000f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Chocolate = { { { 0.823529482f, 0.411764741f, 0.117647067f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Coral = { { { 1.000000000f, 0.498039246f, 0.313725501f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 CornflowerBlue = { { { 0.392156899f, 0.584313750f, 0.929411829f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Cornsilk = { { { 1.000000000f, 0.972549081f, 0.862745166f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Crimson = { { { 0.862745166f, 0.078431375f, 0.235294133f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Cyan = { { { 0.000000000f, 1.000000000f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkBlue = { { { 0.000000000f, 0.000000000f, 0.545098066f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkCyan = { { { 0.000000000f, 0.545098066f, 0.545098066f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkGoldenrod = { { { 0.721568644f, 0.525490224f, 0.043137256f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkGray = { { { 0.662745118f, 0.662745118f, 0.662745118f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkGreen = { { { 0.000000000f, 0.392156899f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkKhaki = { { { 0.741176486f, 0.717647076f, 0.419607878f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkMagenta = { { { 0.545098066f, 0.000000000f, 0.545098066f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkOliveGreen = { { { 0.333333343f, 0.419607878f, 0.184313729f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkOrange = { { { 1.000000000f, 0.549019635f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkOrchid = { { { 0.600000024f, 0.196078449f, 0.800000072f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkRed = { { { 0.545098066f, 0.000000000f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkSalmon = { { { 0.913725555f, 0.588235319f, 0.478431404f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkSeaGreen = { { { 0.560784340f, 0.737254918f, 0.545098066f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkSlateBlue = { { { 0.282352954f, 0.239215702f, 0.545098066f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkSlateGray = { { { 0.184313729f, 0.309803933f, 0.309803933f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkTurquoise = { { { 0.000000000f, 0.807843208f, 0.819607913f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DarkViolet = { { { 0.580392182f, 0.000000000f, 0.827451050f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DeepPink = { { { 1.000000000f, 0.078431375f, 0.576470613f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DeepSkyBlue = { { { 0.000000000f, 0.749019623f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DimGray = { { { 0.411764741f, 0.411764741f, 0.411764741f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 DodgerBlue = { { { 0.117647067f, 0.564705908f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Firebrick = { { { 0.698039234f, 0.133333340f, 0.133333340f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 FloralWhite = { { { 1.000000000f, 0.980392218f, 0.941176534f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 ForestGreen = { { { 0.133333340f, 0.545098066f, 0.133333340f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Fuchsia = { { { 1.000000000f, 0.000000000f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Gainsboro = { { { 0.862745166f, 0.862745166f, 0.862745166f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 GhostWhite = { { { 0.972549081f, 0.972549081f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Gold = { { { 1.000000000f, 0.843137324f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Goldenrod = { { { 0.854902029f, 0.647058845f, 0.125490203f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Gray = { { { 0.501960814f, 0.501960814f, 0.501960814f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Green = { { { 0.000000000f, 0.501960814f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 GreenYellow = { { { 0.678431392f, 1.000000000f, 0.184313729f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Honeydew = { { { 0.941176534f, 1.000000000f, 0.941176534f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 HotPink = { { { 1.000000000f, 0.411764741f, 0.705882370f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 IndianRed = { { { 0.803921640f, 0.360784322f, 0.360784322f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Indigo = { { { 0.294117659f, 0.000000000f, 0.509803951f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Ivory = { { { 1.000000000f, 1.000000000f, 0.941176534f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Khaki = { { { 0.941176534f, 0.901960850f, 0.549019635f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Lavender = { { { 0.901960850f, 0.901960850f, 0.980392218f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LavenderBlush = { { { 1.000000000f, 0.941176534f, 0.960784376f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LawnGreen = { { { 0.486274540f, 0.988235354f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LemonChiffon = { { { 1.000000000f, 0.980392218f, 0.803921640f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightBlue = { { { 0.678431392f, 0.847058892f, 0.901960850f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightCoral = { { { 0.941176534f, 0.501960814f, 0.501960814f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightCyan = { { { 0.878431439f, 1.000000000f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightGoldenrodYellow = { { { 0.980392218f, 0.980392218f, 0.823529482f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightGreen = { { { 0.564705908f, 0.933333397f, 0.564705908f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightGray = { { { 0.827451050f, 0.827451050f, 0.827451050f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightPink = { { { 1.000000000f, 0.713725507f, 0.756862819f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightSalmon = { { { 1.000000000f, 0.627451003f, 0.478431404f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightSeaGreen = { { { 0.125490203f, 0.698039234f, 0.666666687f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightSkyBlue = { { { 0.529411793f, 0.807843208f, 0.980392218f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightSlateGray = { { { 0.466666698f, 0.533333361f, 0.600000024f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightSteelBlue = { { { 0.690196097f, 0.768627524f, 0.870588303f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LightYellow = { { { 1.000000000f, 1.000000000f, 0.878431439f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Lime = { { { 0.000000000f, 1.000000000f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 LimeGreen = { { { 0.196078449f, 0.803921640f, 0.196078449f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Linen = { { { 0.980392218f, 0.941176534f, 0.901960850f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Magenta = { { { 1.000000000f, 0.000000000f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Maroon = { { { 0.501960814f, 0.000000000f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MediumAquamarine = { { { 0.400000036f, 0.803921640f, 0.666666687f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MediumBlue = { { { 0.000000000f, 0.000000000f, 0.803921640f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MediumOrchid = { { { 0.729411781f, 0.333333343f, 0.827451050f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MediumPurple = { { { 0.576470613f, 0.439215720f, 0.858823597f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MediumSeaGreen = { { { 0.235294133f, 0.701960802f, 0.443137288f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MediumSlateBlue = { { { 0.482352972f, 0.407843173f, 0.933333397f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MediumSpringGreen = { { { 0.000000000f, 0.980392218f, 0.603921592f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MediumTurquoise = { { { 0.282352954f, 0.819607913f, 0.800000072f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MediumVioletRed = { { { 0.780392230f, 0.082352944f, 0.521568656f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MidnightBlue = { { { 0.098039225f, 0.098039225f, 0.439215720f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MintCream = { { { 0.960784376f, 1.000000000f, 0.980392218f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 MistyRose = { { { 1.000000000f, 0.894117713f, 0.882353008f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Moccasin = { { { 1.000000000f, 0.894117713f, 0.709803939f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 NavajoWhite = { { { 1.000000000f, 0.870588303f, 0.678431392f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Navy = { { { 0.000000000f, 0.000000000f, 0.501960814f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 OldLace = { { { 0.992156923f, 0.960784376f, 0.901960850f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Olive = { { { 0.501960814f, 0.501960814f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 OliveDrab = { { { 0.419607878f, 0.556862772f, 0.137254909f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Orange = { { { 1.000000000f, 0.647058845f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 OrangeRed = { { { 1.000000000f, 0.270588249f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Orchid = { { { 0.854902029f, 0.439215720f, 0.839215755f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 PaleGoldenrod = { { { 0.933333397f, 0.909803987f, 0.666666687f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 PaleGreen = { { { 0.596078455f, 0.984313786f, 0.596078455f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 PaleTurquoise = { { { 0.686274529f, 0.933333397f, 0.933333397f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 PaleVioletRed = { { { 0.858823597f, 0.439215720f, 0.576470613f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 PapayaWhip = { { { 1.000000000f, 0.937254965f, 0.835294187f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 PeachPuff = { { { 1.000000000f, 0.854902029f, 0.725490212f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Peru = { { { 0.803921640f, 0.521568656f, 0.247058839f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Pink = { { { 1.000000000f, 0.752941251f, 0.796078503f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Plum = { { { 0.866666734f, 0.627451003f, 0.866666734f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 PowderBlue = { { { 0.690196097f, 0.878431439f, 0.901960850f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Purple = { { { 0.501960814f, 0.000000000f, 0.501960814f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Red = { { { 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 RosyBrown = { { { 0.737254918f, 0.560784340f, 0.560784340f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 RoyalBlue = { { { 0.254901975f, 0.411764741f, 0.882353008f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 SaddleBrown = { { { 0.545098066f, 0.270588249f, 0.074509807f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Salmon = { { { 0.980392218f, 0.501960814f, 0.447058856f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 SandyBrown = { { { 0.956862807f, 0.643137276f, 0.376470625f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 SeaGreen = { { { 0.180392161f, 0.545098066f, 0.341176480f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 SeaShell = { { { 1.000000000f, 0.960784376f, 0.933333397f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Sienna = { { { 0.627451003f, 0.321568638f, 0.176470593f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Silver = { { { 0.752941251f, 0.752941251f, 0.752941251f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 SkyBlue = { { { 0.529411793f, 0.807843208f, 0.921568692f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 SlateBlue = { { { 0.415686309f, 0.352941185f, 0.803921640f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 SlateGray = { { { 0.439215720f, 0.501960814f, 0.564705908f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Snow = { { { 1.000000000f, 0.980392218f, 0.980392218f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 SpringGreen = { { { 0.000000000f, 1.000000000f, 0.498039246f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 SteelBlue = { { { 0.274509817f, 0.509803951f, 0.705882370f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Tan = { { { 0.823529482f, 0.705882370f, 0.549019635f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Teal = { { { 0.000000000f, 0.501960814f, 0.501960814f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Thistle = { { { 0.847058892f, 0.749019623f, 0.847058892f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Tomato = { { { 1.000000000f, 0.388235331f, 0.278431386f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Transparent = { { { 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f } } };
XMGLOBALCONST XMVECTORF32 Turquoise = { { { 0.250980407f, 0.878431439f, 0.815686345f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Violet = { { { 0.933333397f, 0.509803951f, 0.933333397f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Wheat = { { { 0.960784376f, 0.870588303f, 0.701960802f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 White = { { { 1.000000000f, 1.000000000f, 1.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 WhiteSmoke = { { { 0.960784376f, 0.960784376f, 0.960784376f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 Yellow = { { { 1.000000000f, 1.000000000f, 0.000000000f, 1.000000000f } } };
XMGLOBALCONST XMVECTORF32 YellowGreen = { { { 0.603921592f, 0.803921640f, 0.196078449f, 1.000000000f } } };

如果颜色相关的运算在C++代码层进行的话,则在最后需要调用XMVectorSaturate函数确保各个分量都控制在[0.0f, 1.0f]之间。

法向量

法向量用于表述物体表面的朝向,它是单位向量,并且垂直于该表面。对于曲面上一点,通常描述的是曲面该点的切面所对应的法向量。在光照的计算中会经常用到该向量。但是我们传入的通常是顶点数据而不是面的数据,因此顶点结构体内还会包含法向量数据:

struct VertexPosNormalColor
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT4 color;
static const D3D11_INPUT_ELEMENT_DESC inputLayout[3];
};

对应的每个输入元素的描述为:

const D3D11_INPUT_ELEMENT_DESC VertexPosNormalColor::inputLayout[3] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0}
};

对于棱角分明的物体,如立方体,一共由12个三角形组成,2个三角形的4个顶点构成一个面。由于该面的4个顶点要求法向量朝向一致,而一个顶点虽然与立方体的三个面邻接,但是法向量只有一个,因此需要分化出3个包含不同法向量的顶点。最终用于绘制该立方体的顶点数就需要24个,是立方体顶点数的3倍!

而对于没那么棱角分明的物体,表面只是稍微有些不平坦的话,我们可以求出该点相邻的所有面的平均法向量。

对于可以用函数表示的曲面,如球,则可以求出曲面一点对应切面的法向量。

对于函数:

\[f(x,y,z)≡0\]

则对应点切平面的法向量(非单位向量)为:

\[\frac{\partial{f}}{\partial{x}}\vec{i}+\frac{\partial{f}}{\partial{y}}\vec{j}+\frac{\partial{f}}{\partial{z}}\vec{k}\]

最后经过标准化后即为曲面该点对应的单位法向量。如球面方程:

\[f(x,y,z)=x^2+y^2+z^2-1≡0\]

最终对应的单位法向量为:

\[\vec{n}=(x,y,z)\]

法向量的变换

这里省略变换的证明,若一个物体的向量u与法向量n正交,当向量u经过了矩阵变换得到了\(\mathbf{u'}=\mathbf{u}\mathbf{A}\)时,对应变换后的法向量应为\(\mathbf{n'}=\mathbf{n}(\mathbf{A^{-1}})^{\mathbf{T}}\)

物体材质

光在照射到物体上时,由于物体的材质特性会反射一部分光到人眼,最终我们观察到的物体颜色就是被反射的那部分光的颜色。不同的物体有不同的材质属性,决定了各种颜色分量的反射系数是多少。其中红绿蓝每个分量的取值范围为[0.0f, 1.0f]

在C++中的结构体表示为:

// 物体表面材质
struct Material
{
Material() { memset(this, 0, sizeof(Material)); } DirectX::XMFLOAT4 Ambient;
DirectX::XMFLOAT4 Diffuse;
DirectX::XMFLOAT4 Specular; // w = 镜面反射强度
DirectX::XMFLOAT4 Reflect;
};

在HLSL中则表示为:

// 物体表面材质
struct Material
{
float4 Ambient;
float4 Diffuse;
float4 Specular; // w = SpecPower
float4 Reflect;
};

光的种类

环境光(Ambient Lighting)

环境光也可以说是间接光照,即光线经过了多次反射后最终被我们的人眼所见。即便在一个密闭的暗室使用手电筒照射,你还是可以感受到光充满了整个房间。同样,在阳光照射下,阴影部分也并不是纯粹的黑色,只是比其他地方暗上许多而已。因此在设置环境光的时候,可以考虑以你想呈现的阴影部分亮度进行设置。

在HLSL中,若环境光向量为\(\mathbf{l_a}\) ,物体材质对环境光的反射向量为\(\mathbf{m_a}\),最终环境光分量呈现的颜色为:

\(\mathbf{A} = \mathbf{l_a} \otimes \mathbf{m_a}\)

这里的乘号表示各个分量相乘,最终得到的向量为

(la.r * ma.r, la.g * ma.g, la.b * ma.b, la.a * ma.a)

漫反射光(Diffuse Lighting)

在现实生活中,我们看到的光以漫反射光为主。对于粗糙的表面,光照射在物体表面一点后反射的方向是不确定的。我们可以近似认为光线在照射到物体表面一点后会朝任意方向反射等量的光照,这样我们人眼无论在哪个方向观察该点,呈现的亮度应该是不会变化的(在没有镜面反射的基础)。但是物体的亮度与光线照射的方向有所关系,比如当均匀光线垂直照射物体的时候,此时看到的物体表面是最亮的;而均匀光线不经过物体表面,与表面平行的时候,物体的表面此时几乎是看不到的(此时可能仍有少量的光会到达物体表面,取决于光束的汇聚程度和与物体的距离)。毕竟光束不可能做到完全同一个方向照射,仍会有少数的散射光。

朗伯余弦定理

既然光照方向与亮度有关系,我们可以使用朗伯余弦定理来表示这种现象,用L表示光反射后的单位方向向量,n表示平面单位法向量,则有:

\(k_d = max(\mathbf{L} \cdot \mathbf{n}, 0)\)

若漫反射光向量为ld,物体材质对环境光的反射向量为md,最终漫反射光分量呈现的颜色为:

\(k_d * \mathbf{l_d} \otimes \mathbf{m_d} = k_d \mathbf{D}\)

镜面反射光(Specular Lighting)

某些较为光滑的平面可以均匀地反射光照。镜面反射光与人眼所在位置有联系,若人站在反射光的路径上看反射点,可以看到此时的点是最亮的,然后随着人眼远离反射光线的路径,看到的镜面反射光会越来越少,直至只能看到漫反射光和环境光的部分。

在HLSL中,若R为光反射后的单位方向向量,toEye为物体表面一点到人眼的单位方向向量,p为镜面系数(系数越大,表面越光滑,而且p的值必须大于等于1,否则会有很奇怪的效果),可以用下面的公式来表达镜面系数:

\(k_s=\begin{cases} max(\mathbf{R} \cdot \mathbf{toEye}, 0)^p, \mathbf{L} \cdot \mathbf{n} > 0\\ 0, \mathbf{L} \cdot \mathbf{n} <= 0\\ \end{cases}\)

若镜面反射光向量为ls,物体材质对镜面光的反射向量为ms,最终镜面反射光分量呈现的颜色为:

\(k_s * \mathbf{l_s} \otimes \mathbf{m_s} = k_s \mathbf{S}\)

HLSL常量缓冲区打包规则

这一部分内容已经转移到下面的链接了:

https://www.cnblogs.com/X-Jun/p/9376474.html

光照模型

平行光/方向光

平行光通常是一种全局光,它有一个固定的照射方向。

经平行光照射下物体表面一点的颜色可以初步表示为:

\(\mathbf{litColor}=\mathbf{A} + k_d \mathbf{D} + k_s \mathbf{S}\)

其中

\(k_d = max(\mathbf{L} \cdot \mathbf{n}, 0)\)

\(k_s=\begin{cases} max(\mathbf{R} \cdot \mathbf{toEye}, 0)^p, \mathbf{L} \cdot \mathbf{n} > 0\\ 0, \mathbf{L} \cdot \mathbf{n} <= 0\\ \end{cases}\)

在C++中,方向光的结构体表示为:

struct DirectionalLight
{
DirectionalLight() { memset(this, 0, sizeof(DirectionalLight)); } DirectX::XMFLOAT4 Ambient;
DirectX::XMFLOAT4 Diffuse;
DirectX::XMFLOAT4 Specular;
DirectX::XMFLOAT3 Direction;
float Pad; // 最后用一个浮点数填充使得该结构体大小满足16的倍数,便于我们以后在HLSL设置数组
};

在HLSL则表示为:

struct DirectionalLight
{
float4 Ambient;
float4 Diffuse;
float4 Specular;
float3 Direction;
float Pad;
};

在HLSL中计算平行光/方向光的函数如下(光向量是与光照射方向相反的单位向量):

void ComputeDirectionalLight(Material mat, DirectionalLight L,
float3 normal, float3 toEye,
out float4 ambient,
out float4 diffuse,
out float4 spec)
{
// 初始化输出
ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
spec = float4(0.0f, 0.0f, 0.0f, 0.0f); // 光向量与照射方向相反
float3 lightVec = -L.Direction; // 添加环境光
ambient = mat.Ambient * L.Ambient; // 添加漫反射光和镜面光
float diffuseFactor = dot(lightVec, normal); // 展开,避免动态分支
[flatten]
if (diffuseFactor > 0.0f)
{
float3 v = reflect(-lightVec, normal);
float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
spec = specFactor * mat.Specular * L.Specular;
}
}

注意该函数的normaltoEye向量都为单位向量,除此之外输出了光的三种分量。

点光

点光是一种有源光照模型,确定光源位置后,它会朝所有方向辐射光照。

在HLSL,若已知光源位置向量Q和照射点P,则单位光向量为:

(Q - P) / length(Q - P)

光的衰弱

随着距离的增长,光源照射到物体表面更远一点时呈现的亮度更低。其中光的衰弱只对直接光照有效,对间接光(如环境光分量)没有影响。

d为光源到物体一点的距离,则有:

\(I(d) = \frac{I_{0}}{a_{0}+a_{1}d+a_{2}d^2}\)

若灯光距离物体太远,计算到的衰弱因子接近于0.我们可以使用标量range来控制照射范围,对距离过大的物体提前避免了漫射光和镜面光的计算。

则经点光照射下物体表面一点的亮度可以初步表示为:

\(\mathbf{litColor}=\mathbf{A} + \frac{k_d \mathbf{D} + k_s \mathbf{S}}{a_{0}+a_{1}d+a_{2}d^2}\)

在C++中,点光可以被表示为:

// 点光
struct PointLight
{
PointLight() { memset(this, 0, sizeof(PointLight)); } DirectX::XMFLOAT4 Ambient;
DirectX::XMFLOAT4 Diffuse;
DirectX::XMFLOAT4 Specular; // 打包成4D向量: (Position, Range)
DirectX::XMFLOAT3 Position;
float Range; // 打包成4D向量: (A0, A1, A2, Pad)
DirectX::XMFLOAT3 Att;
float Pad; // 最后用一个浮点数填充使得该结构体大小满足16的倍数,便于我们以后在HLSL设置数组
};

在HLSL则表示为:

// 点光
struct PointLight
{
float4 Ambient;
float4 Diffuse;
float4 Specular; float3 Position;
float Range; float3 Att;
float Pad;
};

在HLSL中计算点光的函数如下

void ComputePointLight(Material mat, PointLight L, float3 pos, float3 normal, float3 toEye,
out float4 ambient, out float4 diffuse, out float4 spec)
{
// 初始化输出
ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
spec = float4(0.0f, 0.0f, 0.0f, 0.0f); // 从表面到光源的向量
float3 lightVec = L.Position - pos; // 表面到光线的距离
float d = length(lightVec); // 灯光范围测试
if (d > L.Range)
return; // 标准化光向量
lightVec /= d; // 环境光计算
ambient = mat.Ambient * L.Ambient; // 漫反射和镜面计算
float diffuseFactor = dot(lightVec, normal); // 展开以避免动态分支
[flatten]
if (diffuseFactor > 0.0f)
{
float3 v = reflect(-lightVec, normal);
float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
spec = specFactor * mat.Specular * L.Specular;
} // 光的衰弱
float att = 1.0f / dot(L.Att, float3(1.0f, d, d * d)); diffuse *= att;
spec *= att;
}

聚光灯

聚光灯也是一种有源光照模型,在确定光源位置后,其照射区域可以看作一个锥体,在同等照射距离下,越靠近照射中心,亮度越强。同样随着距离的增大,光照强度逐渐减弱。

在HLSL,若已知光源位置向量Q和照射点P,则单位光向量为:

(Q - P) / length(Q - P)

若已知光向量L和照射强度d,以及光的汇聚程度spot,则可以得到光照强度因子:

\(k_{spot} = max(\mathbf{-L} \cdot \mathbf{d}, 0)^{spot}\)

通常spot的值越大,光束的汇聚程度越强。

则经聚光灯照射下物体表面一点的亮度可以初步表示为:

\(\mathbf{LitColor} = k_{spot}(\mathbf{A} + \frac{k_d \mathbf{D} + k_s \mathbf{S}}{a_{0}+a_{1}d+a_{2}d^2})\)

在C++中,聚光灯的结构体如下:

struct SpotLight
{
SpotLight() { memset(this, 0, sizeof(SpotLight)); } DirectX::XMFLOAT4 Ambient;
DirectX::XMFLOAT4 Diffuse;
DirectX::XMFLOAT4 Specular; // 打包成4D向量: (Position, Range)
DirectX::XMFLOAT3 Position;
float Range; // 打包成4D向量: (Direction, Spot)
DirectX::XMFLOAT3 Direction;
float Spot; // 打包成4D向量: (Att, Pad)
DirectX::XMFLOAT3 Att;
float Pad; // 最后用一个浮点数填充使得该结构体大小满足16的倍数,便于我们以后在HLSL设置数组
};

在HLSL中,则表示为:

struct SpotLight
{
float4 Ambient;
float4 Diffuse;
float4 Specular; float3 Position;
float Range; float3 Direction;
float Spot; float3 Att;
float Pad;
};

在HLSL中计算聚光灯的函数如下

void ComputeSpotLight(Material mat, SpotLight L, float3 pos, float3 normal, float3 toEye,
out float4 ambient, out float4 diffuse, out float4 spec)
{
// 初始化输出
ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
spec = float4(0.0f, 0.0f, 0.0f, 0.0f); // // 从表面到光源的向量
float3 lightVec = L.Position - pos; // 表面到光源的距离
float d = length(lightVec); // 范围测试
if (d > L.Range)
return; // 标准化光向量
lightVec /= d; // 计算环境光部分
ambient = mat.Ambient * L.Ambient; // 计算漫反射光和镜面反射光部分
float diffuseFactor = dot(lightVec, normal); // 展开以避免动态分支
[flatten]
if (diffuseFactor > 0.0f)
{
float3 v = reflect(-lightVec, normal);
float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
spec = specFactor * mat.Specular * L.Specular;
} // 计算汇聚因子和衰弱系数
float spot = pow(max(dot(-lightVec, L.Direction), 0.0f), L.Spot);
float att = spot / dot(L.Att, float3(1.0f, d, d * d)); ambient *= spot;
diffuse *= att;
spec *= att;
}

HLSL代码

在LightHelper.hlsli标头文件中包含了上述所有的HLSL结构体以及三种光照模型的函数

然后Light_VS.hlsl和Light_PS.hlsl分别存放的是需要用到的顶点着色器和像素着色器

// Light.hlsli
#include "LightHelper.hlsli" cbuffer VSConstantBuffer : register(b0)
{
matrix g_World;
matrix g_View;
matrix g_Proj;
matrix g_WorldInvTranspose;
} cbuffer PSConstantBuffer : register(b1)
{
DirectionalLight g_DirLight;
PointLight g_PointLight;
SpotLight g_SpotLight;
Material g_Material;
float3 g_EyePosW;
float g_Pad;
} struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float4 Color : COLOR;
}; struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION; // 在世界中的位置
float3 NormalW : NORMAL; // 法向量在世界中的方向
float4 Color : COLOR;
};
// Light_VS.hlsl
#include "Light.hlsli" // 顶点着色器
VertexOut VS(VertexIn vIn)
{
VertexOut vOut;
matrix viewProj = mul(g_View, g_Proj);
float4 posW = mul(float4(vIn.PosL, 1.0f), g_World); vOut.PosH = mul(posW, viewProj);
vOut.PosW = posW.xyz;
vOut.NormalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose);
vOut.Color = vIn.Color; // 这里alpha通道的值默认为1.0
return vOut;
}
// Light_PS.hlsl
#include "Light.hlsli" // 像素着色器
float4 PS(VertexOut pIn) : SV_Target
{
// 标准化法向量
pIn.NormalW = normalize(pIn.NormalW); // 顶点指向眼睛的向量
float3 toEyeW = normalize(g_EyePosW - pIn.PosW); // 初始化为0
float4 ambient, diffuse, spec;
float4 A, D, S;
ambient = diffuse = spec = A = D = S = float4(0.0f, 0.0f, 0.0f, 0.0f); ComputeDirectionalLight(g_Material, g_DirLight, pIn.NormalW, toEyeW, A, D, S);
ambient += A;
diffuse += D;
spec += S; ComputePointLight(g_Material, g_PointLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
ambient += A;
diffuse += D;
spec += S; ComputeSpotLight(g_Material, g_SpotLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
ambient += A;
diffuse += D;
spec += S; float4 litColor = pIn.Color * (ambient + diffuse) + spec; litColor.a = g_Material.Diffuse.a * pIn.Color.a; return litColor;
}

这里有两个常量缓冲区,一个用于顶点着色阶段,另一个用于像素着色阶段。

由于这里的顶点信息还包含了颜色,我们只让像素颜色受环境光和漫反射光的影响,以凸显高光效果。

常用几何模型

在头文件Geometry.h中的名称空间Geometry内包含了五个常用的3D模型:球、立方体、圆柱体、圆锥体和平面。调用它可以生成模型数据以写入顶点和索引缓冲区。

(2018/12/15更新)目前的几何模型网格数据采用模板形式,可以做到支持VertexPosVertexPosColorVertexPosTexVertexPosNormalTexVertexPosNormalColorVertexPosNormalTangentTex的顶点以及WORDDWORD索引的输出

namespace Geometry
{
// 网格数据
template<class VertexType = VertexPosNormalTex, class IndexType = WORD>
struct MeshData
{
std::vector<VertexType> vertexVec; // 顶点数组
std::vector<IndexType> indexVec; // 索引数组 MeshData()
{
// 需检验索引类型合法性
static_assert(sizeof(IndexType) == 2 || sizeof(IndexType) == 4, "The size of IndexType must be 2 bytes or 4 bytes!");
static_assert(std::is_unsigned<IndexType>::value, "IndexType must be unsigned integer!");
}
}; // 创建球体网格数据,levels和slices越大,精度越高。
template<class VertexType = VertexPosNormalTex, class IndexType = WORD>
MeshData<VertexType, IndexType> CreateSphere(float radius = 1.0f, UINT levels = 20, UINT slices = 20,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f }); // 创建立方体网格数据
template<class VertexType = VertexPosNormalTex, class IndexType = WORD>
MeshData<VertexType, IndexType> CreateBox(float width = 2.0f, float height = 2.0f, float depth = 2.0f,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f }); // 创建圆柱体网格数据,slices越大,精度越高。
template<class VertexType = VertexPosNormalTex, class IndexType = WORD>
MeshData<VertexType, IndexType> CreateCylinder(float radius = 1.0f, float height = 2.0f, UINT slices = 20,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f }); // 创建只有圆柱体侧面的网格数据,slices越大,精度越高
template<class VertexType = VertexPosNormalTex, class IndexType = WORD>
MeshData<VertexType, IndexType> CreateCylinderNoCap(float radius = 1.0f, float height = 2.0f, UINT slices = 20,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f }); // 创建一个覆盖NDC屏幕的面
template<class VertexType = VertexPosTex, class IndexType = WORD>
MeshData<VertexType, IndexType> Create2DShow(const DirectX::XMFLOAT2& center, const DirectX::XMFLOAT2& scale = { 1.0f, 1.0f },
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f });
template<class VertexType = VertexPosTex, class IndexType = WORD>
MeshData<VertexType, IndexType> Create2DShow(float centerX = 0.0f, float centerY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f }); // 创建一个平面
template<class VertexType = VertexPosNormalTex, class IndexType = WORD>
MeshData<VertexType, IndexType> CreatePlane(const DirectX::XMFLOAT3& center, const DirectX::XMFLOAT2& planeSize = { 10.0f, 10.0f },
const DirectX::XMFLOAT2& maxTexCoord = { 1.0f, 1.0f }, const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f });
template<class VertexType = VertexPosNormalTex, class IndexType = WORD>
MeshData<VertexType, IndexType> CreatePlane(float centerX = 0.0f, float centerY = 0.0f, float centerZ = 0.0f,
float width = 10.0f, float depth = 10.0f, float texU = 1.0f, float texV = 1.0f,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f });
}

为了能够保证支持前面提到的顶点类型,在内部实现中需要依赖VertexData产生完整的顶点数据,并根据顶点的类型进行针对性筛选保留,并输出到对应的顶点类型。其中筛选的核心,一是要建立元素语义与VertexData对应内存区间关系,二是要确定当前顶点需要插入的字节偏移位置。关于第一点,在下面的代码中,我们直接利用map建立映射关系

namespace Geometry
{
namespace Internal
{
//
// 以下结构体和函数仅供内部实现使用
// struct VertexData
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT4 tangent;
DirectX::XMFLOAT4 color;
DirectX::XMFLOAT2 tex;
}; // 根据目标顶点类型选择性将数据插入
template<class VertexType>
inline void InsertVertexElement(VertexType& vertexDst, const VertexData& vertexSrc)
{
static std::string semanticName;
static const std::map<std::string, std::pair<size_t, size_t>> semanticSizeMap = {
{"POSITION", std::pair<size_t, size_t>(0, 12)},
{"NORMAL", std::pair<size_t, size_t>(12, 24)},
{"TANGENT", std::pair<size_t, size_t>(24, 40)},
{"COLOR", std::pair<size_t, size_t>(40, 56)},
{"TEXCOORD", std::pair<size_t, size_t>(56, 64)}
}; for (size_t i = 0; i < ARRAYSIZE(VertexType::inputLayout); i++)
{
semanticName = VertexType::inputLayout[i].SemanticName;
const auto& range = semanticSizeMap.at(semanticName);
memcpy_s(reinterpret_cast<char*>(&vertexDst) + VertexType::inputLayout[i].AlignedByteOffset,
range.second - range.first,
reinterpret_cast<const char*>(&vertexSrc) + range.first,
range.second - range.first);
}
}
}
// ...

上面的pair对应了VertexData指定语义的元素所在的内存区间。

而关于第二点,就是要确定当前我要赋值的顶点元素目前处理到第几个成员,其字节偏移量是多少。这些可以使用顶点的成员布局来实现,比如VertexPosNormalTex

struct VertexPosNormalTex
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT2 tex;
static const D3D11_INPUT_ELEMENT_DESC inputLayout[3];
}; const D3D11_INPUT_ELEMENT_DESC VertexPosNormalTex::inputLayout[3] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};

最后就是顶点元素插入的实现:

// 根据目标顶点类型选择性将数据插入
template<class VertexType>
inline void Geometry::InsertVertexElement(VertexType& vertexDst, const VertexData& vertexSrc)
{
static std::string semanticName;
for (size_t i = 0; i < ARRAYSIZE(VertexType::inputLayout); i++)
{
semanticName = VertexType::inputLayout[i].SemanticName;
const auto& range = Geometry::semanticSizeMap.at(semanticName);
memcpy_s(reinterpret_cast<char*>(&vertexDst) + VertexType::inputLayout[i].AlignedByteOffset,
range.second - range.first,
reinterpret_cast<const char*>(&vertexSrc) + range.first,
range.second - range.first);
}
}

Geometry::CreateBox函数--创建立方体

该函数会创建包含24个顶点的数组(立方体一个顶点重复3次,但法向量不相同),以及一个含36个索引的数组:

template<class VertexType, class IndexType>
inline MeshData<VertexType, IndexType> CreateBox(float width, float height, float depth, const DirectX::XMFLOAT4 & color)
{
using namespace DirectX; MeshData<VertexType, IndexType> meshData;
meshData.vertexVec.resize(24); Internal::VertexData vertexDataArr[24];
float w2 = width / 2, h2 = height / 2, d2 = depth / 2; // 右面(+X面)
vertexDataArr[0].pos = XMFLOAT3(w2, -h2, -d2);
vertexDataArr[1].pos = XMFLOAT3(w2, h2, -d2);
vertexDataArr[2].pos = XMFLOAT3(w2, h2, d2);
vertexDataArr[3].pos = XMFLOAT3(w2, -h2, d2);
// 左面(-X面)
vertexDataArr[4].pos = XMFLOAT3(-w2, -h2, d2);
vertexDataArr[5].pos = XMFLOAT3(-w2, h2, d2);
vertexDataArr[6].pos = XMFLOAT3(-w2, h2, -d2);
vertexDataArr[7].pos = XMFLOAT3(-w2, -h2, -d2);
// 顶面(+Y面)
vertexDataArr[8].pos = XMFLOAT3(-w2, h2, -d2);
vertexDataArr[9].pos = XMFLOAT3(-w2, h2, d2);
vertexDataArr[10].pos = XMFLOAT3(w2, h2, d2);
vertexDataArr[11].pos = XMFLOAT3(w2, h2, -d2);
// 底面(-Y面)
vertexDataArr[12].pos = XMFLOAT3(w2, -h2, -d2);
vertexDataArr[13].pos = XMFLOAT3(w2, -h2, d2);
vertexDataArr[14].pos = XMFLOAT3(-w2, -h2, d2);
vertexDataArr[15].pos = XMFLOAT3(-w2, -h2, -d2);
// 背面(+Z面)
vertexDataArr[16].pos = XMFLOAT3(w2, -h2, d2);
vertexDataArr[17].pos = XMFLOAT3(w2, h2, d2);
vertexDataArr[18].pos = XMFLOAT3(-w2, h2, d2);
vertexDataArr[19].pos = XMFLOAT3(-w2, -h2, d2);
// 正面(-Z面)
vertexDataArr[20].pos = XMFLOAT3(-w2, -h2, -d2);
vertexDataArr[21].pos = XMFLOAT3(-w2, h2, -d2);
vertexDataArr[22].pos = XMFLOAT3(w2, h2, -d2);
vertexDataArr[23].pos = XMFLOAT3(w2, -h2, -d2); for (UINT i = 0; i < 4; ++i)
{
// 右面(+X面)
vertexDataArr[i].normal = XMFLOAT3(1.0f, 0.0f, 0.0f);
vertexDataArr[i].tangent = XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f);
vertexDataArr[i].color = color;
// 左面(-X面)
vertexDataArr[i + 4].normal = XMFLOAT3(-1.0f, 0.0f, 0.0f);
vertexDataArr[i + 4].tangent = XMFLOAT4(0.0f, 0.0f, -1.0f, 1.0f);
vertexDataArr[i + 4].color = color;
// 顶面(+Y面)
vertexDataArr[i + 8].normal = XMFLOAT3(0.0f, 1.0f, 0.0f);
vertexDataArr[i + 8].tangent = XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f);
vertexDataArr[i + 8].color = color;
// 底面(-Y面)
vertexDataArr[i + 12].normal = XMFLOAT3(0.0f, -1.0f, 0.0f);
vertexDataArr[i + 12].tangent = XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f);
vertexDataArr[i + 12].color = color;
// 背面(+Z面)
vertexDataArr[i + 16].normal = XMFLOAT3(0.0f, 0.0f, 1.0f);
vertexDataArr[i + 16].tangent = XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f);
vertexDataArr[i + 16].color = color;
// 正面(-Z面)
vertexDataArr[i + 20].normal = XMFLOAT3(0.0f, 0.0f, -1.0f);
vertexDataArr[i + 20].tangent = XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f);
vertexDataArr[i + 20].color = color;
} for (UINT i = 0; i < 6; ++i)
{
vertexDataArr[i * 4].tex = XMFLOAT2(0.0f, 1.0f);
vertexDataArr[i * 4 + 1].tex = XMFLOAT2(0.0f, 0.0f);
vertexDataArr[i * 4 + 2].tex = XMFLOAT2(1.0f, 0.0f);
vertexDataArr[i * 4 + 3].tex = XMFLOAT2(1.0f, 1.0f);
} for (UINT i = 0; i < 24; ++i)
{
Internal::InsertVertexElement(meshData.vertexVec[i], vertexDataArr[i]);
} meshData.indexVec = {
0, 1, 2, 2, 3, 0, // 右面(+X面)
4, 5, 6, 6, 7, 4, // 左面(-X面)
8, 9, 10, 10, 11, 8, // 顶面(+Y面)
12, 13, 14, 14, 15, 12, // 底面(-Y面)
16, 17, 18, 18, 19, 16, // 背面(+Z面)
20, 21, 22, 22, 23, 20 // 正面(-Z面)
}; return meshData;
}

Geometry::CreateSphere函数--创建球体

由于3D模型都是用三角形模拟的,这里的球体如果想要效果更佳逼真,需要用到更多的三角形。球体的法向量如前面所述,使用微分法求出。在提供参数的时候,levels决定上下分多少层,slices决定一个水平圆切面的顶点数目。levelsslices越高,生成的顶点数、索引数都会越多。

template<class VertexType, class IndexType>
inline MeshData<VertexType, IndexType> CreateSphere(float radius, UINT levels, UINT slices, const DirectX::XMFLOAT4 & color)
{
using namespace DirectX; MeshData<VertexType, IndexType> meshData;
UINT vertexCount = 2 + (levels - 1) * (slices + 1);
UINT indexCount = 6 * (levels - 1) * slices;
meshData.vertexVec.resize(vertexCount);
meshData.indexVec.resize(indexCount); Internal::VertexData vertexData;
IndexType vIndex = 0, iIndex = 0; float phi = 0.0f, theta = 0.0f;
float per_phi = XM_PI / levels;
float per_theta = XM_2PI / slices;
float x, y, z; // 放入顶端点
vertexData = { XMFLOAT3(0.0f, radius, 0.0f), XMFLOAT3(0.0f, 1.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.0f, 0.0f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData); for (UINT i = 1; i < levels; ++i)
{
phi = per_phi * i;
// 需要slices + 1个顶点是因为 起点和终点需为同一点,但纹理坐标值不一致
for (UINT j = 0; j <= slices; ++j)
{
theta = per_theta * j;
x = radius * sinf(phi) * cosf(theta);
y = radius * cosf(phi);
z = radius * sinf(phi) * sinf(theta);
// 计算出局部坐标、法向量、Tangent向量和纹理坐标
XMFLOAT3 pos = XMFLOAT3(x, y, z), normal;
XMStoreFloat3(&normal, XMVector3Normalize(XMLoadFloat3(&pos))); vertexData = { pos, normal, XMFLOAT4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, XMFLOAT2(theta / XM_2PI, phi / XM_PI) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
}
} // 放入底端点
vertexData = { XMFLOAT3(0.0f, -radius, 0.0f), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.0f, 1.0f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData); // 逐渐放入索引
if (levels > 1)
{
for (UINT j = 1; j <= slices; ++j)
{
meshData.indexVec[iIndex++] = 0;
meshData.indexVec[iIndex++] = j % (slices + 1) + 1;
meshData.indexVec[iIndex++] = j;
}
} for (UINT i = 1; i < levels - 1; ++i)
{
for (UINT j = 1; j <= slices; ++j)
{
meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j;
meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j % (slices + 1) + 1;
meshData.indexVec[iIndex++] = i * (slices + 1) + j % (slices + 1) + 1; meshData.indexVec[iIndex++] = i * (slices + 1) + j % (slices + 1) + 1;
meshData.indexVec[iIndex++] = i * (slices + 1) + j;
meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j;
}
} // 逐渐放入索引
if (levels > 1)
{
for (UINT j = 1; j <= slices; ++j)
{
meshData.indexVec[iIndex++] = (levels - 2) * (slices + 1) + j;
meshData.indexVec[iIndex++] = (levels - 2) * (slices + 1) + j % (slices + 1) + 1;
meshData.indexVec[iIndex++] = (levels - 1) * (slices + 1) + 1;
}
} return meshData;
}

Geometry::CreateCylinderNoCap函数--创建圆柱体的侧面

由于后续项目要用到无上下圆面的圆柱体,故拆分出来单独实现:

template<class VertexType, class IndexType>
inline MeshData<VertexType, IndexType> CreateCylinderNoCap(float radius, float height, UINT slices, const DirectX::XMFLOAT4 & color)
{
using namespace DirectX; MeshData<VertexType, IndexType> meshData;
UINT vertexCount = 2 * (slices + 1);
UINT indexCount = 6 * slices;
meshData.vertexVec.resize(vertexCount);
meshData.indexVec.resize(indexCount); float h2 = height / 2;
float theta = 0.0f;
float per_theta = XM_2PI / slices; Internal::VertexData vertexData; // 放入侧面顶端点
for (UINT i = 0; i <= slices; ++i)
{
theta = i * per_theta;
vertexData = { XMFLOAT3(radius * cosf(theta), h2, radius * sinf(theta)), XMFLOAT3(cosf(theta), 0.0f, sinf(theta)),
XMFLOAT4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, XMFLOAT2(theta / XM_2PI, 0.0f) };
Internal::InsertVertexElement(meshData.vertexVec[i], vertexData);
} // 放入侧面底端点
for (UINT i = 0; i <= slices; ++i)
{
theta = i * per_theta;
vertexData = { XMFLOAT3(radius * cosf(theta), -h2, radius * sinf(theta)), XMFLOAT3(cosf(theta), 0.0f, sinf(theta)),
XMFLOAT4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, XMFLOAT2(theta / XM_2PI, 1.0f) };
UINT vIndex = (slices + 1) + i;
Internal::InsertVertexElement(meshData.vertexVec[vIndex], vertexData);
} // 放入索引
UINT iIndex = 0; for (UINT i = 0; i < slices; ++i)
{
meshData.indexVec[iIndex++] = i;
meshData.indexVec[iIndex++] = i + 1;
meshData.indexVec[iIndex++] = (slices + 1) + i + 1; meshData.indexVec[iIndex++] = (slices + 1) + i + 1;
meshData.indexVec[iIndex++] = (slices + 1) + i;
meshData.indexVec[iIndex++] = i;
} return meshData;
}

Geometry::CreateCylinder函数--创建圆柱体

圆柱体侧面可以调用上面的函数,剩下的两个圆盖在这完成:

template<class VertexType, class IndexType>
inline MeshData<VertexType, IndexType> CreateCylinder(float radius, float height, UINT slices, const DirectX::XMFLOAT4 & color)
{
using namespace DirectX; auto meshData = CreateCylinderNoCap<VertexType, IndexType>(radius, height, slices, color);
UINT vertexCount = 4 * (slices + 1) + 2;
UINT indexCount = 12 * slices;
meshData.vertexVec.resize(vertexCount);
meshData.indexVec.resize(indexCount); float h2 = height / 2;
float theta = 0.0f;
float per_theta = XM_2PI / slices; IndexType vIndex = 2 * (slices + 1), iIndex = 6 * slices;
IndexType offset = 2 * (slices + 1);
Internal::VertexData vertexData; // 放入顶端圆心
vertexData = { XMFLOAT3(0.0f, h2, 0.0f), XMFLOAT3(0.0f, 1.0f, 0.0f),
XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.5f, 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData); // 放入顶端圆上各点
for (UINT i = 0; i <= slices; ++i)
{
theta = i * per_theta;
vertexData = { XMFLOAT3(radius * cosf(theta), h2, radius * sinf(theta)), XMFLOAT3(0.0f, 1.0f, 0.0f),
XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
} // 放入底端圆心
vertexData = { XMFLOAT3(0.0f, -h2, 0.0f), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.5f, 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData); // 放入底部圆上各点
for (UINT i = 0; i <= slices; ++i)
{
theta = i * per_theta;
vertexData = { XMFLOAT3(radius * cosf(theta), -h2, radius * sinf(theta)), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
} // 逐渐放入顶部三角形索引
for (UINT i = 1; i <= slices; ++i)
{
meshData.indexVec[iIndex++] = offset;
meshData.indexVec[iIndex++] = offset + i % (slices + 1) + 1;
meshData.indexVec[iIndex++] = offset + i;
} // 逐渐放入底部三角形索引
offset += slices + 2;
for (UINT i = 1; i <= slices; ++i)
{
meshData.indexVec[iIndex++] = offset;
meshData.indexVec[iIndex++] = offset + i;
meshData.indexVec[iIndex++] = offset + i % (slices + 1) + 1;
} return meshData;
}

Geometry::CreateConeNoCap函数--创建圆锥体的侧面

该函数不包含底部的圆形面,注意圆锥的尖端因为各自所处的三角形法向量不一致,需要创建和底部圆相同的数目的顶点:

template<class VertexType, class IndexType>
MeshData<VertexType, IndexType> CreateConeNoCap(float radius, float height, UINT slices, const DirectX::XMFLOAT4& color)
{
using namespace DirectX; MeshData<VertexType, IndexType> meshData;
meshData.vertexVec.resize(2 * slices);
meshData.indexVec.resize(3 * slices); float h2 = height / 2;
float theta = 0.0f;
float per_theta = XM_2PI / slices;
float len = sqrtf(height * height + radius * radius);
UINT iIndex = 0;
UINT vIndex = 0;
Internal::VertexData vertexData; // 放入圆锥尖端顶点(每个顶点包含不同的法向量和切线向量)
for (UINT i = 0; i < slices; ++i)
{
theta = i * per_theta + per_theta / 2;
vertexData = { XMFLOAT3(0.0f, h2, 0.0f), XMFLOAT3(radius * cosf(theta) / len, height / len, radius * sinf(theta) / len),
XMFLOAT4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, XMFLOAT2(0.5f, 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
} // 放入圆锥底面顶点
for (UINT i = 0; i < slices; ++i)
{
theta = i * per_theta;
vertexData = { XMFLOAT3(radius * cosf(theta), -h2, radius * sinf(theta)), XMFLOAT3(radius * cosf(theta) / len, height / len, radius * sinf(theta) / len),
XMFLOAT4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, XMFLOAT2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
} // 放入索引
for (UINT i = 0; i < slices; ++i)
{
meshData.indexVec[iIndex++] = i;
meshData.indexVec[iIndex++] = slices + (i + 1) % slices;
meshData.indexVec[iIndex++] = slices + i % slices;
} return meshData;
}

Geometry::CreateCone函数--创建圆锥体

template<class VertexType, class IndexType>
MeshData<VertexType, IndexType> CreateCone(float radius, float height, UINT slices, const DirectX::XMFLOAT4& color)
{
using namespace DirectX;
auto meshData = CreateConeNoCap<VertexType, IndexType>(radius, height, slices, color); UINT vertexCount = 3 * slices + 1;
UINT indexCount = 6 * slices;
meshData.vertexVec.resize(vertexCount);
meshData.indexVec.resize(indexCount); float h2 = height / 2;
float theta = 0.0f;
float per_theta = XM_2PI / slices;
UINT iIndex = 3 * slices;
UINT vIndex = 2 * slices;
Internal::VertexData vertexData; // 放入圆锥底面顶点
for (UINT i = 0; i < slices; ++i)
{
theta = i * per_theta;
vertexData = { XMFLOAT3(radius * cosf(theta), -h2, radius * sinf(theta)), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
}
vertexData = { XMFLOAT3(0.0f, -h2, 0.0f), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.5f, 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData); // 放入索引
UINT offset = 2 * slices;
for (UINT i = 0; i < slices; ++i)
{
meshData.indexVec[iIndex++] = offset + slices;
meshData.indexVec[iIndex++] = offset + i % slices;
meshData.indexVec[iIndex++] = offset + (i + 1) % slices;
} return meshData;
}

剩下关于平面的创建可以在项目源码中阅读。

光栅化状态

Direct3D是基于状态机的,我们可以通过修改这些状态来修改渲染管线的当前行为。有三种状态值得我们现在以及后续留意:

  1. 光栅化状态(光栅化阶段)
  2. 混合状态(输出合并阶段)
  3. 深度/模板状态(输出合并阶段)

光栅化阶段负责对前面传来的顶点数据,尤其是对4D位置向量(SV_POSITION)进行透视除法,判断顶点是否在NDC空间内。其次,就是根据光栅化设置的状态来决定顺(逆)时针三角形是否通过。此外,它还能指定额外的裁剪区域,即矩形区域外的三角形(或者部分)被裁剪掉,仅留下剩余在矩形区域内的像素片元传递到像素着色器中进行处理。最后的视口变换也是在光栅化阶段完成的,在像素着色器中,SV_POSITIONx分量和y分量都已经经过视口变换成为最终的屏幕坐标,且带有小数点0.5,这是因为要取到像素的中心位置,即对于800x600的视口区域,实际上的屏幕坐标取值范围为[0.5, 800.5]x[0.5, 600.5]z分量取值范围为[0, 1]。这一点读者可以修改像素着色器使得SV_POSITION与像素颜色结果有关联,然后进入调试以验证。

ID3D11Device::CreateRasterizerState方法--创建光栅化状态

在创建光栅化状态前,我们需要先填充D3D11_RASTERIZER_DESC结构体来描述光栅化状态:

typedef struct D3D11_RASTERIZER_DESC
{
D3D11_FILL_MODE FillMode; // 填充模式
D3D11_CULL_MODE CullMode; // 裁剪模式
BOOL FrontCounterClockwise; // 是否三角形顶点按逆时针排布时为正面
INT DepthBias; // 忽略
FLOAT DepthBiasClamp; // 忽略
FLOAT SlopeScaledDepthBias; // 忽略
BOOL DepthClipEnable; // 是否允许深度测试将范围外的像素进行裁剪,默认TRUE
BOOL ScissorEnable; // 是否允许指定矩形范围的裁剪,若TRUE,则需要在RSSetScissor设置像素保留的矩形区域
BOOL MultisampleEnable; // 是否允许多重采样
BOOL AntialiasedLineEnable; // 是否允许反走样线,仅当多重采样为FALSE时才有效
} D3D11_RASTERIZER_DESC;

对于枚举类型D3D11_FILL_MODE有如下枚举值:

枚举值 含义
D3D11_FILL_WIREFRAME = 2 线框填充方式
D3D11_FILL_SOLID = 3 面填充方式

枚举类型D3D11_CULL_MODE有如下枚举值:

枚举值 含义
D3D11_CULL_NONE = 1 无背面裁剪,即三角形无论处在视野的正面还是背面都能看到
D3D11_CULL_FRONT = 2 对处在视野正面的三角形进行裁剪
D3D11_CULL_BACK = 3 对处在视野背面的三角形进行裁剪

光栅化创建的方法如下:

HRESULT ID3D11Device::CreateRasterizerState(
const D3D11_RASTERIZER_DESC *pRasterizerDesc, // [In]光栅化状态描述
ID3D11RasterizerState **ppRasterizerState) = 0; // [Out]输出光栅化状态

ID3D11DeviceContext::RSSetState方法--设置光栅化状态

void ID3D11DeviceContext::RSSetState(
ID3D11RasterizerState *pRasterizerState); // [In]光栅化状态,若为nullptr,则使用默认光栅化状态

默认光栅化状态如下:

FillMode = D3D11_FILL_SOLID;
CullMode = D3D11_CULL_BACK;
FrontCounterClockwise = FALSE;
DepthBias = 0;
SlopeScaledDepthBias = 0.0f;
DepthBiasClamp = 0.0f;
DepthClipEnable = TRUE;
ScissorEnable = FALSE;
MultisampleEnable = FALSE;
AntialiasedLineEnable = FALSE;

绘制线框

绘制线框有两种方式:

  1. 在输入装配阶段,以图元linelist的方式进行装配,然后还修改索引缓冲区,使得每两个索引对应一条线。
  2. 修改光栅化阶段,以线框(wireframe)的方式进行像素点的标记。

显然,第2种方式操作起来会更加容易一些。

首先我们需要创建线框绘制的光栅化状态:

// ******************
// 初始化光栅化状态
//
D3D11_RASTERIZER_DESC rasterizerDesc;
ZeroMemory(&rasterizerDesc, sizeof(rasterizerDesc));
rasterizerDesc.FillMode = D3D11_FILL_WIREFRAME;
rasterizerDesc.CullMode = D3D11_CULL_NONE;
rasterizerDesc.FrontCounterClockwise = false;
rasterizerDesc.DepthClipEnable = true;
HR(m_pd3dDevice->CreateRasterizerState(&rasterizerDesc, m_pRSWireframe.GetAddressOf()));

然后绘制线框的时候只需要这样调用:

m_pd3dImmediateContext->RSSetState(m_pRSWireframe.Get());

如果想要恢复正常的面绘制,上述传入nullptr即可。

GameApp类

GameApp类的变化如下:

class GameApp : public D3DApp
{
public: struct VSConstantBuffer
{
DirectX::XMMATRIX world;
DirectX::XMMATRIX view;
DirectX::XMMATRIX proj;
DirectX::XMMATRIX worldInvTranspose; }; struct PSConstantBuffer
{
DirectionalLight dirLight;
PointLight pointLight;
SpotLight spotLight;
Material material;
DirectX::XMFLOAT4 eyePos;
}; public:
GameApp(HINSTANCE hInstance);
~GameApp(); bool Init();
void OnResize();
void UpdateScene(float dt);
void DrawScene(); private:
bool InitEffect();
bool InitResource();
bool ResetMesh(const Geometry::MeshData<VertexPosNormalColor>& meshData); private:
ComPtr<ID3D11InputLayout> m_pVertexLayout; // 顶点输入布局
ComPtr<ID3D11Buffer> m_pVertexBuffer; // 顶点缓冲区
ComPtr<ID3D11Buffer> m_pIndexBuffer; // 索引缓冲区
ComPtr<ID3D11Buffer> m_pConstantBuffers[2]; // 常量缓冲区
UINT m_IndexCount; // 绘制物体的索引数组大小 ComPtr<ID3D11VertexShader> m_pVertexShader; // 顶点着色器
ComPtr<ID3D11PixelShader> m_pPixelShader; // 像素着色器
VSConstantBuffer m_VSConstantBuffer; // 用于修改用于VS的GPU常量缓冲区的变量
PSConstantBuffer m_PSConstantBuffer; // 用于修改用于PS的GPU常量缓冲区的变量 DirectionalLight m_DirLight; // 默认环境光
PointLight m_PointLight; // 默认点光
SpotLight m_SpotLight; // 默认汇聚光 ComPtr<ID3D11RasterizerState> m_pRSWireframe; // 光栅化状态: 线框模式
bool m_IsWireframeMode; // 当前是否为线框模式 };

其中,LightHelper.h包含了前面描述的材质和三种光照模型的结构体

GameApp类中则新添加了几个结构体和对应的一些成员。

GameApp::ResetMesh方法--重新设置要使用的模型

该项目为了演示不同3D模型下的光照效果,创建了该方法用于重新设置要使用的模型,但在这里物体表面的颜色默认都被设置为白色。实现如下:

bool GameApp::ResetMesh(const Geometry::MeshData<VertexPosNormalColor>& meshData)
{
// 释放旧资源
m_pVertexBuffer.Reset();
m_pIndexBuffer.Reset(); // 设置顶点缓冲区描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = (UINT)meshData.vertexVec.size() * sizeof(VertexPosNormalColor);
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// 新建顶点缓冲区
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = meshData.vertexVec.data();
HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffer.GetAddressOf())); // 输入装配阶段的顶点缓冲区设置
UINT stride = sizeof(VertexPosNormalColor); // 跨越字节数
UINT offset = 0; // 起始偏移量 m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), &stride, &offset); // 设置索引缓冲区描述
m_IndexCount = (UINT)meshData.indexVec.size();
D3D11_BUFFER_DESC ibd;
ZeroMemory(&ibd, sizeof(ibd));
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = m_IndexCount * sizeof(WORD);
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
// 新建索引缓冲区
InitData.pSysMem = meshData.indexVec.data();
HR(m_pd3dDevice->CreateBuffer(&ibd, &InitData, m_pIndexBuffer.GetAddressOf()));
// 输入装配阶段的索引缓冲区设置
m_pd3dImmediateContext->IASetIndexBuffer(m_pIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0); return true;
}

GameApp::InitResource方法的变化

该方法初始化模型后,就会初始化常量缓冲区的数据,以及一些光照模型和物体材质。矩阵需要注意转置:

bool GameApp::InitResource()
{
// ******************
// 初始化网格模型
//
auto meshData = Geometry::CreateBox<VertexPosNormalColor>();
ResetMesh(meshData); // ******************
// 设置常量缓冲区描述
//
D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DYNAMIC;
cbd.ByteWidth = sizeof(VSConstantBuffer);
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
// 新建用于VS和PS的常量缓冲区
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[0].GetAddressOf()));
cbd.ByteWidth = sizeof(PSConstantBuffer);
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[1].GetAddressOf())); // ******************
// 初始化默认光照
// 方向光
m_DirLight.ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
m_DirLight.diffuse = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
m_DirLight.specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_DirLight.direction = XMFLOAT3(-0.577f, -0.577f, 0.577f);
// 点光
m_PointLight.position = XMFLOAT3(0.0f, 0.0f, -10.0f);
m_PointLight.ambient = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f);
m_PointLight.diffuse = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);
m_PointLight.specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_PointLight.att = XMFLOAT3(0.0f, 0.1f, 0.0f);
m_PointLight.range = 25.0f;
// 聚光灯
m_SpotLight.position = XMFLOAT3(0.0f, 0.0f, -5.0f);
m_SpotLight.direction = XMFLOAT3(0.0f, 0.0f, 1.0f);
m_SpotLight.ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
m_SpotLight.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
m_SpotLight.specular = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
m_SpotLight.att = XMFLOAT3(1.0f, 0.0f, 0.0f);
m_SpotLight.spot = 12.0f;
m_SpotLight.range = 10000.0f;
// 初始化用于VS的常量缓冲区的值
m_VSConstantBuffer.world = XMMatrixIdentity();
m_VSConstantBuffer.view = XMMatrixTranspose(XMMatrixLookAtLH(
XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f),
XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),
XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)
));
m_VSConstantBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f));
m_VSConstantBuffer.worldInvTranspose = XMMatrixIdentity(); // 初始化用于PS的常量缓冲区的值
m_PSConstantBuffer.material.ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_PSConstantBuffer.material.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
m_PSConstantBuffer.material.specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 5.0f);
// 使用默认平行光
m_PSConstantBuffer.dirLight = m_DirLight;
// 注意不要忘记设置此处的观察位置,否则高亮部分会有问题
m_PSConstantBuffer.eyePos = XMFLOAT4(0.0f, 0.0f, -5.0f, 0.0f); // 更新PS常量缓冲区资源
D3D11_MAPPED_SUBRESOURCE mappedData;
HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[1].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
memcpy_s(mappedData.pData, sizeof(PSConstantBuffer), &m_VSConstantBuffer, sizeof(PSConstantBuffer));
m_pd3dImmediateContext->Unmap(m_pConstantBuffers[1].Get(), 0); // ******************
// 初始化光栅化状态
//
D3D11_RASTERIZER_DESC rasterizerDesc;
ZeroMemory(&rasterizerDesc, sizeof(rasterizerDesc));
rasterizerDesc.FillMode = D3D11_FILL_WIREFRAME;
rasterizerDesc.CullMode = D3D11_CULL_NONE;
rasterizerDesc.FrontCounterClockwise = false;
rasterizerDesc.DepthClipEnable = true;
HR(m_pd3dDevice->CreateRasterizerState(&rasterizerDesc, m_pRSWireframe.GetAddressOf())); // ******************
// 给渲染管线各个阶段绑定好所需资源
// // 设置图元类型,设定输入布局
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout.Get());
// 将着色器绑定到渲染管线
m_pd3dImmediateContext->VSSetShader(m_pVertexShader.Get(), nullptr, 0);
// VS常量缓冲区对应HLSL寄存于b0的常量缓冲区
m_pd3dImmediateContext->VSSetConstantBuffers(0, 1, m_pConstantBuffers[0].GetAddressOf());
// PS常量缓冲区对应HLSL寄存于b1的常量缓冲区
m_pd3dImmediateContext->PSSetConstantBuffers(1, 1, m_pConstantBuffers[1].GetAddressOf());
m_pd3dImmediateContext->PSSetShader(m_pPixelShader.Get(), nullptr, 0); // ******************
// 设置调试对象名
//
D3D11SetDebugObjectName(m_pVertexLayout.Get(), "VertexPosNormalTexLayout");
D3D11SetDebugObjectName(m_pConstantBuffers[0].Get(), "VSConstantBuffer");
D3D11SetDebugObjectName(m_pConstantBuffers[1].Get(), "PSConstantBuffer");
D3D11SetDebugObjectName(m_pVertexShader.Get(), "Light_VS");
D3D11SetDebugObjectName(m_pPixelShader.Get(), "Light_PS"); return true;
}

GameApp::UpdateScene方法的变化

这里排除掉键盘操作的代码部分,重点需要观察worldworldInvTranspose的赋值:

void GameApp::UpdateScene(float dt)
{
static float phi = 0.0f, theta = 0.0f;
phi += 0.0001f, theta += 0.00015f;
XMMATRIX W = XMMatrixRotationX(phi) * XMMatrixRotationY(theta);
m_VSConstantBuffer.world = XMMatrixTranspose(W);
m_VSConstantBuffer.worldInvTranspose = XMMatrixInverse(nullptr, W); // 两次转置可以抵消 // ... // 更新常量缓冲区,让立方体转起来
D3D11_MAPPED_SUBRESOURCE mappedData;
HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[0].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
memcpy_s(mappedData.pData, sizeof(VSConstantBuffer), &m_VSConstantBuffer, sizeof(VSConstantBuffer));
m_pd3dImmediateContext->Unmap(m_pConstantBuffers[0].Get(), 0); HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[1].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
memcpy_s(mappedData.pData, sizeof(PSConstantBuffer), &m_PSConstantBuffer, sizeof(PSConstantBuffer));
m_pd3dImmediateContext->Unmap(m_pConstantBuffers[1].Get(), 0); }

下面的程序按1、2、3分别对应切换平行光、点光、聚光,按Q、W、E、R分别对应切换立方体、球体、柱体和圆锥体,按S则切换面模式或线框模式:

练习题

粗体字为自定义题目

  1. 尝试修改本章Demo的光照,让方向光只射出红光,点光灯只射出绿光,聚光灯只射出蓝光。
  2. 尝试修改本章Demo所用到的材质,让其只反射红光。
  3. 尝试修改本章Demo所用到的聚光灯,通过鼠标滚轮的形式,对光照汇聚强度增加/减少,范围为2-512,观察效果。
  4. 尝试修改本章Demo所用到的材质,看看如果镜面反射强度的值小于1会发生什么情况。
  5. 尝试用HLSL内建函数lit来改写LightHelper.hlsli
  6. 实现一个函数用于创建胶囊(capsule)几何体,需要指定上下半球的半径(radius)、柱体部分高度(height)、球面三角形切片数(slices)和上下半球的层级数(levels),并且实现顶点要包含位置(position)、法向量(normal)和颜色信息(color)。

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

DirectX11 With Windows SDK--07 添加光照与常用几何模型、光栅化状态的更多相关文章

  1. DirectX11 With Windows SDK--07 添加光照与常用几何模型

    前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的光照模型.除此之外,该项目还用到了多个常量缓冲区,因此还会提及HLSL的常量缓冲区打包规则以及如何设置多 ...

  2. 粒子系统与雨的效果 (DirectX11 with Windows SDK)

    前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用W ...

  3. DirectX11 With Windows SDK--11 混合状态与光栅化状态

    前言 虽然这一部分的内容主要偏向于混合(Blending),但这里还需提及一下,关于渲染管线可以绑定的状态主要有如下四种: 光栅化状态(光栅化阶段) 采样器状态(像素着色阶段) 混合状态(输出合并阶段 ...

  4. DirectX11 With Windows SDK--00 目录

    前言 (更新于 2019/4/10) 从第一次接触DirectX 11到现在已经有将近两年的时间了.还记得前年暑假被要求学习DirectX 11,在用龙书的源码配置项目运行环境的时候都花了好几天的时间 ...

  5. DirectX11 With Windows SDK--25 法线贴图

    前言 在很早之前的纹理映射中,纹理存放的元素是像素的颜色,通过纹理坐标映射到目标像素以获取其颜色.但是我们的法向量依然只是定义在顶点上,对于三角形面内一点的法向量,也只是通过比较简单的插值法计算出相应 ...

  6. DirectX11 With Windows SDK--12 深度/模板状态、平面镜反射绘制

    前言 深度/模板测试使用的是与后备缓冲区同等分辨率大小的缓冲区,每个元素的一部分连续位用于深度测试,其余的则用作模板测试.两个测试的目的都是为了能够根据深度/模板状态需求的设置来选择需要绘制的像素. ...

  7. DirectX11 With Windows SDK--15 几何着色器初探

    前言 从这一部分开始,感觉就像是踏入了无人深空一样,在之前初学DX11的时候,这部分内容都是基本上跳过的,现在打算重新认真地把它给拾回来. DirectX11 With Windows SDK完整目录 ...

  8. DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果

    前言 上一章我们知道了如何使用几何着色器将顶点通过流输出阶段输出到绑定的顶点缓冲区.接下来我们继续利用它来实现一些新的效果,在这一章,你将了解: 实现公告板效果 Alpha-To-Coverage 对 ...

  9. DirectX11 With Windows SDK--20 硬件实例化与视锥体裁剪

    前言 这一章将了解如何在DirectX 11利用硬件实例化技术高效地绘制重复的物体,以及使用视锥体裁剪技术提前将位于视锥体外的物体进行排除. 在此之前需要额外了解的章节如下: 章节回顾 18 使用Di ...

随机推荐

  1. 解读Python中 locals() 和 globals() 内置函数

    首先globals() 和 locals() 是作用于作用域下的内置函数,所以我将它们分为作用域类型的内置函数 1.作用域相关: 1)globals() # 返回全局作用域中的所有名字 2)local ...

  2. Python学习之函数(多层函数)、re模块的正则匹配--计算复杂加减乘除

    头疼,其实这个程序在我看的视频当中是当做re模块的运用来进行测试的,而到了我这里就成了简化版的了,因为我实在是做吐了,恕小弟无能,只能做简化版的.为何说是简化版呢,因为要求是给的计算式是多层嵌套的小括 ...

  3. Vue. 之 Element dialog 拖拽

    Vue. 之 Element dialog 拖拽 默认情况下,在使用Element的Dialog模块时,弹出框是不能移动的,且 一旦点击遮罩层区域,弹框就会消失. 解决方案: 1 在 utils 中新 ...

  4. spring定时任务scheduler集群环境下指定运行服务器防止多服务器多次执行

    使用spring的@Scheduler注解可以非常方便的启动一个定时任务,但是当服务部署在多台服务器上做负载均衡的时候,可能会出现重复执行的情况. 现在我们通过代码指定job只在某一台机器执行. 首先 ...

  5. 统一建模语言简介UML

    统一建模语言(Unified Modeling Language,UML)是用来设计软件蓝图的可视化建模语言,1997 年被国际对象管理组织(OMG)采纳为面向对象的建模语言的国际标准.它的特点是简单 ...

  6. Luogu P1314 聪明的质监员(二分+前缀和)

    P1314 聪明的质监员 题意 题目描述 小\(T\)是一名质量监督员,最近负责检验一批矿产的质量.这批矿产共有\(n\)个矿石,从\(1\)到\(n\)逐一编号,每个矿石都有自己的重量\(w_i\) ...

  7. shell 第一篇

    1. 查看当前linux 支持的shell 类型 [root@nfs01 ~]# cat /etc/shells /bin/sh /bin/bash /sbin/nologin /bin/dash / ...

  8. switch...case...之替换方案一

    很多时候,当switch中有N个分支,且分支数已达10+,每个分支都是一个不小的方法体,那我们是不是应该考虑换一种方式来实现这个分支. 而我目前所能想到的是会用到如下几种方法. 1.Action 2. ...

  9. neo4j遍历和图算法

    阅读更多 这篇blog主要和大家分享一下neo4j中是如何对节点进行遍历,和其中集成的图论的一些常用算法. 遍历 http://docs.neo4j.org.cn/tutorials-java-emb ...

  10. day18 16.dbcp连接池使用介绍

    package cn.itcast.datasource; import java.io.FileInputStream; import java.sql.Connection; import jav ...