此文章由机器翻译。
DirectX 因素
像素着色器和光线反射
如果你能看到光子......好吧,你可以看到光子,或至少一些人。光子是弥补电磁波辐射的粒子,并且眼睛是敏感的光子波长在可见光范围内。
但你不能看到光子作为他们飞所有的地方。那一定会很有趣。有时光子穿过对象 ; 有时他们吸收 ; 有时他们被反映 ; 但通常这是一个组合的所有的这些影响。有些最终从物体反弹回来的光子到达你的眼睛,让每一个对象,其特定的颜色和纹理。
为极高品质的 3D 图形,一种叫做射线追踪技术可以实际绘制出模拟这些无数的光子,模仿的反射和阴影效果的路径。但很多简单的技术用于更为传统的需求。这往往是案件时使用 Direct3D — — 或者,对我来说,写作在 Direct2D 利用 3D 的自定义效果。
重用的效果
如您所见,在本专栏的前面几篇文章中,一个 Direct2D 效果基本上是在 GPU 运行的代码的包装。这样的代码被称为一个着色器,和最重要的是,顶点着色器和像素着色器。在每个这些着色器代码称为显示视频刷新率。顶点着色器被呼吁每个人在每个三角形三个顶点,弥补显示受影响,虽然像素着色器被呼吁在这些三角形内的每个像素的图形对象。
很明显,像素着色器叫做频率远高于顶点着色器,所以它有道理继续尽可能多尽可能处理顶点着色器,而不是像素着色器。不过,这并非总是可能的和当使用这些着色器来模拟光的反射,它通常是平衡和这些治理复杂的两种着色器和灵活性的明暗变化之间的相互作用。
在 8 月期的这本杂志,我提出了称为 RotatingTriangleEffect 的构造顶点缓冲区组成的点和颜色,并允许将标准模型和相机转换应用到顶点的 Direct2D 效应。我用这种效应来转动三个三角形。那不是大量的数据。只共九个顶点,涉及三个三角形,我就提到过,相同的效果,可以用于很多较大的顶点缓冲区。
让我们试试看:可下载程序 (msdn.microsoft.com/magazine/msdnmag1014) 为该栏目叫做 ShadedCircularText,它使用 RotatingTriangleEffect 没有一个简单的改变。
ShadedCircularText 程序返回到这一问题开始了探索今年早些时候的显示在三个维度的棋盘格 2D 文本。ShadedCircularTextRenderer 类的构造函数在字体文件中加载,它,从创建字体,然后调用 GetGlyphRunOutline 来获取字符轮廓路径几何图形。该路径几何图形是然后镶嵌使用一个称为积累实际三角形的 InterrogableTessellationSink 我创建的类。
后注册 RotatingTriangleEffect ShadedCircularText渲染器创建一个 ID2D1Effect 对象,基于这种效应。它然后将三角形的棋盘格的文本转换成一个球体,基本上文字环绕赤道和两极弯曲表面的顶点。每个顶点的颜色基于一种色调来自原始文本几何的 X 坐标。这将创建一个彩虹般的效果,和图 1 显示的结果。
图 1 三维文字彩虹从 ShadedCircularText
正如你所看到的一个小小的菜单装饰顶部。该计划实际上包括了实施更为传统的光照模型的三个额外的 Direct2D 效果。他们都使用相同的点,采用同一种转换和相同的动画,所以你可以让他们看到差异之间进行切换。差异涉及只有三角形的颜色底纹。
右下角将显示性能在帧每秒,但你会发现什么在这个程序中导致放弃远低于 60 除了如果别的事情正在进行。
高氏着色
光子飞在我们周围,就像他们时常跳掉空气中的氮气和氧气分子。即使在没有直射阴暗的一天,还有大量的光线。环境光线往往非常均匀的方式照亮对象。
也许你有一个对象,是蓝绿色,与 RGB 值 (0、 0.5、 1.0)。如果光线是四分之一的最大亮度白色,可以将一个 RGB 值分配给 (0.25、 0.25、 0.25) 光。此对象的感知的颜色是红色、 绿色和蓝色成分的这些数字,或 (0、 0.125、 0.25) 产品。它仍然是蓝绿色,但要暗许多。
但是简单的 3D 场景不住周围的光一个人。在现实生活中,对象通常有很多颜色的变化在其表面上,因此,即使如果他们均匀照明,对象仍然有可见的纹理。但是在简单的 3D 场景中,只有被光线照亮一个蓝绿色对象将只是看起来像均匀颜色未分化板。
为此,简单的 3D 场景极大地得益于一些定向光。这是最简单的方法假设这光来自遥远的距离 (如太阳),所以光的方向是只适用于整个场景的单个矢量。如果只有一个光源,通常它被假定来自观众的左肩,也许该载体是右手坐标系 (1、-1,-1)。这个方向性的光源也具有一种颜色,也许 (0.75、 0.75、 0.75),所以有时与周围的光线 (0.25、 0.25、 0.25),实现最大的照明是。
定向表面反射的光的数量取决于光与表面的角度。(这是一个概念,探索我 2014 年 5 月 DirectX 因素列。)定向光是垂直于表面,并反射的光减少到零,当光线是从哪儿冒出来的表面或到来相切时发生最大的反射表面上的。
Lambert 余弦法 — — 德国数学家和物理学家约翰 · 海因里希 Lambert (1728年 — — 1777 年) 的名字命名 — — 说,从表面的反射光的分数是光的方向和垂直于表面,被称作表面正常的向量的方向之间的角度的负面余弦值。如果这两个向量归一化的处理 — — 那就是,如果他们有震级为 1 — — 这两个向量之间的夹角的余弦的值是相同的向量的点积。
例如,如果光线呈 45 度角的特定曲面,余弦值大约是 0.7,所以这个数字乘以方向传统光 0.75 0.75 0.75) 的颜色和颜色的对象 (0、 0.5、 1.0) 从定向光 (0、 0.26、 0.53) 派生对象的颜色。从光线向颜色添加的。
然而,请记住那弯弯的 3D 场景中的对象不被弯曲的其实。在场景中的一切都由平坦三角形连接。如果每个三角形的照明基于表面的正常垂直于三角形本身,每个三角形将有不同的色泽均匀。这是精细的柏拉图式的固体,如那些我 2014 年 5 月的专栏中显示,但不是那么好了曲面。曲面,你想要的颜色的三角形与彼此融合。
这意味着有必要为每个三角形有渐变的色,而不是统一的颜色。从方向性光源的颜色不能基于单一表面正常的三角形。相反,每个顶点的三角形应该有不同的颜色基于曲面法向该顶点处。这些顶点颜色然后可以在三角形的所有像素内插。相邻三角形然后混在一起彼此类似于一个曲面。
这种类型的底纹被发明的法国计算机科学家 Henri 高氏 (b。 1944 年) 在一篇论文发表在 1971 年,因此被称为高氏着色。
高氏着色是在 ShadedCircularText 程序中实现第二个选项。这个效应本身被称为 GouraudShadingEffect,并且它需要顶点缓冲区和某种程度上更多的数据:
struct PositionNormalColorVertex
{
DirectX::XMFLOAT3 position;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT3 color;
DirectX::XMFLOAT3 backColor;
};
有趣的是,因为是有效的文本被缠绕在一个球体中心在点 (0,0,0),每个顶点处的曲面法是相同的位置,但归一化有大小是 1。 影响允许特有的色彩,对于每个顶点,但在这个程序中每个顶点获取相同的颜色,是 (0、 0.5、 1) 和 (0.5,0.5,0.5) 的相同背景色的颜色用于如果后面的一个面朝观众。
GouraudShadingEffect 还需要更多的效果属性。它必须能够设置环境光色、 定向光颜色和方向的光的矢量方向。GouraudShadingEffect 为顶点着色器将所有这些值转移到较大的恒定缓冲区。顶点着色器本身所示图 2。
图 2 的顶点着色器高氏着色
// Per-vertex data input to the vertex shader
struct VertexShaderInput
{
float3 position : MESH_POSITION;
float3 normal : NORMAL;
float3 color : COLOR0;
float3 backColor : COLOR1;
};
// Per-vertex data output from the vertex shader
struct VertexShaderOutput
{
float4 clipSpaceOutput : SV_POSITION;
float4 sceneSpaceOutput : SCENE_POSITION;
float3 color : COLOR0;
};
// Constant buffer provided by effect.
cbuffer VertexShaderConstantBuffer : register(b1)
{
float4x4 modelMatrix;
float4x4 viewMatrix;
float4x4 projectionMatrix;
float4 ambientLight;
float4 directionalLight;
float4 lightDirection;
};
// Called for each vertex.
VertexShaderOutput main(VertexShaderInput input)
{
// Output structure
VertexShaderOutput output;
// Get the input vertex, and include a W coordinate
float4 pos = float4(input.position.xyz, 1.0f);
// Pass through the resultant scene space output value
// (not necessary -- can be removed from both shaders)
output.sceneSpaceOutput = pos;
// Apply transforms to that vertex
pos = mul(pos, modelMatrix);
pos = mul(pos, viewMatrix);
pos = mul(pos, projectionMatrix);
// The result is clip space output
output.clipSpaceOutput = pos;
// Apply model transform to normal
float4 normal = float4(input.normal, 0);
normal = mul(normal, modelMatrix);
// Find angle between light and normal
float3 lightDir = normalize(lightDirection.xyz);
float cosine = -dot(normal.xyz, lightDir);
cosine = max(cosine, 0);
// Apply view transform to normal
normal = mul(normal, viewMatrix);
// Check if normal pointing at viewer
if (normal.z > 0)
{
output.color = (ambientLight.xyz + cosine *
directionalLight.xyz) * input.color;
}
else
{
output.color = input.backColor;
}
return output;
}
像素着色器是 RotatingTriangleEffect,相同,所示图 3。幕后顶点着色器和像素着色器,之间的插值在整个三角形的顶点颜色显示所以像素着色器只是将传递到的颜色。
图 3 的像素着色器高氏着色
// Per-pixel data input to the pixel shader
struct PixelShaderInput
{
float4 clipSpaceOutput : SV_POSITION;
float4 sceneSpaceOutput : SCENE_POSITION;
float3 color : COLOR0;
};
// Called for each pixel
float4 main(PixelShaderInput input) : SV_TARGET
{
// Simply return color with opacity of 1
return float4(input.color, 1);
}
结果显示在图 4,这个时候就 Windows Phone 8.1,而不是 Windows 8.1。ShadedCircularText 解决方案用新的通用应用程序模板在 Visual Studio 中创建,并可以编译两个平台。除了应用程序和 DirectXPage 类的两个平台之间共享所有代码。这两个程序的布局的差异表明为什么有不同的页面定义的往往是个好主意,即使该程序的功能基本上是一样的。
图 4 显示的高氏着色模型
正如你所看到的这个数字是打火机在其左上角区域中,清楚地显示定向光源的作用和协助的表面的圆形外观的幻觉中。
海防改进
高氏着色是一个历史悠久的技术,但它有一个基本的缺陷:在高氏着色的定向光反射在三角形的中心量光被反射在顶点插值后的值。在顶点反射的光基于光的方向和这些顶点处的表面法线之间的夹角的余弦值。
但真的应该在这一点在三角形的中心的反射光基于表面正常处理。换句话说,颜色不应该插在三角形 ; 相反,表面法线应插值的三角形表面和反射的光计算基于正常的每个像素。
进入越南出生的计算机科学家培祥丰 (1942年-1975),在 32 岁时死于白血病。 在他 1973年的博士论文,海防描述稍有不同的着色算法。而不是插在三角形的顶点颜色,顶点的法向量的三角形上,样条插值,然后从那些计算反射的光。
在实际意义上,Phong 光照效果需要反射光线,并将从顶点着色器移动到像素着色器,以及致力于这项工作的不断缓冲区的部分的计算。这增加的每像素极大,加工量,但幸运的是,它正在做基于 GPU 在哪里,你希望它似乎不会产生多大的差异。
顶点着色器 Phong 着色模型所示图 5。一些输入的数据 — — 例如颜色和背景色 — — 简单地转嫁像素着色器。但它仍然很有用,适用所有变换。世界变换和两个摄像机转换必须应用到的位置,同时也计算了两个法线 — — 一只模型变换为反射的光,和另一个与视图变换来确定是否一个表面面临朝向或远离查看者。
图 5 的顶点着色器 Phong 着色模型
// Per-vertex data input to the vertex shader
struct VertexShaderInput
{
float3 position : MESH_POSITION;
float3 normal : NORMAL;
float3 color : COLOR0;
float3 backColor : COLOR1;
};
// Per-vertex data output from the vertex shader
struct VertexShaderOutput
{
float4 clipSpaceOutput : SV_POSITION;
float4 sceneSpaceOutput : SCENE_POSITION;
float3 normalModel : NORMAL0;
float3 normalView : NORMAL1;
float3 color : COLOR0;
float3 backColor : COLOR1;
};
// Constant buffer provided by effect
cbuffer VertexShaderConstantBuffer : register(b1)
{
float4x4 modelMatrix;
float4x4 viewMatrix;
float4x4 projectionMatrix;
};
// Called for each vertex
VertexShaderOutput main(VertexShaderInput input)
{
// Output structure
VertexShaderOutput output;
// Get the input vertex, and include a W coordinate
float4 pos = float4(input.position.xyz, 1.0f);
// Pass through the resultant scene space output value
// (not necessary — can be removed from both shaders)
output.sceneSpaceOutput = pos;
// Apply transforms to that vertex
pos = mul(pos, modelMatrix);
pos = mul(pos, viewMatrix);
pos = mul(pos, projectionMatrix);
// The result is clip space output
output.clipSpaceOutput = pos;
// Apply model transform to normal
float4 normal = float4(input.normal, 0);
normal = mul(normal, modelMatrix);
output.normalModel = normal.xyz;
// Apply view transform to normal
normal = mul(normal, viewMatrix);
output.normalView = normal.xyz;
// Transfer colors
output.color = input.color;
output.backColor = input.backColor;
return output;
}
顶点着色器的输出成为投入的像素着色器,这些法线被插值的三角形表面上。像素着色器可以然后完成这项工作通过计算反射的光,如中所示图 6。
图 6 的像素着色器 Phong 着色模型
// Per-pixel data input to the pixel shader
struct PixelShaderInput
{
float4 clipSpaceOutput : SV_POSITION;
float4 sceneSpaceOutput : SCENE_POSITION;
float3 normalModel : NORMAL0;
float3 normalView : NORMAL1;
float3 color : COLOR0;
float3 backColor : COLOR1;
};
// Constant buffer provided by effect
cbuffer PixelShaderConstantBuffer : register(b0)
{
float4 ambientLight;
float4 directionalLight;
float4 lightDirection;
};
// Called for each pixel
float4 main(PixelShaderInput input) : SV_TARGET
{
// Find angle between light and normal
float3 lightDir = normalize(lightDirection.xyz);
float cosine = -dot(input.normalModel, lightDir);
cosine = max(cosine, 0);
float3 color;
// Check if normal pointing at viewer
if (input.normalView.z > 0)
{
color = (ambientLight.xyz + cosine *
directionalLight.xyz) * input.color;
}
else
{
color = input.backColor;
}
// Return color with opacity of 1
return float4(color, 1);
}
然而,我不会告诉你结果的截图。它是非常视觉上相同的高氏着色。高氏着色真的是一个好的近似。
镜面高光
真正 Phong 光照效果的重要性是它使成为可能依赖于更准确的曲面法线的其他功能。
到目前为止在这篇文章中,您看到了是适当的漫反射表面的底纹。这些都是的表面粗糙而枯燥,那往往将它们的表面反射的光线散射到。
是稍有光泽的表面反射光的稍有不同。如果倾斜的表面只是如此,定向光可以反弹,直接去找观众的眼睛。这通常被视为明亮白色的光,和它被称为镜面高光。你可以看到在较夸张的效果图 7。如果这一数字有更清晰的曲线,白色的光会更本地化。
图 7 镜面高光显示
得到这种效应似乎在第一,它可能是复杂的计算,但就只有几行中的像素着色器的代码。这特别的技术,由美国国家航空航天局图形 maven 吉姆 Blinn (b。 1949 年)。
我们首先需要指示 3D 场景的观众看的方向向量。这是很容易的因为视图镜头转换已调整所有的坐标,因此观众正在沿着 Z 轴:
float3 viewVector = float3(0, 0, -1);
下一步,计算一个向量,介于该视图矢量和光线的方向:
float3 halfway = -normalize(viewVector + lightDirection.xyz);
请注意负号。这使得向量指向相反的方向 — — 光源和观众之间的中途。
如果一个特定的三角形包含曲面的法线,完全符合这一半的矢量,这意味着光线反射表面直接进入了观众的眼球。这会导致最大镜面高光。
小雅突出显示结果非零半路矢量和曲面法线之间的夹角。这是另一个应用程序之间两个向量,如果两个向量归一化的处理是点积相同的余弦值为:
float dotProduct = max(0.0f, dot(input.normalView, halfway));
此值从 1 为最大的镜面高光,当两个向量之间的角度为 0,0 表示没有镜面高光,到 dotProduct 范围时发生两个向量是垂直的。
然而,镜面高光不应该可见的所有角度介于 0 和 90 度之间。它应本地化。它应该只存在于那些两个向量之间的极小角度。你需要一个函数,它不会影响到 1,点产品,但会导致值小于 1,成为低得多。这是战俘函数:
float specularLuminance = pow(dotProduct, 20);
这个战俘函数点积 20 掌权。如果的点积是 1,则战俘函数返回 1。 如果的点积是 0.7 (导致从两个向量之间的夹角为 45 度),则该战俘函数返回 0.0008,这实际上是 0 到照明去。使用较高的指数值,使效果更本地化。
现在都是必要的就是这一因素乘以定向光色和将它添加到的颜色已经计算从环境光线和定向光:
color += specularLuminance * directionalLight.xyz;
作为动画变成图创建白光扑通一的声。
告别
,DirectX 因素列接近尾声。这陷入 DirectX 一直是最具挑战性的工作我的职业生涯,但因此也是最有收获的和我希望能有机会,总有一天要回到这个强大的技术。
Charles Petzold 是长期贡献 MSDN 杂志和"编程窗口,第 6 版"的作者 (微软出版社,2013年),一本关于编写 Windows 8 应用程序书。 他的网站是 charlespetzold.com。
衷心感谢以下 Microsoft 技术专家对本文的审阅:道格 · 埃里克森
这一问题,标志着 Charles Petzold 最后作为固定在 MSDN 杂志专栏作家。查尔斯离开来加入我们的团队在 Xamarin,利用 Microsoft.NET 框架的跨平台工具的领先供应商。查尔斯几十年来一直与 MSDN 杂志和撰写了许多专栏,包括基金会、 UI 前沿技术和 DirectX 因子。我们祝他好对他新的事业。