Dot函数-详解

 

一、点积(DOT)

1、数学原理

① 二维向量的点积

② 三维向量的点积

2、几何意义及功能

① 几何意义

② 功能

3、语法

① 基本语法

② 在GLSL中的示例

4.、应用示例

① 计算光照强度

② 边缘检测

③ 积雪效果判断

④ Fresnel效果

⑤ 向量长度计算

⑥ 伪光照效果

⑦ 粒子效果圆形遮罩

⑧ 向量元素求和

5、代码示例

① Shader中用dot函数计算向量夹角处理向量的长度

② Shader中用dot函数计算向量夹角

6.、使用Dot函数的问题或限制

① 向量维度匹配

② 精度问题

③ 性能开销

④ 语义理解偏差

⑤ Dot函数的精度导致的画面闪烁问题

7.、Dot的优劣势

① 优势

② 劣势

8、Dot的适用范围和计算领域

① 计算领域

② 适用范围

二、延申

1、其它计算向量夹角的函数

① acos 函数

② atan2 函数

③ cross 叉乘函数

④ Shader中用acos函数计算向量的夹角

⑤ atan2函数和dot函数区别

2、GLSL中的dot函数

①函数定义与功能

②使用场景

③兼容性

3、不同编程语言和库中dot 函数的差异

① Python与NumPy

② PyTorch

③ TensorFlow


一、点积(DOT)

        本文主要介绍Dot函数在Shader中的使用方法、计算原理、实际的应用场景以及代码示例。同时也会对Dot函数做了一些延申。在Shader中,Dot函数主要用于计算两个向量的点积,它是一个非常常用的向量点乘函数,Dot可以计算两个向量之间的夹角、投影、投影比等等。掌握了Dot函数的使用方法和计算原理,可以为Shader的编写和优化提供很大的帮助。

1、数学原理

① 二维向量的点积

  • 向量a和向量b的二维坐标:

  • 点积结果如下:

② 三维向量的点积

  • 向量a和向量b的三维坐标:

  • 点积结果如下:

 

2、几何意义及功能

① 几何意义

        向量的方向

        点积结果等于两个向量的模长相乘再乘以它们夹角的余弦值。若两个向量都为单位向量,点积结果就是夹角的余弦值,可据此判断向量的方向关系,如结果为1,夹角为0度,方向相同;结果为-1,夹角为180度,方向相反;结果为0,夹角为90度,向量垂直。

        阴影

        点积还可用于计算一个向量在另一个向量方向上的投影长度,如方向上的投影长度为

        长度

        向量的长度|| = ,可用于计算物体的速度大小、位移大小等物理量。

② 功能

        从几何角度看:若两个向量都为单位向量,点积结果是两向量夹角的余弦值,据此可判断两向量的夹角情况 。如:

dot(normalize(vector1), normalize(vector2));

        物理意义上:点积结果表示向量 vector2 在向量 vector1 方向上的投影长度,可用于计算力在某方向上的做功等。

3、语法

① 基本语法

其中 vector1 和 vector2 是两个要进行点积运算的同维度向量。

dot(vector1, vector2) 

② 在GLSL中的示例

下边计算得到的 result 值为 1.0 * 3.0 + 2.0 * 4.0 = 11.0 

vec2 a = vec2(1.0, 2.0);

vec2 b = vec2(3.0, 4.0); float result = dot(a, b); 

4.、应用示例

① 计算光照强度

        在漫反射光照模型中,通过计算表面法线向量与光照方向向量的点积,来确定光照对物体表面的影响程度。点积结果越大,光照强度越强。如:

half4 diff = albedo * _LightColor0 * max(0, dot(i.worldNormal, worldLight)); 

② 边缘检测

        通过计算视线方向向量与法线方向向量的点积,可判断像素是否处于物体边缘。当夹角接近 度时,点积绝对值趋近于 ,透明度趋近于 ,实现边缘高亮效果。如

float newOpacity = min(1.0, tex.a / abs(dot(viewDirection, normalDirection))); 

③ 积雪效果判断

        计算面法线与雪落方向的点积,根据点积结果与积雪程度参数比较,判断是否有积雪。点积越大,积雪越多。

if(dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz)>lerp(1,-1,_Snow)) ,

④ Fresnel效果

        计算顶点或像素法线向量与相机向量的点积,得到一个随视角变化的渐变值,常用于实现物体边缘或中心提亮的效果,如衣物、物体及角色的高光效果等。

⑤ 向量长度计算

        向量与自身做点积后再开方可得到向量长度,但有时可利用点积代替长度计算来优化性能,如计算相机到世界中某点的距离。

⑥ 伪光照效果

        计算像素的世界位置与光源位置的点积,经处理后可模拟简单的光照衰减,实现伪光照效果。

⑦ 粒子效果圆形遮罩

        在粒子效果中,通过计算纹理坐标与中心点坐标差值向量的点积,可得到具有球形渐变的圆形遮罩,用于限制粒子效果的显示范围,相比传统方法更高效。

⑧ 向量元素求和

计算向量与全为1的向量的点积,可快速得到向量元素的总和,如 dot(vector, 1) 。

5、代码示例

① Shader中用dot函数计算向量夹角处理向量的长度

         在Shader中用 dot 函数计算向量夹角时,为避免向量长度影响夹角计算结果,需先将参与计算的向量归一化,以下是示例代码(基于GLSL):

// 顶点着色器
#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 FragPos;
out vec3 Normal;

void main() {
// 将顶点位置转换到世界坐标
FragPos = vec3(model * vec4(aPos, 1.0));
// 转换法线向量到世界坐标并归一化
Normal = mat3(transpose(inverse(model))) * aNormal;
Normal = normalize(Normal);
// 输出裁剪空间的顶点位置
gl_Position = projection * view * vec4(FragPos, 1.0);
}
// 片段着色器
#version 330 core

out vec4 FragColor;

in vec3 FragPos;
in vec3 Normal;

uniform vec3 lightDir;

void main() {
// 归一化光照方向向量
vec3 lightDirNorm = normalize(lightDir);

// 用dot函数计算夹角余弦值
float cosTheta = dot(Normal, lightDirNorm);
// 通过反三角函数得到夹角
float angleInRadians = acos(cosTheta);
// 将弧度转换为角度
float angleInDegrees = radiansToDegrees(angleInRadians);

// 根据夹角设置颜色示例
FragColor = vec4(angleInDegrees/360.0, 0.0, 0.0, 1.0);
}

在上述代码中:

  • 顶点着色器:将法线向量 aNormal 转换到世界坐标后,立即使用 normalize 函数将其归一化,这样传递给片段着色器的 Normal 向量长度为1。
  • 片段着色器:在拿到光照方向向量 lightDir  后,同样用 normalize 函数将其归一化处理,变为单位向量。之后再进行 dot 函数计算,此时算出的 cosTheta 才精准反映两向量夹角的余弦值,不受原始向量长度干扰。

② Shader中用dot函数计算向量夹角

        下面是一段使用GLSL编写的Shader代码,利用 dot 函数来计算向量夹角,实现简单的光照效果:

// 顶点着色器
#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 FragPos;
out vec3 Normal;

void main() {
// 顶点位置变换到世界坐标
FragPos = vec3(model * vec4(aPos, 1.0));
// 法线向量变换到世界坐标并归一化
Normal = mat3(transpose(inverse(model))) * aNormal;
Normal = normalize(Normal);

// 计算顶点的裁剪空间位置
gl_Position = projection * view * vec4(FragPos, 1.0);
}
// 片段着色器
#version 330 core

out vec4 FragColor;

in vec3 FragPos;
in vec3 Normal;

uniform vec3 lightPos;
uniform vec3 lightColor;

void main() {
// 计算光照方向并归一化
vec3 lightDir = normalize(lightPos - FragPos);

// 使用dot函数计算夹角余弦值
float cosTheta = dot(Normal, lightDir);
// 获取夹角(弧度制)
float theta = acos(max(cosTheta, 0.0));

// 根据夹角计算光照强度
float intensity = max(0.0, cosTheta);

// 设置片段颜色
FragColor = vec4(lightColor * intensity, 1.0);
}

        这段代码先在顶点着色器里把顶点位置和法线向量转换、归一化,传递到片段着色器。片段着色器计算光照方向向量,接着用 dot 函数算出它与法线向量夹角的余弦值,以此来确定光照强度,最终设置片段颜色。

6.、使用Dot函数的问题或限制

        在使用Cg、HLSL、 GLSL(OpenGL Shading Language)等 shader 语言中的点积(dot)函数时,有以下潜在问题与限制。

① 向量维度匹配

  • 问题: dot  函数用于计算两个向量的点积,传入的两个参数必须是维度相同的向量。比如在 GLSL 里,常见的是2D、3D 或 4D 向量,要是维度不一致,编译就会出错 。例如,将一个  vec2  和  vec3  作为参数传入  dot  函数,代码无法通过编译。
  • 解决:编程前需梳理好数据,保证参与点积运算的向量维度相符,利用类型转换函数,把低维向量合理扩展,或者把高维向量恰当截断。

