DX11 Without DirectX SDK--07 添加光照与常用几何模型
对于3D游戏来说,合理的光照可以让游戏显得更加真实。接下来会介绍光照的各种分量,以及常见的光照模型。除此之外,该项目还用到了多个固定缓冲区,因此还会提及HLSL的固定缓冲区打包规则以及如何设置多个固定缓冲区。
颜色向量
一个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
经过了矩阵变换得到了\(u’=uA\)时,对应变换后的法向量应为\(n’=n(A^{-1})^{T}\)
物体材质
光在照射到物体上时,由于物体的材质特性会反射一部分光到人眼,最终我们观察到的物体颜色就是被反射的那部分光的颜色。不同的物体有不同的材质属性,决定了各种颜色分量的反射系数是多少。其中红绿蓝每个分量的取值范围为[0.0f, 1.0f]
。
在C++中的结构体表示为:
// 物体表面材质
struct Material
{
Material() { memset(this, 0, sizeof(SpotLight)); }
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中,若环境光向量为la
,物体材质对环境光的反射向量为ma
,最终环境光分量呈现的颜色为:
la * ma
这里的乘号表示各个分量相乘,最终得到的向量为
(la.r * ma.r, la.g * ma.g, la.b * ma.b, la.a * ma.a)
漫反射光(Diffuse Lighting)
在现实生活中,我们看到的光以漫反射光为主。对于粗糙的表面,光照射在物体表面一点后反射的方向是不确定的。我们可以近似认为光线在照射到物体表面一点后会朝任意方向反射等量的光照,这样我们人眼无论在哪个方向观察该点,呈现的亮度应该是不会变化的(在没有镜面反射的基础)。但是物体的亮度与光线照射的方向有所关系,比如当均匀光线垂直照射物体的时候,此时看到的物体表面是最亮的;而均匀光线不经过物体表面,与表面平行的时候,物体的表面此时几乎是看不到的(此时可能仍有少量的光会到达物体表面,取决于光束的汇聚程度和与物体的距离)。毕竟光束不可能做到完全同一个方向照射,仍会有少数的散射光。
朗伯余弦定理
既然光照方向与亮度有关系,我们可以使用朗伯余弦定理来表示这种现象,用L
表示光反射后的单位方向向量,n
表示平面单位法向量,则可以在HLSL写成:
kd = max(dot(L, n), 0.0f)
若漫反射光向量为ld
,物体材质对环境光的反射向量为md
,最终漫反射光分量呈现的颜色为:
kd * ld * md
镜面反射光(Specular Lighting)
某些较为光滑的平面可以均匀地反射光照。镜面反射光与人眼所在位置有联系,若人站在反射光的路径上看反射点,可以看到此时的点是最亮的,然后随着人眼远离反射光线的路径,看到的镜面反射光会越来越少,直至只能看到漫反射光和环境光的部分。
在HLSL中,若R
为光反射后的单位方向向量,toEye
为物体表面一点到人眼的单位方向向量,p
为镜面系数(系数越大,表面越光滑,而且p的值必须大于等于1,否则会有很奇怪的效果),可以用下面的公式来表达镜面系数:
ks = pow(max(dot(R, toEye), 0.0f), p)
仅当dot(L, n) > 0
ks = 0
仅当dot(L, n) <= 0
若镜面反射光向量为ls
,物体材质对镜面光的反射向量为ms
,最终镜面反射光分量呈现的颜色为:
ks * ls * ms
HLSL固定缓冲区打包规则
首先要注意的是,C++中的结构体数据是以字节流传输给HLSL的
若C++结构体和HLSL固定缓冲区如下:
// cpp
struct S1
{
XMFLOAT3 p1;
XMFLOAT4 p2;
};
// HLSL
cbuffer C1
{
float4 v1;
float4 v2;
}
则最终C1
两个向量接收到的数据如下:(p1.x, p1.y, p1.z, p2.x)
(p2.y, p2.z, p2.w, empty)
其次,HLSL固定缓冲区中两个向量不允许拆分,若HLSL结构体如下:
// HLSL
cbuffer C2
{
float3 v1;
float4 v2;
};
p1
将被单独打包成一个4D向量,确保固定缓冲区的内存按128位对齐。
C2的内存布局为:
(v1.x, v1.y, v1.z, empty)
(v2.x, v2.y, v2.z, v2.w)
这时用S1结构体的数据再传输给C2
,结果如下:
(p1.x, p1.y, p1.z, p2.x)
(p2.y, p2.z, p2.w, empty)
然后,HLSL固定缓冲区多个相邻的变量如果可以,则优先打包进同一个4D向量中,如:
// HLSL
cbuffer C3
{
float2 v1;
float v2;
float3 v3;
}
C3的内存布局为:
(v1.x, v1.y, v2.x, empty)
(v3.x, v3.y, v3.z, empty)
打包顺序是从最上面的变量开始往下的。
对于在固定缓冲区的结构体,内部也会进行打包操作,如:
// HLSL
struct S2
{
XMFLOAT2 p1;
XMFLOAT3 p2;
XMFLOAT p3;
};
cbuffer C4
{
float v1;
S2 v2;
float3 v3;
}
C4的内存布局为:
(v1.x, empty, empty, empty)
(v2.p1.x, v2.p1.y, empty, empty)
(v2.p2.x, v2.p2.y, v2.p2.z, v2.p3.x)
(v3.x, v3.y, v3.z, empty)
对于在固定缓冲区的数组来说,每个元素都会被强制按16字节的倍数进行打包。对于:
float2 arr[16]
来说会造成一半空间的浪费,合理的做法是(在C++不能这样写):
// HLSL
cbuffer C5
{
float4 v1[8];
}
static float2 arr[16] = (float2[16])v1;
光照模型
平行光/方向光
平行光通常是一种全局光,它有一个固定的照射方向。
经平行光照射下物体表面一点的颜色可以初步表示为:
ambient = la * ma;
kd = max(dot(L, n), 0.0f);
if (kd > 0)
ks = pow(max(dot(R, toEye), 0.0f), p);
else
ks = 0.0f;
diffuse = kd * ld * md;
specular = ks * ls * ms;
litColor = ambient + diffuse + specular;
在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;
};
在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;
}
}
注意该函数的normal
和toEye
向量都为单位向量,除此之外输出了光的三种分量。
点光
点光是一种有源光照模型,确定光源位置后,它会朝所有方向辐射光照。
在HLSL,若已知光源位置向量Q
和照射点P
,则单位光向量为:
(Q - P) / length(Q - P)
光的衰弱
随着距离的增长,光源照射到物体表面更远一点时呈现的亮度更低。其中光的衰弱只对直接光照有效,对间接光(如环境光分量)没有影响。
若d
为光源到物体一点的距离,则有:
则经点光照射下物体表面一点的亮度可以初步表示为:
ambient = la * ma;
kd = max(dot(L, n), 0.0f);
if (kd > 0)
ks = pow(max(dot(R, toEye), 0.0f), p);
else
ks = 0.0f;
diffuse = kd * ld * md;
specular = ks * ls * ms;
att = 1.0f / (a0 + a1 * d + a2 * d * d);
litColor = ambient + (diffuse + specular) * att;
在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设置数组
};
// 点光
struct PointLight
{
float4 Ambient;
float4 Diffuse;
float4 Specular;
float3 Position;
float Range;
float3 Att;
};
在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
,则可以得到光照强度因子:
kspot = pow(max(dot(L, d), 0), spot)
通常spot
的值越大,光束的汇聚程度越强。
则经聚光灯照射下物体表面一点的亮度可以初步表示为:
ambient = la * ma;
kd = max(dot(L, n), 0.0f);
if (kd > 0)
ks = pow(max(dot(R, toEye), 0.0f), p);
else
ks = 0.0f;
diffuse = kd * ld * md;
specular = ks * ls * ms;
att = 1.0f / (a0 + a1 * d + a2 * d * d);
kspot = pow(max(dot(L, d), 0), spot);
litColor = kspot * (ambient + (diffuse + specular) * att);
在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.fx,用于生成顶点着色器和像素着色器
最后是Light.fx的代码:
// Light.fx
#include "LightHelper.hlsli"
cbuffer VSConstantBuffer : register(b0)
{
row_major matrix gWorld;
row_major matrix gView;
row_major matrix gProj;
row_major matrix gWorldInvTranspose;
}
cbuffer PSConstantBuffer : register(b1)
{
DirectionalLight gDirLight;
PointLight gPointLight;
SpotLight gSpotLight;
Material gMaterial;
float3 gEyePosW;
}
struct VertexIn
{
float3 Pos : POSITION;
float3 Normal : NORMAL;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION; // 在世界中的位置
float3 NormalW : NORMAL; // 法向量在世界中的方向
float4 Color : COLOR;
};
// 顶点着色器
VertexOut VS(VertexIn pIn)
{
VertexOut pOut;
row_major matrix worldViewProj = mul(mul(gWorld, gView), gProj);
pOut.PosH = mul(float4(pIn.Pos, 1.0f), worldViewProj);
pOut.PosW = mul(float4(pIn.Pos, 1.0f), gWorld).xyz;
pOut.NormalW = mul(pIn.Normal, (float3x3)gWorldInvTranspose);
pOut.Color = pIn.Color; // 这里alpha通道的值默认为1.0
return pOut;
}
// 像素着色器
float4 PS(VertexOut pIn) : SV_Target
{
// 标准化法向量
pIn.NormalW = normalize(pIn.NormalW);
// 顶点指向眼睛的向量
float3 toEyeW = normalize(gEyePosW - 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(gMaterial, gDirLight, pIn.NormalW, toEyeW, A, D, S);
ambient += A;
diffuse += D;
spec += S;
ComputePointLight(gMaterial, gPointLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
ambient += A;
diffuse += D;
spec += S;
ComputeSpotLight(gMaterial, gSpotLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
ambient += A;
diffuse += D;
spec += S;
float4 litColor = pIn.Color * (ambient + diffuse) + spec;
litColor.a = gMaterial.Diffuse.a * pIn.Color.a;
return litColor;
}
这里有两个常量缓冲区,一个用于顶点着色阶段,另一个用于像素着色阶段。
Geometry类–常用几何模型
该类包含了几个常用的3D模型:球、立方体和圆柱体。调用它可以生成模型数据以写入顶点和索引缓冲区。
#ifndef GEOMETRY_H_
#define GEOMETRY_H_
#include <Windows.h>
#include <DirectXMath.h>
#include <vector>
class Geometry
{
public:
struct MeshData
{
std::vector<DirectX::XMFLOAT3> posVec; // 位置数组
std::vector<DirectX::XMFLOAT3> normalVec; // 法向量数组
std::vector<WORD> indexVec; // 索引数组
};
// 创建球体模型数据,levels和slices越大,精度越高。
static MeshData CreateSphere(float radius = 1.0f, int levels = 20, int slices = 20);
// 创建立方体模型数据
static MeshData CreateBox(float width = 2.0f, float height = 2.0f, float depth = 2.0f);
// 创建圆柱体模型数据,slices越大,精度越高。
static MeshData CreateCylinder(float radius = 1.0f, float height = 2.0f, int slices = 20);
};
#endif
Geometry::CreateBox方法–创建立方体
该方法会创建包含24个顶点位置和24个单位法向量的两个数组,以及一个含36个索引的数组:
Geometry::MeshData Geometry::CreateBox(float width, float height, float depth)
{
MeshData meshData;
float w2 = width / 2, h2 = height / 2, d2 = depth / 2;
meshData.posVec.resize(24);
// 顶面
meshData.posVec[0] = XMFLOAT3(-w2, h2, -d2);
meshData.posVec[1] = XMFLOAT3(-w2, h2, d2);
meshData.posVec[2] = XMFLOAT3(w2, h2, d2);
meshData.posVec[3] = XMFLOAT3(w2, h2, -d2);
// 底面
meshData.posVec[4] = XMFLOAT3(w2, -h2, -d2);
meshData.posVec[5] = XMFLOAT3(w2, -h2, d2);
meshData.posVec[6] = XMFLOAT3(-w2, -h2, d2);
meshData.posVec[7] = XMFLOAT3(-w2, -h2, -d2);
// 左面
meshData.posVec[8] = XMFLOAT3(-w2, -h2, d2);
meshData.posVec[9] = XMFLOAT3(-w2, h2, d2);
meshData.posVec[10] = XMFLOAT3(-w2, h2, -d2);
meshData.posVec[11] = XMFLOAT3(-w2, -h2, -d2);
// 右面
meshData.posVec[12] = XMFLOAT3(w2, -h2, -d2);
meshData.posVec[13] = XMFLOAT3(w2, h2, -d2);
meshData.posVec[14] = XMFLOAT3(w2, h2, d2);
meshData.posVec[15] = XMFLOAT3(w2, -h2, d2);
// 背面
meshData.posVec[16] = XMFLOAT3(w2, -h2, d2);
meshData.posVec[17] = XMFLOAT3(w2, h2, d2);
meshData.posVec[18] = XMFLOAT3(-w2, h2, d2);
meshData.posVec[19] = XMFLOAT3(-w2, -h2, d2);
// 正面
meshData.posVec[20] = XMFLOAT3(-w2, -h2, -d2);
meshData.posVec[21] = XMFLOAT3(-w2, h2, -d2);
meshData.posVec[22] = XMFLOAT3(w2, h2, -d2);
meshData.posVec[23] = XMFLOAT3(w2, -h2, -d2);
meshData.normalVec.resize(24);
for (int i = 0; i < 4; ++i)
{
meshData.normalVec[i] = XMFLOAT3(0.0f, 1.0f, 0.0f); // 顶面
meshData.normalVec[i + 4] = XMFLOAT3(0.0f, -1.0f, 0.0f); // 底面
meshData.normalVec[i + 8] = XMFLOAT3(-1.0f, 0.0f, 0.0f); // 左面
meshData.normalVec[i + 12] = XMFLOAT3(1.0f, 0.0f, 0.0f); // 右面
meshData.normalVec[i + 16] = XMFLOAT3(0.0f, 0.0f, 1.0f); // 背面
meshData.normalVec[i + 20] = XMFLOAT3(0.0f, 0.0f, -1.0f); // 正面
}
meshData.indexVec = {
0, 1, 2, 2, 3, 0, // 顶面
4, 5, 6, 6, 7, 4, // 底面
8, 9, 10, 10, 11, 8, // 左面
12, 13, 14, 14, 15, 12, // 右面
16, 17, 18, 18, 19, 16, // 背面
20, 21, 22, 22, 23, 20 // 正面
};
return meshData;
}
Geometry::CreateSphere方法–创建球体
由于3D模型都是用三角形模拟的,这里的球体如果想要效果更佳逼真,需要用到更多的三角形。球体的法向量如前面所述,使用微分法求出。在提供参数的时候,levels
决定上下分多少层,slices
决定一个圆分成多少份。levels
和slices
越高,生成的顶点数、索引数都会越多:
Geometry::MeshData Geometry::CreateSphere(float radius, int levels, int slices)
{
MeshData meshData;
float phi = 0.0f, theta = 0.0f;
float per_phi = XM_PI / levels;
float per_theta = XM_2PI / slices;
float x, y, z;
// 放入顶端点
meshData.posVec.push_back(XMFLOAT3(0.0f, radius, 0.0f));
meshData.normalVec.push_back(XMFLOAT3(0.0f, 1.0f, 0.0f));
for (int i = 1; i < levels; ++i)
{
phi = per_phi * i;
for (int 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);
// 计算出局部坐标和法向量
XMFLOAT3 pos = XMFLOAT3(x, y, z), normal;
meshData.posVec.push_back(pos);
XMStoreFloat3(&normal, XMVector3Normalize(XMLoadFloat3(&pos)));
meshData.normalVec.push_back(pos);
}
}
// 放入底端点
meshData.posVec.push_back(XMFLOAT3(0.0f, -radius, 0.0f));
meshData.normalVec.push_back(XMFLOAT3(0.0f, -1.0f, 0.0f));
// 逐渐放入索引
if (levels > 1)
{
for (int j = 1; j <= slices; ++j)
{
meshData.indexVec.push_back(0);
meshData.indexVec.push_back(j % slices + 1);
meshData.indexVec.push_back(j);
}
}
for (int i = 1; i < levels - 1; ++i)
{
for (int j = 1; j <= slices; ++j)
{
meshData.indexVec.push_back((i - 1) * slices + j);
meshData.indexVec.push_back((i - 1) * slices + j % slices + 1);
meshData.indexVec.push_back(i * slices + j % slices + 1);
meshData.indexVec.push_back(i * slices + j % slices + 1);
meshData.indexVec.push_back(i * slices + j);
meshData.indexVec.push_back((i - 1) * slices + j);
}
}
// 逐渐放入索引
if (levels > 1)
{
for (int j = 1; j <= slices; ++j)
{
meshData.indexVec.push_back((levels - 2) * slices + j);
meshData.indexVec.push_back((levels - 2) * slices + j % slices + 1);
meshData.indexVec.push_back((WORD)(meshData.posVec.size() - 1));
}
}
return meshData;
}
Geometry::CreateCylinder方法–创建圆柱体
将圆柱体划分为上下圆面和侧边部分来处理,实现如下:
Geometry::MeshData Geometry::CreateCylinder(float radius, float height, int slices)
{
MeshData meshData;
float h2 = height / 2;
float theta = 0.0f;
float per_theta = XM_2PI / slices;
// 放入顶端圆心
meshData.posVec.push_back(XMFLOAT3(0.0f, h2, 0.0f));
meshData.normalVec.push_back(XMFLOAT3(0.0f, 1.0f, 0.0f));
// 放入顶端圆上各点
for (int i = 0; i < slices; ++i)
{
theta = i * per_theta;
meshData.posVec.push_back(XMFLOAT3(cosf(theta), h2, sinf(theta)));
meshData.normalVec.push_back(XMFLOAT3(0.0f, 1.0f, 0.0f));
}
// 逐渐放入索引
for (int i = 1; i <= slices; ++i)
{
meshData.indexVec.push_back(0);
meshData.indexVec.push_back(i % slices + 1);
meshData.indexVec.push_back(i);
}
// 放入侧面顶端点
for (int i = 0; i < slices; ++i)
{
theta = i * per_theta;
meshData.posVec.push_back(XMFLOAT3(cosf(theta), h2, sinf(theta)));
meshData.normalVec.push_back(XMFLOAT3(cosf(theta), 0.0f, sinf(theta)));
}
// 放入侧面底端点
for (int i = 0; i < slices; ++i)
{
theta = i * per_theta;
meshData.posVec.push_back(XMFLOAT3(cosf(theta), -h2, sinf(theta)));
meshData.normalVec.push_back(XMFLOAT3(cosf(theta), 0.0f, sinf(theta)));
}
// 放入索引
for (int i = 1; i <= slices; ++i)
{
meshData.indexVec.push_back(slices + i);
meshData.indexVec.push_back(slices + i % slices + 1);
meshData.indexVec.push_back(2 * slices + i % slices + 1);
meshData.indexVec.push_back(2 * slices + i % slices + 1);
meshData.indexVec.push_back(2 * slices + i);
meshData.indexVec.push_back(slices + i);
}
// 放入底部圆上各点
for (int i = 0; i < slices; ++i)
{
theta = i * per_theta;
meshData.posVec.push_back(XMFLOAT3(cosf(theta), -h2, sinf(theta)));
meshData.normalVec.push_back(XMFLOAT3(cosf(theta), 0.0f, sinf(theta)));
}
// 放入底端圆心
meshData.posVec.push_back(XMFLOAT3(0.0f, -h2, 0.0f));
meshData.normalVec.push_back(XMFLOAT3(0.0f, -1.0f, 0.0f));
// 逐渐放入索引
WORD numPos = (WORD)meshData.posVec.size() - 1;
for (int i = 1; i <= slices; ++i)
{
meshData.indexVec.push_back(numPos);
meshData.indexVec.push_back(3 * slices + i);
meshData.indexVec.push_back(3 * slices + i % slices + 1);
}
return meshData;
}
GameApp类
GameApp
类的变化如下:
#ifndef GAMEAPP_H
#define GAMEAPP_H
#include <d3dcompiler.h>
#include <directxmath.h>
#include <DirectXColors.h>
#include "d3dApp.h"
#include "LightHelper.h"
#include "Geometry.h"
class GameApp : public D3DApp
{
public:
struct VertexPosNormalColor
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT4 color;
static const D3D11_INPUT_ELEMENT_DESC inputLayout[3];
};
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();
// 从.fx/.hlsl文件中编译着色器
HRESULT CompileShaderFromFile(const WCHAR* szFileName, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut);
private:
bool InitEffect();
bool InitResource();
bool ResetMesh(const Geometry::MeshData& meshData);
private:
Microsoft::WRL::ComPtr<ID3D11InputLayout> mVertexLayout; // 顶点输入布局
Microsoft::WRL::ComPtr<ID3D11Buffer> mVertexBuffer; // 顶点缓冲区
Microsoft::WRL::ComPtr<ID3D11Buffer> mIndexBuffer; // 索引缓冲区
Microsoft::WRL::ComPtr<ID3D11Buffer> mConstantBuffers[2]; // 常量缓冲区
Microsoft::WRL::ComPtr<ID3D11VertexShader> mVertexShader; // 顶点着色器
Microsoft::WRL::ComPtr<ID3D11PixelShader> mPixelShader; // 像素着色器
VSConstantBuffer mVSConstantBuffer; // 用于修改用于VS的GPU常量缓冲区的变量
PSConstantBuffer mPSConstantBuffer; // 用于修改用于PS的GPU常量缓冲区的变量
DirectionalLight mDirLight; // 默认环境光
PointLight mPointLight; // 默认点光
SpotLight mSpotLight; // 默认汇聚光
int mIndexCount; // 绘制物体的索引数组大小
};
#endif
其中,LightHelper.h包含了前面描述的材质和三种光照模型的结构体。
GameApp
类中则新添加了几个结构体和对应的一些成员。
GameApp::ResetMesh方法–重新设置要使用的模型
该项目为了演示不同3D模型下的光照效果,创建了该方法用于重新设置要使用的模型,但在这里物体表面的颜色默认都被设置为白色。实现如下:
bool GameApp::ResetMesh(const Geometry::MeshData & meshData)
{
// 释放旧资源
mVertexBuffer.Reset();
mIndexBuffer.Reset();
int vertexSize = meshData.posVec.size();
std::vector<VertexPosNormalColor> vertices(vertexSize);
for (int i = 0; i < vertexSize; ++i)
{
vertices[i].pos = meshData.posVec[i];
vertices[i].normal = meshData.normalVec[i];
vertices[i].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
}
// 设置顶点缓冲区描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_DEFAULT;
vbd.ByteWidth = vertices.size() * sizeof(VertexPosNormalColor);
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// 新建顶点缓冲区
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices.data();
HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.GetAddressOf()));
// 输入装配阶段的顶点缓冲区设置
UINT stride = sizeof(VertexPosNormalColor); // 跨越字节数
UINT offset = 0; // 起始偏移量
md3dImmediateContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), &stride, &offset);
// 设置索引缓冲区描述
mIndexCount = meshData.indexVec.size();
D3D11_BUFFER_DESC ibd;
ZeroMemory(&ibd, sizeof(ibd));
ibd.Usage = D3D11_USAGE_DEFAULT;
ibd.ByteWidth = sizeof(WORD) * mIndexCount;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
// 新建索引缓冲区
InitData.pSysMem = meshData.indexVec.data();
HR(md3dDevice->CreateBuffer(&ibd, &InitData, mIndexBuffer.GetAddressOf()));
// 输入装配阶段的索引缓冲区设置
md3dImmediateContext->IASetIndexBuffer(mIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
return true;
}
GameApp::InitResource方法的变化
该方法初始化模型后,就会初始化常量缓冲区的数据,以及一些光照模型和物体材质:
bool GameApp::InitResource()
{
// 初始化网格模型
Geometry::MeshData meshData = Geometry::CreateBox();
ResetMesh(meshData);
// ******************
// 设置常量缓冲区描述
D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DEFAULT;
cbd.ByteWidth = sizeof(VSConstantBuffer);
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = 0;
// 新建用于VS常量缓冲区,不使用初始数据
HR(md3dDevice->CreateBuffer(&cbd, nullptr, mConstantBuffers[0].GetAddressOf()));
cbd.ByteWidth = sizeof(PSConstantBuffer);
HR(md3dDevice->CreateBuffer(&cbd, nullptr, mConstantBuffers[1].GetAddressOf()));
// 初始化默认光照
// 方向光
mDirLight.Ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
mDirLight.Diffuse = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
mDirLight.Specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
mDirLight.Direction = XMFLOAT3(-0.577f, -0.577f, 0.577f);
// 点光
mPointLight.Position = XMFLOAT3(0.0f, 0.0f, -10.0f);
mPointLight.Ambient = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f);
mPointLight.Diffuse = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);
mPointLight.Specular = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);
mPointLight.Att = XMFLOAT3(0.0f, 0.1f, 0.0f);
mPointLight.Range = 25.0f;
// 聚光灯
mSpotLight.Position = XMFLOAT3(0.0f, 0.0f, -5.0f);
mSpotLight.Direction = XMFLOAT3(0.0f, 0.0f, 1.0f);
mSpotLight.Ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
mSpotLight.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
mSpotLight.Specular = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
mSpotLight.Att = XMFLOAT3(1.0f, 0.0f, 0.0f);
mSpotLight.Spot = 12.0f;
mSpotLight.Range = 10000.0f;
// 初始化用于VS的常量缓冲区的值
mVSConstantBuffer.world = XMMatrixIdentity();
mVSConstantBuffer.view = 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)
);
mVSConstantBuffer.proj = XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f);
mVSConstantBuffer.worldInvTranspose = XMMatrixIdentity();
// 初始化用于PS的常量缓冲区的值
mPSConstantBuffer.material.Ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
mPSConstantBuffer.material.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
mPSConstantBuffer.material.Specular = XMFLOAT4(1.0f, 1.0f, 1.0f, 50.0f);
// 使用默认平行光
mPSConstantBuffer.dirLight = mDirLight;
// 更新PS常量缓冲区资源
md3dImmediateContext->UpdateSubresource(mConstantBuffers[1].Get(), 0, nullptr, &mPSConstantBuffer, 0, 0);
// 设置图元类型
md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
return true;
}
GameApp::UpdateScene方法的变化
该方法增加了键盘操作,按1,2或3分别对应切换为平行光,点光或聚光灯;按q,w或e分别对应切换为立方体、球体和圆柱体:
void GameApp::UpdateScene(float dt)
{
// 更新常量缓冲区,让立方体转起来
static float phi = 0.0f, theta = 0.0f;
phi += 0.00003f, theta += 0.00005f;
mVSConstantBuffer.world = XMMatrixRotationX(phi) * XMMatrixRotationY(theta);
mVSConstantBuffer.worldInvTranspose = XMMatrixTranspose(XMMatrixInverse(nullptr, mVSConstantBuffer.world));
md3dImmediateContext->UpdateSubresource(mConstantBuffers[0].Get(), 0, nullptr, &mVSConstantBuffer, 0, 0);
// 键盘切换灯光类型
Keyboard::State state = mKeyboard->GetState();
mKeyboardTracker.Update(state);
if (mKeyboardTracker.IsKeyPressed(Keyboard::D1))
{
mPSConstantBuffer.dirLight = mDirLight;
mPSConstantBuffer.pointLight = PointLight();
mPSConstantBuffer.spotLight = SpotLight();
md3dImmediateContext->UpdateSubresource(mConstantBuffers[1].Get(), 0, nullptr, &mPSConstantBuffer, 0, 0);
}
else if (mKeyboardTracker.IsKeyPressed(Keyboard::D2))
{
mPSConstantBuffer.dirLight = DirectionalLight();
mPSConstantBuffer.pointLight = mPointLight;
mPSConstantBuffer.spotLight = SpotLight();
md3dImmediateContext->UpdateSubresource(mConstantBuffers[1].Get(), 0, nullptr, &mPSConstantBuffer, 0, 0);
}
else if (mKeyboardTracker.IsKeyPressed(Keyboard::D3))
{
mPSConstantBuffer.dirLight = DirectionalLight();
mPSConstantBuffer.pointLight = PointLight();
mPSConstantBuffer.spotLight = mSpotLight;
md3dImmediateContext->UpdateSubresource(mConstantBuffers[1].Get(), 0, nullptr, &mPSConstantBuffer, 0, 0);
}
// 键盘切换模型类型
if (mKeyboardTracker.IsKeyPressed(Keyboard::Q))
{
Geometry::MeshData meshData = Geometry::CreateBox();
ResetMesh(meshData);
}
else if (mKeyboardTracker.IsKeyPressed(Keyboard::W))
{
Geometry::MeshData meshData = Geometry::CreateSphere();
ResetMesh(meshData);
}
else if (mKeyboardTracker.IsKeyPressed(Keyboard::E))
{
Geometry::MeshData meshData = Geometry::CreateCylinder();
ResetMesh(meshData);
}
}
GameApp::DrawScene方法的变化
在这里尤其要注意设置着色器设置常量缓冲区方法的第一个参数:
void GameApp::DrawScene()
{
assert(md3dImmediateContext);
assert(mSwapChain);
md3dImmediateContext->ClearRenderTargetView(mRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black));
md3dImmediateContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
// VS常量缓冲区对应HLSL寄存于b0的常量缓冲区
md3dImmediateContext->VSSetConstantBuffers(0, 1, mConstantBuffers[0].GetAddressOf());
// PS常量缓冲区对应HLSL寄存于b1的常量缓冲区
md3dImmediateContext->PSSetConstantBuffers(1, 1, mConstantBuffers[1].GetAddressOf());
// 绘制几何模型
md3dImmediateContext->DrawIndexed(mIndexCount, 0, 0);
HR(mSwapChain->Present(0, 0));
}
最终效果如下: