Metal学习笔记十:光照基础
光和阴影是使场景流行的重要要求。通过一些着色器艺术,您可以突出重要的对象、描述天气和一天中的时间并设置场景的气氛。即使您的场景由卡通对象组成,如果您没有正确地照亮它们,场景也会变得平淡无奇。
最简单的光照方法之一是 Phong 反射模型。它以 Bui Tong Phong 的名字命名,他在 1975 年发表了一篇论文,扩展了旧的光照模型。这个想法不是尝试复制光线和反射物理学,而是生成看起来逼真的图片。
这种模型已经流行了 40 多年,是开始使用几行代码来学习如何伪造光照的好地方。所有计算机图像都是假的,但有更现代的实时渲染方法可以模拟光的物理特性。
在第11章“地图和材质”中,您将了解基于物理的渲染(PBR),这是您的渲染器最终将使用的光照技术。PBR 是一种更逼真的光照模型,但 Phong 易于理解和入门。
starter项目
打开本节的starter项目。
起始项目的文件现在位于合理的分组中。在 Game 组中,项目包含一个新的游戏控制器类,该类进一步分离了场景更新和渲染。Renderer 现在独立于 GameScene。GameController 初始化并拥有 Renderer 和 GameScene。在每一帧上,作为 MetalView 的代理,GameController 首先更新场景,然后将其传递给 Renderer 进行绘制。
在 GameScene.swift 中,新场景包含一个球体和一个指示场景旋转的 3D 小工具。
Utility 组中的 DebugLights.swift 包含一些代码,您稍后将使用这些代码来调试光源的位置。点光源将绘制为点,太阳的方向将绘制为线条。
熟悉代码,构建并运行项目。
为了围绕球体旋转并充分欣赏您的光照,相机是 ArcballCamera 类型。按 1(在 Alpha 键上方)将摄像机设置为前视图,按 2 将摄像机重置为默认视图。GameScene 包含用于此功能的按键代码。
您可以看到球体颜色非常平坦。在本章中,您将添加着色光照和镜面反射高光。
颜色表示
在本书中,您将学习必要的基础知识,以渲染光线、颜色和简单的着色。然而,光的物理学是一个庞大而迷人的话题,有许多书籍和很大一部分互联网专门用于讲解它。您可以在本章 resources 目录中的 references.markdown 中找到进一步的阅读内容。
在现实世界中,不同波长的光的反射赋予对象颜色。吸收所有光的对象表面是黑色的。在计算机世界中,像素显示颜色。像素越多,分辨率越好,这使得生成的图像更清晰。每个像素都由子像素组成。这些是预先确定的单一颜色,红色、绿色或蓝色。通过打开和关闭这些子像素,根据颜色深度,屏幕可以显示人眼可见的大部分颜色。
在 Swift 中,您可以使用该像素的 RGB 值来表示颜色。例如,float3(1, 0, 0) 是红色像素,float3(0, 0, 0) 是黑色,float3(1, 1, 1) 是白色。
从着色的角度来看,您可以通过将两个值相乘来将红色表面与灰色光照组合在一起:
let result = float3(1.0, 0.0, 0.0) * float3(0.5, 0.5, 0.5) 结果是 (0.5, 0, 0),这是一个较深的红色着色。
对于简单的 Phong 光照,我们可以使用表面的斜率。物体表面离光源越倾斜,表面就越暗。
法线
表面的斜率可以确定表面反射光线的程度。
在下图中,点 A 正对着太阳,将接收到最多的光;点B不那么直接朝向太阳,但仍会接收到一些光线;点 C 完全背对太阳,接收不到任何光线。
注: 在现实世界中,光线从一个表面反射到另一个表面;如果房间内有任何光线,则物体会有一些反射,这些反射会柔和地照亮所有其他物体的背面。这是全局光照。Phong 光照模型单独照亮每个对象,称为局部光照。
图中的虚线与表面相切。切线是最能描述曲线在某一点处斜率的直线。
从圆中出来的线与切线成直角。这些称为表面法线,您第一次遇到这些法线是在第 7 章 “片段函数”中。
光照类型
计算机图形学中有几个标准的光源选项,每个选项都起源于现实世界。
• Directional Light:沿单个方向发送光线。太阳是定向光。
• Point Light:像灯泡一样向各个方向发送光线。
• Spotlight:向圆锥体定义的有限方向发送光线。手电筒或台灯将是聚光灯。
定向光
一个场景中可以有许多光照。事实上,在工作室摄影中,只有一个灯光是非常不寻常的。通过将灯光放入场景中,可以控制阴影落下的位置和黑暗程度。在本章中,您将向场景添加多个灯光。
您将创建的第一种光照是太阳。太阳是向各个方向发射光线的点光源,但对于计算机建模,您可以将其视为定向光。它是一个遥远的强大光源。当光线到达地球时,光线似乎是平行的。在阳光明媚的日子里在外面检查一下——你所看到的一切都有它的影子,朝着同一个方向移动。
要定义光源类型,您需要创建一个 GPU 和 CPU 都可以读取的 Light 结构体,以及一个描述 GameScene 光照的 SceneLighting 结构体。
➤ 在 Shaders 组中,打开 Common.h,在 #endif 之前,创建您将使用的光源类型的枚举:
typedef enum {unused = 0,Sun = 1,Spot = 2,Point = 3,Ambient = 4
} LightType;
➤ 在此之下,添加定义光源的结构体:
typedef struct {LightType type;vector_float3 position;vector_float3 color;vector_float3 specularColor;float radius;vector_float3 attenuation;float coneAngle;vector_float3 coneDirection;float coneAttenuation;
} Light;
此结构体保存光的位置和颜色。在学习本章时,您将了解其他属性。
➤ 在 Game 组中创建一个新的 Swift 文件,并将其命名为 SceneLighting.swift。然后,添加以下内容:
struct SceneLighting {static func buildDefaultLight() -> Light {var light = Light()light.position = [0, 0, 0]light.color = [1, 1, 1]light.specularColor = [0.6, 0.6, 0.6]light.attenuation = [1, 0, 0]light.type = Sunreturn light}
}
此文件将保存 GameScene 的光照。您将拥有多个光源,buildDefaultLight() 将创建一个基本光源。
➤ 在 SceneLighting 中为太阳定向光创建一个属性:
let sunlight: Light = {var light = Self.buildDefaultLight()light.position = [1, 2, -2]return light
}()
position 在世界空间中。这会在场景的右侧,球体的前方放置一个光源。球体将放置在世界空间的原点处。
➤ 创建一个数组来保存您即将创建的各种光:
var lights: [Light] = []
➤ 添加初始化器:
init() {lights.append(sunlight)
}
您将在初始化器中添加场景的所有灯光。
➤ 打开 GameScene.swift,并将 lighting 属性添加到 GameScene:
let lighting = SceneLighting()
您将在 fragment 函数中执行所有光照着色,因此您需要将光源数组传递给该函数。Metal Shading Language 没有动态数组功能,因此无法找出数组中的项目数。您将此值传递给 Params 中的片段着色器。
➤ 打开 Common.h,并将这些属性添加到 Params 中:
uint lightCount;
vector_float3 cameraPosition;
稍后您将需要 Camera Position 属性。
在 Common.h 中向 BufferIndices 添加新索引:
LightBuffer = 13
您将使用此索引将光照详细信息发送到 fragment 函数。
➤ 打开 Renderer.swift,并将其添加到 updateUniforms(scene:) 中:
params.lightCount = UInt32(scene.lighting.lights.count)
您将能够在片段着色器函数中访问此值。
➤ 在 draw(scene:in:) 中,在 scene.models 中 model 之前,添加以下内容:
var lights = scene.lighting.lights
renderEncoder.setFragmentBytes(&lights,length: MemoryLayout<Light>.stride * lights.count,index: LightBuffer.index)
在这里,您将索引13缓冲区中的灯光数组发送到 fragment 函数。
您现在已经在 Swift 端设置了一个太阳光。您将在 fragment 函数中执行所有实际的光照计算,并了解有关光照属性的更多信息。
Phong反射模型
在 Phong 反射模型中,有三种类型的光照反射。您将计算每个颜色,然后将它们相加以生成最终颜色。
• 漫反射:理论上,照射到表面的光线会以围绕该点的表面法线的反射角度反射。然而,表面在微观上是粗糙的,因此光线会向各个方向反射,如上图所示。这将产生漫反射颜色,其中光强度与入射光与曲面法线之间的角度成正比。在计算机图形学中,这个模型被称为朗伯反射率,以 1777 年去世的约翰·海因里希·兰伯特 (Johann Heinrich Lambert) 的名字命名。在现实世界中,这种漫反射通常适用于暗淡、粗糙的表面,但具有最朗伯特性的表面是人造的:Spectralon (https://en.wikipedia.org/wiki/Spectralon),用于光学元件。
• 镜面反射:表面越光滑,越闪亮,并且光线从表面反射的方向就越集中。镜子完全从表面法线反射,没有偏转。闪亮的物体会产生可见的镜面高光,渲染镜面反射可以让观众了解物体的表面类型,无论汽车是旧车残骸还是刚从销售地新鲜出炉。
• 环境光:在现实世界中,光线会到处反射,因此被遮蔽对象很少是全黑的。这是环境反射。
表面颜色由自发光物体表面颜色加上环境光、漫反射和镜面反射的贡献组成。对于漫反射和镜面反射,要找出表面在特定点应接收多少光,您只需找出入射光方向与表面法线之间的角度即可。
点乘
幸运的是,有一个简单的数学运算来发现两个向量之间的角度,称为点积。
和:
其中 ||A||表示向量 A 的长度(或大小)。
更幸运的是,simd 和 Metal Shading Language 都有一个函数 dot()来获取点积,因此您不必记住公式。
除了找出两个向量之间的角度外,您还可以使用点积来检查两个向量是否指向同一方向。
将两个向量的大小调整为单位向量 — 即长度为 1 的向量。您可以使用 normalize() 函数执行此操作。如果两个单位向量平行且指向同一个方向,则点积结果将为 1。如果它们是平行的但方向相反,则结果将为 -1。如果它们成直角(正交),则结果将为 0。
看上图,如果黄色(太阳)向量垂直向下,蓝色(法线)向量垂直向上,则点积将为 -1。该值是两个向量之间的余弦角。余弦的优点在于它们的值始终是介于 -1 和 1 之间,因此您可以使用此范围来确定光线在某个点的亮度。
以下面示例为例:
太阳从天而降,方向矢量为 [2, -2, 0]。向量 A 是 [-2, 2, 0] 的法向量。这两个向量指向相反的方向,因此当您将向量转换为单位向量(归一化它们)时,它们的点积将为 -1。
向量 B 是 [0.3, 2, 0] 的法向量。太阳光是定向光,因此使用相同的方向向量。归一化后,太阳光和 B 的点积为 -0.59。
此 Playground 代码演示了计算。
注意:第 8 行之后的结果表明,使用浮点时应始终小心,因为结果永远不会精确。切勿使用表达式,例如 if (x == 1.0) - 应始终使用<= 或 >=检查。
在片段着色器中,您将能够获取这些值,并使用点乘乘以片段颜色,以获得片段的亮度。
漫反射
从太阳光中着色,并不取决于摄像机的位置。旋转场景时,将旋转世界,包括太阳。太阳的位置将位于世界空间中,您需要将模型的法线放入同一世界空间,以便能够根据太阳光方向计算点积。事实上,我们可以选择任何空间,只需要把两个点乘的向量变换到同一个空间即可。
为了能够在 fragment 函数中评估表面的斜率,您需要重新定位 vertex 函数中的法线,其方式与之前重新定位顶点位置的方式大致相同。您需要将法线添加到顶点描述符中,以便顶点函数可以处理它们。
➤ 打开 ShaderDefs.h,并将这些属性添加到 VertexOut 中:
float3 worldPosition;
float3 worldNormal;
它们将持有世界空间中的顶点位置和顶点法线。
计算法线的新位置与计算顶点位置略有不同。MathLibrary.swift 包含一个 matrix 方法,用于从另一个矩阵创建法线矩阵。这个法线矩阵是一个 3×3 矩阵,因为首先,您将在不需要投影的世界空间中进行光照,其次,平移对象不会影响法线的斜率。因此,您不需要第四个 W 维度。但是,如果沿一个方向(非线性)缩放对象,则对象的法线不再是正交的,因此此方法将不再适用。只要你决定你的引擎不允许非线性缩放,那么你可以使用模型矩阵左上角的 3×3 部分,这就是你在这里要做的。
➤ 打开 Common.h 并将此矩阵属性添加到 Uniforms:
matrix_float3x3 normalMatrix;
这将在世界空间中保存法线矩阵。
➤ 在 Game 组中,打开 Rendering.swift,在render(encoder:uniforms:params:)中设置 uniforms.modelMatrix:后添加这个
uniforms.normalMatrix = uniforms.modelMatrix.upperLeft
这将从模型矩阵创建法线矩阵。
➤ 打开 Vertex.metal,在 vertex_main 中,分配position后,添加以下内容:
float4 worldPosition = uniforms.modelMatrix * in.position;
在转换为相机和投影空间之前,您可以持有顶点的世界位置。
➤ 定义 out 时,填充 VertexOut 属性:
.worldPosition = worldPosition.xyz / worldPosition.w,
.worldNormal = uniforms.normalMatrix * in.normal
光栅器按position执行透视除法,如第 6 章 “坐标空间”中所述。要确保处理所有缩放问题,请在此处对 worldPosition除以 w。
在本章的前面部分,您将 LightBuffer索引缓冲区中Renderer 的 lights 数组发送到fragment 函数,但您尚未更改 fragment 函数以接收该数组。
➤ 打开 Fragment.metal 并将以下内容添加到 fragment_main 的参数列表中:
constant Light *lights [[buffer(LightBuffer)]],
使用c++创建着色函数
通常,您需要从多个文件访问 C++ 函数。光照函数是您可能希望分离出来的一些函数的一个很好的示例,因为您可以拥有各种光照模型,这些模型可能会调用一些相同的代码。
要从多个 .metal 文件中调用函数:
1. 使用要创建的函数的名称设置头文件。
2. 创建一个新的 .metal 文件并导入头文件,如果您打算使用该文件中的结构体,则还要导入桥接头文件 Common.h。
3. 在此新文件中创建光照函数。
4. 在现有的 .metal 文件中,导入新的头文件并使用光照函数。
在 Shaders 组中,创建一个名为 Lighting.h 的新 Header File。不要将其添加到target中。
➤ 在 #endif /* Lighting_h */ 之前添加此函数头:
#import "Common.h"
float3 phongLighting(float3 normal,float3 position,constant Params ¶ms,constant Light *lights,float3 baseColor);
在这里,您定义一个返回 float3 的 C++ 函数。
在 Shaders 组中,创建一个名为 Lighting.metal 的新 Metal 文件。将其添加到target。
➤ 添加此新函数:
#import "Lighting.h"
float3 phongLighting(float3 normal,float3 position,constant Params ¶ms,constant Light *lights,float3 baseColor) {return float3(0);
}
您创建一个返回float3 零值的新函数。您将在 phongLighting 中构建代码来计算这个最终的光照值。
➤ 打开 Fragment.metal,#import “Common.h” 替换为:
#import "Lighting.h"
现在,您将能够在此文件中使用 phongLighting。
➤ 在 fragment_main 中,替换 return float4(baseColor, 1);为:
float3 normalDirection = normalize(in.worldNormal);
float3 color = phongLighting(normalDirection,in.worldPosition,params,lights,
baseColor );
return float4(color, 1);
在这里,您将世界法线设置为单位向量,并使用必要的参数调用新的光照函数。
如果您现在构建并运行该应用程序,您的模型将呈现为黑色,因为这是您当前从 phongLighting 返回的颜色。
➤ 打开 Lighting.metal,并替换 return float3(0);为:
float3 diffuseColor = 0;
float3 ambientColor = 0;
float3 specularColor = 0;
for (uint i = 0; i < params.lightCount; i++) {Light light = lights[i];switch (light.type) {
case Sun: {
break; }
case Point: {
break; }
case Spot: {
break; }case Ambient: {
break; }case unused: {
break; }
} }
return diffuseColor + specularColor + ambientColor;
这里是您计算所有光照的代码框架。您将累积得到最终的片段颜色,由漫反射、镜面反射和环境光组成。
➤ 在 case Sun 的break前面,添加以下内容:
// 1
float3 lightDirection = normalize(-light.position);
// 2
float diffuseIntensity =saturate(-dot(lightDirection, normal));
// 3
diffuseColor += light.color * baseColor * diffuseIntensity;
浏览此代码:
1. 将光线的方向设为单位向量。
2. 计算两个向量的点积。当片段完全指向光线时,点积将为 -1。让这个值为正数,更易于进一步计算,因此您取点积的负值。saturate 通过截断负数来确保该值介于 0 和 1 之间。这为您提供了表面的斜率,从而提供了漫反射的强度。
3. 将基础颜色乘以漫反射强度,以获得漫反射着色。如果有多个太阳光,则 diffuseColor 将累积漫反射着色。
➤ 构建并运行应用程序。
您可以通过从 phongLighting 返回中间计算结果来对结果进行健全性检查。下图显示了前视图中的 normal 和 diffuseIntensity。
注意:要在应用程序中获取前视图,请在运行时按 Alpha 键上方的“1”。“2” 将重置为默认视图。
Utility 组中的 DebugLights.swift 和 DebugLights.metal 具有一些调试方法,以便您可以直观地了解光源的位置。
➤ 打开 DebugLights.swift,并删除文件顶部和底部的 /* 和 */。在本章中添加代码之前,此文件不会编译,但现在可以编译。
➤ 打开 Renderer.swift,在 draw(scene:in:) 的末尾,在 renderEncoder.endEncoding() 之前,添加以下内容:
DebugLights.draw(lights: scene.lighting.lights,encoder: renderEncoder,uniforms: uniforms)
此代码将显示线条以可视化太阳光的方向。
➤ 构建并运行应用程序。
红线显示平行太阳光方向矢量。旋转场景时,可以看到最亮的部分是面向太阳的部分。
注意:调试方法使用 .line 作为渲染类型。遗憾的是,线宽在 GPU 上是不可配置的,它们可能会在某些角度,因线条太细而无法渲染时消失。
这个着色效果令人愉悦,但不准确。看看球体的背面。球体的背面是黑色的;但是,您可以看到绿色环绕的顶部是亮绿色的,因为它朝上。在现实世界中,周围环境会被球体阻挡,因此处于阴影中。但是,您目前没有考虑遮挡,只有在第 13 章 “阴影” 中掌握阴影后才考虑这个问题。
环境反射
在现实世界中,颜色很少是纯黑色。到处都是光线反射。要模拟这种情况,您可以使用环境光照。您将找到场景中灯光的平均颜色,并将其应用于场景中的所有表面。
➤ 打开 SceneLighting.swift,并添加一个 ambient light 属性:
let ambientLight: Light = {var light = Self.buildDefaultLight()light.color = [0.05, 0.1, 0]light.type = Ambientreturn light
}()
此光照略带绿色。
➤ 将以下内容添加到 init() 的末尾:
lights.append(ambientLight)
➤ 打开 Lighting.metal,case Ambient的break上面,添加以下内容:
ambientColor += light.color;
➤ 构建并运行应用程序。场景现在呈绿色,就像有绿灯在周围反射一样。
镜面反射
在 Phong 反射模型中,最后但并非最不重要的一点是镜面反射。您现在有机会在球体上涂上一层闪亮的清漆。镜面高光取决于观察者的位置。如果您经过一辆闪亮的汽车,您只会在某些角度看到高光。
光线进入 (L) 并被绕着法线 (N)反射 (R) 。如果观察者 (V) 位于反射 (R) 周围的特定圆锥体内,则观察者将看到镜面高光。cone是指数光泽度参数。表面越闪亮,镜面反射高光就越小、越强烈。
在本例中,观察者是您的相机,因此您需要将相机坐标再次传递到 fragment 函数。之前,您在 params 中设置了一个 cameraPosition 属性,您将使用它来传递相机位置。
➤ 打开 Renderer.swift,然后在 updateUniforms(scene:) 中添加以下内容:
params.cameraPosition = scene.camera.position
scene.camera.position 已在世界空间中,并且您已将参数传递给 fragment 函数,因此您无需在此处执行进一步操作。
➤ 打开 Lighting.metal,然后在 phongLighting 中,将以下变量添加到函数顶部:
float materialShininess = 32;
float3 materialSpecularColor = float3(1, 1, 1);
它们包含光泽度因子和镜面反射颜色的表面材质属性。由于这些是表面属性,您应该从每个模型的材质中获取这些值,您将在下一章中执行此操作。
➤ 在 case Sun的break前面添加以下内容:
if (diffuseIntensity > 0) {// 1 (R)float3 reflection =reflect(lightDirection, normal);
// 2 (V)float3 viewDirection =normalize(params.cameraPosition);// 3float specularIntensity =pow(saturate(dot(reflection, viewDirection)),materialShininess);specularColor +=light.specularColor * materialSpecularColor* specularIntensity;
}
浏览此代码:
1. 为了计算镜面反射颜色,您需要 (L) ight、(R) eflection、(N) ormal 和 (V)iew。您已经有 (L) 和 (N),因此在这里使用 Metal Shading Language 函数 reflect 来获取 (R)。
2. 您需要片段和相机之间的视图向量(V)。
3. 现在,您计算镜面反射强度。您可以使用点积找到反射和视图向量之间的角度,使用 saturate 将结果限制在 0 和 1 之间,并使用 pow 将结果提升到光泽度次幂。然后,您可以使用此强度来确定片段的镜面反射颜色。
➤ 构建并运行应用程序以查看您完成的光照。
尝试将材质光泽度从 2 更改为 1600。在第11章“贴图和材质”中,您将了解如何从模型中读取材质和纹理属性以更改其颜色和光照。
您已经为太阳光创建了足够逼真的光照情况。您可以使用点光源和聚光灯为场景添加更多变化。
点光源
在太阳光中,您将位置转换为平行方向向量。与太阳光相反,点光源则向所有方向发射光线。
灯泡只会照亮一定半径的区域,超过该半径后,一切都是黑暗的。因此,您还将指定光线不会无限传播的衰减。
光线衰减可以突然或逐渐发生。衰减的原始 OpenGL 公式为:
其中 x 是常数衰减因子,y 是线性衰减因子,z 是二次衰减因子。
该公式给出了曲线的衰减。您将用 float3 表示 xyz。完全没有衰减将是 float3(1, 0, 0) — 将 x、y 和 z 代入公式得到值 1。
➤ 打开 SceneLighting.swift,并向 SceneLighting 添加点光源属性:
let redLight: Light = {var light = Self.buildDefaultLight()light.type = Pointlight.position = [-0.8, 0.76, -0.18]light.color = [1, 0, 0]light.attenuation = [0.5, 2, 1]return light
}()
聚光
在本章中,您将创建的最后一种光源类型是聚光灯。这会向有限的方向发送光线。想想手电筒,光线从一个小点发出,但当它照射到地面时,它是一个更大的椭圆。
您可以定义一个圆锥体角度以包含圆锥体方向上的光线。我们还要定义一个衰减幂次来控制椭圆边缘的光照衰减。
打开 SceneLighting.swift,添加一个新的光照对象:
lazy var spotlight: Light = {var light = buildDefaultLight()light.position = [0.4, 0.8, 1]light.color = [1, 0, 1]light.attenuation = float3(1, 0.5, 0)light.type = Spotlightlight.coneAngle = Float(40).degreesToRadianslight.coneDirection = [-2, 0, -1.5]light.coneAttenuation = 12return light
}()
本光照和点光源有点类似,不过增加了圆锥角度,方向,以及圆锥衰减因子。
在init(metalView) 添加这个光照到光照数组中。
lights.append(spotlight)
打开 Lighting.metal,然后在 phongLighting 中,在 case Spot 的 break 上方添加以下代码:
// 1
float d = distance(light.position, position);
float3 lightDirection = normalize(light.position - position);
// 2
float3 coneDirection = normalize(light.coneDirection);
float spotResult = dot(lightDirection, -coneDirection);
// 3
if (spotResult > cos(light.coneAngle)) {float attenuation = 1.0 / (light.attenuation.x +light.attenuation.y * d + light.attenuation.z * d * d);
// 4attenuation *= pow(spotResult, light.coneAttenuation);float diffuseIntensity =saturate(dot(lightDirection, normal));float3 color = light.color * baseColor * diffuseIntensity;color *= attenuation;diffuseColor += color;
}
此代码与点光源代码非常相似。浏览注释:
1. 计算距离和方向,就像计算点光源一样。这束光可能在聚光锥体外。
2. 计算该光线方向与聚光源指向的方向之间的余弦角(即点积)。
3. 如果该结果超出圆锥角,则忽略该射线。否则,计算点光源的衰减。指向同一方向的向量的点积为 1.0。
4. 使用 coneAttenuation 作为幂次来计算聚光源边缘的衰减。
➤ 构建并运行应用程序。
尝试更改各种衰减值。锥体角度为 5°,然后衰减向量为(1, 0, 0),锥体衰减因子为 1000 将产生非常小的聚焦柔光;而 20°的锥体角度和 1 的锥体衰减因子将产生锐利的圆形光。
参考
https://zhuanlan.zhihu.com/p/391592709
https://zhuanlan.zhihu.com/p/392622099
相关文章:
Metal学习笔记十:光照基础
光和阴影是使场景流行的重要要求。通过一些着色器艺术,您可以突出重要的对象、描述天气和一天中的时间并设置场景的气氛。即使您的场景由卡通对象组成,如果您没有正确地照亮它们,场景也会变得平淡无奇。 最简单的光照方法之一是 Phong 反射模…...
MongoDB 高级索引
MongoDB 高级索引 摘要 在数据库管理中,索引是提高查询效率的关键因素。MongoDB,作为一款流行的NoSQL数据库,其索引功能尤为强大。本文将深入探讨MongoDB的高级索引特性,包括复合索引、部分索引、文本索引、地理空间索引等,旨在帮助数据库管理员和开发者更好地利用Mongo…...
从DNS到TCP:DNS解析流程和浏览器输入域名访问流程
1 DNS 解析流程 1.1 什么是DNS域名解析 在生活中我们会经常遇到域名,比如说CSDN的域名www.csdn.net,百度的域名www.baidu.com,我们也会碰到IP,现在目前有的是IPV4,IPV6。那这两个有什么区别呢?IP地址是互联网上计算机…...
【JQuery—前端快速入门】JQuery 基础语法
JQuery JQuery是一个快速、简洁且功能丰富的JavaScript框架; 1. 引入依赖 使用JQuery需要先引入对应的库; 在使用 JQuery CDN 时,只需要在 HTML 文档中加入如下代码 <script src"https://code.jquery.com/jquery-3.7.1.min.js"></s…...
即梦AI发布新数字人模型OmniHuman-1
简介 随着人工智能技术的发展,特别是深度学习和自然语言处理的进步,AI在内容创作领域的应用越来越广泛。字节跳动作为一家领先的科技公司,一直在探索如何利用AI技术来提升用户体验和创造力。OmniHuman-1模型正是在这种背景下诞生的ÿ…...
ARM CM3核 压栈流程
STM32F103 使用 ARM Cortex-M3 内核,与 STM32F013(Cortex-M0)相比,其压栈行为有所不同,主要体现在异常自动压栈和**手动压栈(函数调用)**两方面。 1. 进入异常/中断时的自动压栈 当 STM32F103 …...
WHAT - RxJS 异步事件流处理
目录 RxJS 关键概念简单示例常用操作符1. 创建 Observables2. 转换操作符(map)3. 过滤操作符(filter)4. 合并多个流(mergeMap) 适用于 React & TypeScriptReact 组件中使用 RxJS RxJS 是一个用于处理异…...
vue组合式API中prop
*一、了解(事件与$emit) 一个组件需要显示声明它所接受的props,这样才知道外部传入的那些是props,那些是透传attribute。 在<script setup>单文件组件中,props可以使用defineProps()宏来声明,defineProps 是一个仅 <script setup> 中可用的编译宏命令,并不需…...
【HarmonyOS Next】自定义Tabs
背景 项目中Tabs的使用可以说是特别的频繁,但是官方提供的Tabs使用起来,存在tab选项卡切换动画滞后的问题。 原始动画无法满足产品的UI需求,因此,这篇文章将实现下面页面滑动,tab选项卡实时滑动的动画效果。 实现逻…...
算法 并查集
目录 前言 一 并查集的思路 二 并查集的代码分析 三 实操我们的代码 四 并查集的代码优化 总结 前言 并查集主要是用来求解集合问题的,用来查找集合还有就是合并集合,可以把这个运用到最小生成树里面 一 并查集的思路 1 并查集的相关的操作…...
EP 架构:未来主流方向还是特定场景最优解?
DeepSeek MoE架构采用跨节点专家并行(EP)架构,在提升推理系统性能方面展现出巨大潜力。这一架构在发展进程中也面临诸多挑战,其未来究竟是会成为行业的主流方向,还是仅适用于特定场景,成为特定领域的最优解…...
记忆化搜索与动态规划:原理、实现与比较
记忆化搜索和动态规划是解决优化问题的两种重要方法,尤其在处理具有重叠子问题和最优子结构性质的问题时非常有效。 目录 1. 记忆化搜索(Memoization) 定义: 实现步骤: 示例代码(斐波那契数列࿰…...
LLMR//https://github.com/microsoft/llmr?locale=zh-cn
https://github.com/microsoft/llmr?localezh-cn Introduction 这个 repo 包含 LLMR 中描述的代码,实现了混合现实框架的大型语言模型。 此软件包是“用语言创造世界”的原型,它允许通过自然语言实时创建具有视觉、行为和交互元素的对象、工具和场景…...
Free Auto Clicker - 在任意位置自动重复鼠标点击
“想让鼠标自己动起来,解放双手去做更有趣的事?”Free Auto Clicker 就像你的数字小助手,能在任意位置自动重复点击鼠标。从玩游戏到刷网页,这款免费工具让你告别枯燥的重复操作,效率瞬间起飞! 你有没有想…...
高考數學。。。
2024上 具体来说,直线的参数方程可以写为: x1t y−t z1t 二、简答题(本大题共5小题,每小题7分,共35分。) 12.数学学习评价不仅要关注结果评价,也要关注过程评价。简要说明过程评价应关注哪几个方面。…...
MWC 2025|紫光展锐联手美格智能发布5G通信模组SRM812
在2025年世界移动通信大会(MWC 2025)期间,紫光展锐携手美格智能正式推出了基于紫光展锐V620平台的第二代5G Sub6G R16模组SRM812,以超高性价比方案,全面赋能合作伙伴,加速5G规模化应用在各垂直领域的全面落…...
5分钟快速搭建一个 SpringBoot3 + MyBatis-Plus 工程项目
环境 idea 2023.3.5 jdk 17 mysql 8 创建SpringBoot工程 创建SpringBoot工程,这里有两种方式可选,一种是使用idea提供的Spring Initializr自动创建,一种是通过Maven Archetype手动创建 自动创建SpringBoot工程 使用Spring Initializr创建…...
深度学习-大白话解释循环神经网络RNN
目录 一、RNN的思想 二、RNN的基本结构 网络架构 关键点 三、RNN的前向传播 四、RNN的挑战:梯度爆炸和梯度消失 问题分析 示例推导 五、LSTM:RNN的改进 核心组件 网络架构 3. LSTM 的工作流程 4. 数学公式总结 5. LSTM 的优缺点 优点 缺点 6. LSTM 的…...
20.<Spring图书管理系统①(登录+添加图书)>
PS:关于接口定义 接口定义,通常由服务器提供方来定义。 1.路径:自己定义 2.参数:根据需求考虑,我们这个接口功能完成需要哪些信息。 3.返回结果:考虑我们能为对方提供什么。站在对方角度考虑。 我们使用到的…...
windows下使用Hyper+wsl实现ubuntu下git的平替
文章目录 前言一、安装Hyper、wsl1. 安装Hyper2. 安装wsl 二、配置Hyper三、安装并使用git总结 前言 众所周知,Ubuntu下安装git只需执行sudo apt install git即可使用默认终端拉取代码,但是Windows上使用git既没有linux便捷,又没有MacOS优雅…...
Python在实际工作中的运用-提取Pdf文件内容
Pdf文件是我们日常工作中经常会遇到的一种文件格式,对于这种文件的提取 pdfplumber 库可以非常出色的完成处理工作,它是一个纯 Python 第三方库,适合 python 3.x 版本,通常用来查看pdf各类信息,能有效提取文本、表格&…...
Python+Vue+数据可视化的考研知识共享平台(源码+论文+讲解+安装+调试+售后)
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 程序介绍 近些年来,科技以一种近乎狂飙突进的态势呈爆发式发展,成果之丰硕…...
VirtualBox虚拟机MacOS从Big Sur升级到Sequoia(失败)
VirtualBox虚拟机里安装好Big Sur版本,尝试升级到Sequoia,但是最终失败了。 软件升级 直接在系统偏好-软件更新里可以看到提示,提示可以升级到15版本Sequoia 点击同意,看能不能升级到Sequoia吧。升级前先用时光做了备份。 升级…...
深度学习---卷积神经网络
一、卷积尺寸计算公式 二、池化 池化分为最大池化和平均池化 最常用的就是最大池化,可以认为最大池化不需要引入计算,而平均池化需要引出计算(计算平均数) 每种池化还分为Pooling和AdaptiveAvgPool Pooling(2)就是每2*2个格子…...
深入解析网络协议:从OSI七层模型到HTTP与TCP/IP的关系
在网络的世界里,理解不同协议如何协同工作以实现高效、可靠的通信至关重要。无论是构建动态的Web应用,还是进行复杂的网络编程,对基础协议的理解都是不可或缺的。本文首先介绍OSI七层模型,这是一个为网络系统设计提供通用参考框架…...
链表-相关面试算法题
目录 面试题 02.04. 分割链表 面试题 02.05. 链表求和 面试题 02.06. 回文链表 面试题 02.07. 链表相交 面试题 02.04. 分割链表 面试题 02.04. 分割链表 给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点…...
CSS Overflow 属性详解
CSS Overflow 属性详解 在网页设计和开发中,CSS Overflow 属性是一个非常重要的特性,它决定了当内容超出其容器大小时应该如何处理。本文将详细介绍 CSS Overflow 属性的相关知识,包括其语法、作用、常用属性值以及一些实际应用场景。 1. CSS Overflow 属性概述 CSS Over…...
一、MySQL备份恢复
一、MySQL备份恢复 1.1 MySQL日志管理 数据库中数据丢失或被破坏可能原因 误删除数据库 数据库工作时,意外断电或程序意外终止 由于病毒造成的数据库损坏或丢失 文件系统损坏后,系统进行自检操作 升级数据库时,命令语句不严格 设备故…...
Python-04BeautifulSoup网络爬虫
2025-03-04-BeautifulSoup网络爬虫 记录BeautifulSoup网络爬虫的核心知识点 文章目录 2025-03-04-BeautifulSoup网络爬虫 [toc]1-参考网址2-学习要点3-核心知识点1. 安装2. 导入必要的库3. 发送 HTTP 请求4. 创建 BeautifulSoup 对象5. 解析 HTML 内容5.1 查找标签5.2 根据属性…...
记录uniapp小程序对接腾讯IM即时通讯无ui集成(2)
完成以上步骤之后开始进行登录,登陆就需要账号。这个账号我们可以在腾讯云中创建。 有了账号之后开始去小程序进行登陆操作。腾讯云接口文档 这里除了帐号还需要一个校验值userSig正常项目开发这个字段可以在登陆后让后端返回,现在是测试我们直接去控制…...
DE2115实现4位全加器和3-8译码器(FPGA)
一、配置环境 1、Quartus 18.1安装教程 软件:Quartus版本:Quartus 18.1语言:英文大小:5.78G安装环境:Win11/Win10/Win8/Win7硬件要求:CPU2.0GHz 内存4G(或更高) 下载通道①百度网盘丨64位下载…...
大白话面试中应对自我介绍
在面试中,自我介绍是开场的关键环节,它就像你递给面试官的一张“个人名片”,要让面试官快速了解你并对你产生兴趣。下面详细讲讲应对自我介绍的要点及回答范例。 一、自我介绍的时间把控 一般面试中的自我介绍控制在1 - 3分钟比较合适。时间…...
【JavaScript—前端快速入门】JavaScript 综合案例 — 猜数字
JavaScript 综合案例—猜数字 预期效果 需求 完成基本的页面布局在文本框输入数字后,点击"猜"按钮,结果那一行会显示"猜大了"或者"猜小了"每猜一次,就会增加一次猜的次数猜到数字后,结果显示要猜的…...
X Window---图形接口
摘抄自 鸟哥的linux私房菜 基础篇 第四版 有鉴于图形用户接口(Graphical User Interface, GUI) 的需求日益加重,在 1984 年由 MIT 与其他第三方首次发表了 X Window System ,并且更在 1988 年成立了非营利性质的 XFree86 这个组织。所谓的XFree86 其实是…...
CSS浮动详解
1. 浮动的简介 浮动是用来实现文字环绕图片效果的 2.元素浮动后会有哪些影响 对兄弟元素的影响: 后面的兄弟元素,会占据浮动元素之前的位置,在浮动元素的下面;对前面的兄弟 无影响。 对父元素的影响: 不能撑起父元…...
shell脚本编程实践第2天
1 内容格式化 1.1 输出格式化 1.1.1 echo解读 学习目标 这一节,我们从 基础知识、简单实践、小结、三个方面来学习。 基础知识 命令简介 echo命令的功能是将内容输出到默认显示设备,一般起到一个提示的作用。OPTIONS: -n 不要在最后自…...
wgcloud-server端部署说明
Wgcloud 是一款开源的轻量级服务器监控系统,支持多平台,可对服务器的 CPU、内存、磁盘、网络等指标进行实时监控。 以下是 Wgcloud Server端的详细部署步骤: 环境准备 服务器: 至少准备两台服务器,一台作为监控端&a…...
AES/CBC/PKCS5Padding加密
1、加密代码如下 public static String encryptAEs_CBC(String data,String key,byte[] iv) {Cipher cipher = null;try {cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");//位数不够,自动补一个长度int blocksize = cipher.getBlockSize();byte[] dataBytes …...
9.8 Visual Studio 2022安装Qt 和安装graphic
1.安装Qt 1. 安装Qt 首先打开Visual Studio,然后创建一个项目,再在最上面的一行依次打开“扩展-->管理扩展”,再在搜索框内搜索“QT”,显示如下界面: 再在右边QT右边有个“在浏览器中查看”,打开后出现…...
【C++设计模式】第四篇:建造者模式(Builder)
注意:复现代码时,确保 VS2022 使用 C17/20 标准以支持现代特性。 分步骤构造复杂对象,实现灵活装配 1. 模式定义与用途 核心目标:将复杂对象的构建过程分离,使得同样的构建步骤可以创建不同的表示形式。 常见场景&am…...
Spring Boot如何利用Twilio Verify 发送验证码短信?
Twilio提供了一个名为 Twilio Verify 的服务,专门用于处理验证码的发送和验证。这是一个更为简化和安全的解决方案,适合需要用户身份验证的应用。 使用Twilio Verify服务的步骤 以下是如何在Spring Boot中集成Twilio Verify服务的步骤: 1.…...
制造业中的“大数据”:如何实现精准决策?
在当今全球经济竞争日趋激烈、技术变革周期不断缩短的环境下,制造业面临着全新的挑战和机遇。随着信息技术的飞速发展,“大数据”正以前所未有的速度渗透到制造业的各个环节,帮助企业实现更精准的决策、更灵活的生产组织以及更敏捷的市场响应…...
Linux cat 命令
cat(英文全拼:concatenate)命令用于连接文件并打印到标准输出设备上,它的主要作用是用于查看和连接文件。 使用权限 所有使用者 语法格式 cat [选项] [文件] 参数说明: -n:显示行号,会在输…...
CSS—flex布局、过渡transition属性、2D转换transform属性、3D转换transform属性
1.flex布局 也叫弹性布局,是浏览器提倡的布局模型,非常适合结构化布局,提供了强大的空间分布和对齐能力,不会产生浮动布局中脱标现象,布局网页更简单,更灵活。 flex容器属性: 属性描述d…...
Java UDP 通信:实现简单的 Echo 服务器与客户端
在计算机网络编程中,UDP(User Datagram Protocol)是一种无连接的传输层协议,它允许应用程序在不建立连接的情况下发送数据包。与 TCP 不同,UDP 不保证数据包的顺序、可靠性或完整性,但它具有低延迟和低开销…...
使用GitLink个人建站服务部署Allure在线测试报告
更多技术文章,访问软件测试社区 文章目录 🚀前言🔑开通GitLink个人建站服务1. 前提条件2. 登录GitLink平台(https://www.gitlink.org.cn/login)3. 进入设置>个人建站>我的站点4. 新建站点5. 去仓部进行部署6. 安…...
Mybatis plus异常: type `java.time.LocalDateTime` not supported by default
1、问题: Java数据库实体对象有字段如下: TableField(typeHandler JacksonTypeHandler.class) private Map<String, Object> dataDetail; 如果dataDetail中有LocalDateTime对象,在保存时会报如下异常: Caused by: com.…...
Django:文件上传时报错in a frame because it set ‘X-Frame-Options‘ to ‘deny‘.
即:使用Content-Security-Policy 1.安装Django CSP中间件: pip install django-csp 2.更改项目配置: # settings.py MIDDLEWARE [...csp.middleware.CSPMiddleware,... ]CSP_DEFAULT_SRC ("self",) CSP_FRAME_ANCESTORS (&q…...
腾讯云 | 微搭低代码快速开发数据表单应用
如上所示,登录腾讯云微搭低代码业务控制台,开始新创建一个应用,创建应用的方式包括,根据实际的业务需求,从模版列表中选择一个模板填入数据模型创建新应用,使用微搭组件自主设计数据模型创建新应用…...
【RabbitMQ】RabbitMQ的核心概念与七大工作模式
🔥个人主页: 中草药 🔥专栏:【中间件】企业级中间件剖析 在现代分布式系统和微服务架构中,消息队列(Message Queue) 是解决服务间通信、系统解耦和流量削峰的关键技术之一。而 RabbitMQ 作为一…...