② 精度问题

  • 问题:在一些对精度敏感的场景,浮点数精度损耗会影响结果。因为 shader 里的浮点数运算受限于硬件实现,尤其是在移动端 GPU 或老旧 GPU 上,反复的点积计算可能累积误差,微小的偏差不断累积,最终造成画面闪烁、渲染瑕疵。
  • 解决:可以使用更高精度的数据类型,像 GLSL 中的  highp ,但这会占用更多资源,权衡性能与精度需求后谨慎使用;或是减少不必要的中间计算,降低精度损失累积。

③ 性能开销

  • 问题:虽然点积计算本身效率较高,但要是在循环内频繁调用  dot  函数,或者处理海量顶点、片段时,计算开销不容小觑,导致帧率下降,渲染卡顿。
  • 解决:尽可能把常量计算提取到循环外部,预先算好能复用的部分;优化算法逻辑,减少冗余的点积运算次数。

④ 语义理解偏差

  • 问题:新手容易误解点积的几何、物理意义,错误运用点积结果。比如错把点积值直接当作距离使用,而实际上点积与向量夹角余弦相关,和距离并非同一概念。
  • 解决:扎实掌握线性代数知识,清楚点积在光照计算、投影变换等场景下的正确用途,多参考成熟的 shader 代码案例,加深理解。

⑤ Dot函数的精度导致的画面闪烁问题

        提高数据精度

  • 使用更高精度数据类型:在 GLSL 等 shader 语言中,可将数据类型从默认精度改为  highp  等高精度类型,但要注意这可能会增加内存占用和计算开销,需根据实际情况权衡使用。
  • 自定义精度计算:对于关键的中间计算结果,可通过额外的变量和计算步骤来提高精度。如先将向量分量乘以一个较大的常数,进行点积计算后再除以该常数恢复到合理范围,以减少精度损失。

        优化计算逻辑

  • 减少不必要运算:避免在循环内频繁调用  dot  函数,将能在循环外计算的部分提前计算好,减少精度损失累积。如多个顶点的光照计算中,可先计算出共用的光照方向等常量的相关值,再在循环内进行必要的计算。
  • 重构算法:审视整个渲染算法和逻辑,看是否有更优的方式来实现相同效果且能减少  dot  函数的使用或降低其对精度的影响。比如使用近似算法或查找表等方式来替代部分复杂的点积计算。

        调整渲染参数

  • 增大近平面距离:在透视投影中,适当增大近平面距离,可使近平面附近物体的精度相对提高,减少因精度问题导致的闪烁,不过需注意这可能会影响场景的可视范围和物体的大小比例 。
  • 调整深度缓冲区精度:根据具体的图形 API 和硬件平台,调整深度缓冲区的精度设置,以更好地适应场景中的深度变化,减少深度值相近的物体之间的闪烁现象 。

7.、Dot的优劣势

优势

  •  计算简单高效,在图形处理单元(GPU)上可并行计算,能快速处理大量向量数据,提高渲染效率。
  • 具有明确的几何和物理意义,便于理解和实现各种图形学和物理学相关的计算,如光照、投影等,可直观地模拟现实世界中的物理现象和视觉效果。

劣势

  • 精度问题,在某些情况下,由于GPU的计算精度限制,点积结果可能存在精度误差,导致画面闪烁等问题,需采取提高数据精度等方法解决 。
  • 单独的点积函数功能有限,通常需与其他数学函数和操作符结合使用,才能实现更复杂的计算和效果,如与normalize函数结合计算夹角余弦值。

8、Dot的适用范围和计算领域

① 计算领域

  • 在图形学的光照计算中,通过计算表面法线向量与光源方向向量的点积,可得到漫反射光照强度。如:

fixed4 diff = albedo * _LightColor0 * max(0, dot(i.worldNormal, worldLight)); 

  • 可用于计算向量投影,如将一个向量投影到另一个向量方向上,通过点积和向量长度计算投影向量。
  • 在判断两个向量的相关性时,点积结果的正负和大小能反映向量的方向关系,如点积为0表示两向量垂直,大于0表示夹角小于90度,小于0表示夹角大于90度。

② 适用范围

  • 广泛应用于各种图形渲染场景,如游戏开发中的角色、场景光照模型计算,以及实现阴影、高光等效果。
  • 在计算机视觉领域,可用于图像特征提取、匹配等,通过计算图像像素点的向量表示之间的点积来判断特征相似性。
  • 在数据可视化中,可根据数据的向量表示计算点积,以确定数据之间的某种关联程度,并据此进行可视化布局等。

二、延申

1、其它计算向量夹角的函数

除了 dot 函数,以下函数也可用于计算向量夹角:

① acos 函数

  • 原理:已知两向量点积等于两向量模长乘积与夹角余弦值的乘积,即 dot(a, b) = |a| * |b| * cosθ ,由此可得 cosθ = dot(a, b) / (|a| * |b|) ,再通过 acos 函数可求出夹角 θ = acos(dot(a, b) / (|a| * |b|)) 。
  • 适用场景:在图形学中,计算光照模型里表面法线向量与光源方向向量夹角等场景常用到,如 fixed4 diff = albedo * _LightColor0 * max(0, dot(i.worldNormal, worldLight));  中,若要得到夹角,可使用 acos 函数。

② atan2 函数

  • 原理: atan2 函数可根据两向量坐标计算出夹角的正切值,再通过反正切得到夹角,对于二维向量 a(x1,y1) 和 b(x2,y2) ,夹角 θ = atan2(y2-y1,x2-x1) - atan2(y1,x1)  ,三维向量则需先将其投影到二维平面再计算。
  • 适用场景:在一些需要精确角度计算且对角度范围有要求的场景中较有用,如在制作广告牌 shader 时,可用于计算广告牌面向方向与相机方向夹角等。

③ cross 叉乘函数

  • 原理:两向量叉乘的模长等于两向量模长乘积与夹角正弦值的乘积,即 |a x b| = |a| * |b| * sinθ ,若已知两向量叉乘结果的模长及两向量模长,可通过 sinθ = |a x b| / (|a| * |b|) 求出夹角正弦值,再结合 asin 函数或其他方法求出夹角。
  • 适用场景:常用于计算垂直于两向量平面的法向量,通过法向量与其他向量关系间接判断夹角,如判断物体表面某点是否在阴影中,可通过计算表面法线与光源方向叉乘得到的法向量,再与阴影投射方向比较夹角来确定。

④ Shader中用acos函数计算向量的夹角

        以下是一个在Shader中使用 acos 函数计算向量夹角的具体案例,用于实现一个简单的光照效果,判断物体表面某点是否在光源的照射范围内:

// 顶点着色器部分
#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 FragPos;
out vec3 Normal;

void main() {
// 将顶点位置转换到世界坐标
FragPos = vec3(model * vec4(aPos, 1.0));
// 将法线向量转换到世界坐标
Normal = mat3(transpose(inverse(model))) * aNormal;
// 计算顶点在裁剪空间中的位置
gl_Position = projection * view * vec4(FragPos, 1.0);
}
// 片段着色器部分
#version 330 core

out vec4 FragColor;

in vec3 FragPos;
in vec3 Normal;

uniform vec3 lightPos;
uniform vec3 lightColor;
uniform float lightAngle;

void main() {
// 计算表面法线与光照方向的单位向量
vec3 normal = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);

// 计算向量夹角的余弦值
float cosTheta = dot(normal, lightDir);
// 使用acos函数得到夹角的弧度值
float angleInRadians = acos(cosTheta);
// 将弧度转换为角度
float angleInDegrees = radiansToDegrees(angleInRadians);

// 判断夹角是否在光源的照射角度范围内
if (angleInDegrees <= lightAngle) {
// 根据光照强度计算公式计算光照强度
float intensity = max(0.0, cosTheta);
FragColor = vec4(lightColor * intensity, 1.0);
} else {
FragColor = vec4(0.1, 0.1, 0.1, 1.0);
}
}

        在上述代码中,首先在顶点着色器中把顶点位置和法线向量转换到世界坐标。然后在片段着色器中,计算表面法线向量与光照方向向量的夹角余弦值 cosTheta ,再通过 acos 函数得到夹角的弧度值 angleInRadians ,并将其转换为角度值 angleInDegrees 。最后,根据夹角与光源照射角度 lightAngle 的比较结果,计算光照强度并设置片段颜色,从而实现简单的光照效果模拟 。

⑤ atan2函数和dot函数区别

        atan2 函数与 dot 函数主要存在以下区别:

        计算结果类型

  • dot函数:用于计算两个同维度向量的点积,返回的是一个标量。这个标量在几何意义上,若向量为单位向量,等同于两向量夹角的余弦值;物理意义上,是一个向量在另一个向量方向上的投影长度。例如, vec2 a = vec2(1.0, 0.0); vec2 b = vec2(0.0, 1.0); , dot(a, b) 返回 0 。
  • atan2函数:接受两个浮点数作为参数,返回的是一个角度值(弧度制 ),用于确定从原点出发,到给定坐标点的向量与x轴正方向的夹角。如 atan2(1.0, 1.0) ,会得到 0.785398 弧度,即45° 。

        输入参数

  • dot函数:需要传入两个向量,并且这两个向量维度必须一致,常见的有二维向量 vec2 、三维向量 vec3 、四维向量 vec4  。如计算光照时,要传入表面法线向量和光源方向向量, dot(normalize(normal), normalize(lightDir)) 。
  • atan2函数:接收两个标量,通常是一个点的y坐标和x坐标,常用来处理直角坐标转换为极坐标的场景, atan2(y, x) ,以此得到对应向量的角度。

        应用场景

  • dot函数:在图形学领域应用广泛,是光照计算的核心函数之一,通过点积判断光线与表面夹角来确定漫反射强度;也用于向量投影计算、判断向量间方向关系(夹角是锐角、直角还是钝角 )等。
  • atan2函数:常用于涉及旋转角度计算的场景,比如在制作2D游戏中,计算角色朝向与目标方向的夹角;或是在一些纹理映射、坐标变换场景,精确得出向量与坐标轴夹角,辅助定位。

        计算复杂度

  • dot函数:计算较为简单直接,是向量对应分量相乘再相加,在GPU上可高效并行计算。
  • atan2函数:涉及复杂的反正切运算,计算成本相比 dot 函数更高,使用频繁时会对性能产生更明显的影响。

2、GLSL中的dot函数

        在Shader编程里,GLSL(OpenGL Shading Language) 是常用的着色语言,Shader中的dot函数其实通常就指GLSL中的dot函数,二者并没有本质区别。

①函数定义与功能

  • GLSL的 dot 函数用于计算两个向量的点积。不管是顶点着色器、片元着色器,只要在GLSL代码里涉及向量运算,都能用该函数精准获取点积结果,运算规则遵循数学定义,二维向量、三维向量分别对应各自维度元素乘积之和。例如在一个简单的光照着色模型里,计算光线方向与平面法线方向的点积,以此衡量光照强度。
  • 代码形式如下:

vec3 lightDir = normalize(lightPosition - fragPos);
vec3 normal = normalize(fragmentNormal);
float diff = max(dot(lightDir, normal), 0.0);

②使用场景

  • 只要是基于OpenGL的图形渲染管线,用到GLSL编写Shader程序, dot 函数的使用场景都是相通的。从基础的光照计算,利用点积判断光线与表面夹角来分配光强;到复杂的几何变换,判断向量间的相对位置关系辅助变形计算,整个OpenGL生态下的Shader开发, dot  函数都是按照标准的向量点积运算逻辑服务于渲染需求。

③兼容性

  • 不同版本的GLSL对 dot 函数的底层优化、调用效率或许稍有不同,但函数签名、输入输出要求和核心运算逻辑高度一致,确保老代码在新GLSL版本或者新显卡驱动下,涉及 dot 函数的部分能平稳过渡,正常执行向量点积运算。 所以总体而言,Shader范畴内提及的 dot 函数基本等同于GLSL里的对应函数。

3、不同编程语言和库中dot 函数的差异

① Python与NumPy

  • 功能: numpy.dot  用于计算两个数组的点积。点积在数学上,对于两个向量而言,是对应元素相乘再求和;对于矩阵,它的运算规则基于线性代数的矩阵乘法。
  • 示例:
import numpy as np
# 向量点积
a = np.array([1, 2])
b = np.array([3, 4])
print(np.dot(a, b))
# 输出 1*3 + 2*4 = 11

# 矩阵乘法
matrix_a = np.array([[1, 2], [3, 4]])
matrix_b = np.array([[5, 6], [7, 8]])
print(np.dot(matrix_a, matrix_b))
# 按矩阵乘法规则运算,输出对应二维数组

② PyTorch

  • 功能:在PyTorch深度学习框架里, torch.dot  同样用于计算点积,不过它主要针对一维的张量(类似向量) 。
  • 示例:
import torch
a = torch.tensor([1, 2])
b = torch.tensor([3, 4])
print(torch.dot(a, b))
# 输出 1*3 + 2*4 = 11


③ TensorFlow

  • 在TensorFlow中,可利用 tf.tensordot 函数来实现类似效果,它更加通用,不仅能完成简单的向量点积、矩阵乘法,还能处理高维张量,通过指定合适的轴参数,灵活实现沿特定维度收缩张量的乘法运算,满足复杂深度学习和张量运算场景需求。
  • 示例:
import tensorflow as tf
a = tf.constant([1, 2])
b = tf.constant([3, 4])
print(tf.tensordot(a, b, axes=1))
# 输出 11

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值