回到计算距离的主要问题,要计算 A 和 B 两个点之间沿某条路径 \( x^i(t) \) 的长度,我们只需使用积分将无穷小距离加在一起:x我( t )我们只需要使用积分将无穷小的距离加在一起:
l =∫乙一个克μνdxμdxν--------√=∫乙一个克μνdxμdxν--------√dtdt=∫乙一个克μνdxμdtdxνdt----------√dt
其中 \( \frac{dx^i}{dt} \) 只是坐标 x 相对于路径参数(“时钟”)变化的速度,在某种意义上可以解释为速度。dx我dt只是坐标 x 相对于路径参数(“时钟”)变化的速度,在某种意义上可以解释为速度。
浮动x = v * x 。x ;
float r = sqrt ( sqr ( x . y - x ) + x . z * x . z + x . w * x . w ) ;
浮点数f = 0.5 * ( tanh ( sigma * ( r + R ) ) - tanh( sigma * ( r - R ) ) ) / tanh ( sigma * R ) ;
浮动gtt = v * v * f * f - 1.0 ;
浮动gxt = - v * f ;
vec4 HamiltonianGradient ( vec4 x , vec4 p )
{
const float eps = 0.001 ;
返回 ( vec4 (哈密顿量( x + vec4 ( eps , 0 , 0 , 0 ) , p ) ,
哈密顿量( x + vec4 ( 0 , eps , 0 , 0 ) , p) ,
哈密顿量( x + vec4 ( 0 , 0 , eps , 0 ) , p ) ,
哈密顿量( x + vec4 ( 0 , 0 , 0 , eps ) , p ) ) -哈密顿量( x , p ) ) / eps ;
}
现在我们有了哈密顿梯度,我们终于可以写出运动方程 \eqref{eqmotion1} \eqref{eqmotion2} 积分代码(???) (???)集成代码
vec4 IntegrationStep ( inout vec4 x , inout vec4 p )
{
const float TimeStep = 0.1 ;
p = p -时间步长*哈密顿梯度( x , p ) ;
x = x +时间步长* inverse ( Metric ( x ) ) * p ;
}
您可能会问,“等等,就这些了?”,事实上这就是积分测地线所需的全部。当然,它是相当慢的,因为我们进行了多达 6 个矩阵逆评估,通过用没有逆矩阵的拉格朗日函数替换大多数哈密顿量,可以将其优化为 1,因为它们是相等的。更好的是已经通过分析计算了度量逆,但是不可能对每个度量,特别是对于隐式定义的度量。
当然还有最后一个问题,虽然初始化时空位置很容易,但是p在开始追踪时我们如何初始化动量向量的值呢?
在追踪测地线之前,您可以使用方程 \eqref{momentum}(???)
p =公制( x ) * dxdt ;
但 dxdt 是什么?它只不过是光线在时空中移动的 4D 方向。方向可分为 3 类:
类时间,当 \( A < 0 \)A < 0
空,当 \( A = 0 \)A = 0
类空间,当 \( A > 0 \)A > 0
其中 \(A\) 是 \begin{方程}一个是
A =克μνdxμdtdxνdt
或者在 GLSL 中,A = dot(Metric(x) * dxdt, dxdt)
因此,假设平坦空间度量 \eqref{flat} 和空间中的某个 3D 方向,我们的光线 p 将为(???)空间中的某个 3D 方向我们的光线 p 是
vec4 GetNullMomentum ( vec3 dir )
{
return Metric ( x ) * vec4 ( 1.0 , 标准化( dir ) ) ;
}
以及这个操作的逆操作
vec3 GetDirection ( vec4 p )
{
vec4 dxdt = inverse ( Metric ( x ) ) * p ;
返回 标准化( dxdt . yzw ) ;
}
所以,最终的简单算法将如下所示:
void TraceGeodesic ( inout vec3 pos , inout vec3 dir , inout float time )
{
vec4 x = vec4 (时间,位置) ;
vec4 p = GetNullMomentum ( dir ) ;
常量 int步骤= 256 ;
for ( int i = 0 ; i <步骤; i ++ )
{
IntegrationStep ( x , p ) ;
//例如,当 x 低于事件视界时,您可以在此处添加停止条件
}
位置= x 。yzw ;
时间= x 。x ;
dir = GetDirection ( p ) ;
}
本质上,这只是一个 4D 射线行进算法,其中射线的方向每一步都会改变。在这种特定情况下,步长的大小也会发生变化,这可以通过标准化动量来避免p = normalize(p)。这只会改变步长,不会改变测地线路径,即,它的工作原理就像路径的动态重新参数化一样。积分的时间步长也可以根据所使用的度量而变化。例如,在黑洞的情况下,我根据到事件视界的距离成比例地更改时间步长,以便测地线的精度大致与空间曲率成正比。这是获得准确结果的重要优化,同时保持计算成本相对较小。
示例着色器代码:
mat4 diag ( vec4 a )
{
return mat4 ( a . x , 0 , 0 , 0 ,
0 , a . y , 0 , 0 ,
0 , 0 , a . z , 0 ,
0 , 0 , 0 , a . w ) ;
}
mat4 Metric ( vec4 x )
{
//Kerr-Schild 坐标中的 Kerr-Newman 度量
const float a = 0.8 ;
常量 浮点数m = 1.0 ;
常量 浮点Q = 0.0 ;
vec3 p = x 。yzw ;
浮点rho = 点( p , p ) - a * a ;
浮动r2 = 0.5 *( rho + sqrt ( rho * rho + 4.0 * a * a * p .z * p .z ) ) ;浮点数r = sqrt ( r2 ) ; vec4 k = vec4 ( 1 , ( r * p . x + a * p . y ) / ( r2 + a
* a ) , ( r * p . y - a * p . x ) / ( r2 + a * a ) , p . z / r ) ;
浮点数f = r2 * ( 2.0 * m * r - Q * Q ) / ( r2 * r2 + a * a *p 。z * p 。);
返回f * mat4 ( k . x * k , k . y * k , k . z * k , k . w * k ) + diag ( vec4 ( - 1 , 1 , 1 , 1 ) ) ;
}
浮点哈密顿量( vec4 x , vec4 p )
{
mat4 g_inv =逆( Metric ( x ) ) ;
返回 0.5 *点( g_inv * p , p ) ;
}
vec4 HamiltonianGradient ( vec4 x , vec4 p )
{
const float eps = 0.001 ;
返回 ( vec4 (哈密顿量( x + vec4 ( eps , 0 , 0 , 0 ) , p ) ,
哈密顿量( x + vec4 ( 0 , eps , 0 , 0 ) ,p ) ,
哈密顿量( x + vec4 ( 0 , 0 , eps , 0 ) , p ) ,
哈密顿量( x + vec4 ( 0 , 0 , 0 , eps ) , p ) ) -哈密顿量( x , p ) ) / eps ;
}
void IntegrationStep ( inout vec4 x , inout vec4 p )
{
const float TimeStep = 0.15 ;
p = p -时间步长*哈密顿梯度( x , p ) ;
x = x +时间步长* inverse ( Metric ( x ) ) * p ;
}
vec4 GetNullMomentum ( vec4 x , vec3 dir )
{
return Metric ( x ) * vec4 ( 1.0 , 标准化( dir ) ) ;
}
vec3 GetDirection ( vec4 x , vec4 p )
{
vec4 dxdt = inverse ( Metric ( x ) ) * p ;
返回 标准化( dxdt . yzw ) ;
}
void TraceGeodesic ( inout vec3 pos , inout vec3 dir , inout float time )
{
vec4 x = vec4 (时间,位置) ;
vec4 p = GetNullMomentum ( x , dir ) ;
常量 int步骤= 256 ;
for ( int i = 0 ; i <步骤; i ++ )
{
IntegrationStep ( x , p ) ;
//例如,当 x 低于事件视界时,您可以在此处添加停止条件
}
位置= x 。yzw ;
时间= x 。x ;
dir = GetDirection ( x , p ) ;
}
void mainImage ( out vec4 fragColor , in vec2 fragCoord ) {
vec2 uv = 2.0 * ( fragCoord - 0.5 * iResolution . xy ) / max ( iResolution . x , iResolution . y ) ;
mat4 diag ( vec4 a )
{
return mat4 ( a . x , 0 , 0 , 0 ,
0 , a . y , 0 , 0 ,
0 , 0 , a . z , 0 ,
0 , 0 , 0 , a . w ) ;
}
mat4 Metric ( vec4 x )
{
//Kerr-Schild 坐标中的 Kerr-Newman 度量
const float a = 0.8 ;
常量 浮点数m = 1.0 ;
常量 浮点Q = 0.0 ;
vec3 p = x 。yzw ;
浮点rho = 点( p , p ) - a * a ;
浮动r2 = 0.5 *( rho + sqrt ( rho * rho + 4.0 * a * a * p .z * p .z ) ) ;浮点数r = sqrt ( r2 ) ; vec4 k = vec4 ( 1 , ( r * p . x + a * p . y ) / ( r2 + a
* a ) , ( r * p . y - a * p . x ) / ( r2 + a * a ) , p . z / r ) ;
浮点数f = r2 * ( 2.0 * m * r - Q * Q ) / ( r2 * r2 + a * a *p 。z * p 。);
返回f * mat4 ( k . x * k , k . y * k , k . z * k , k . w * k ) + diag ( vec4 ( - 1 , 1 , 1 , 1 ) ) ;
}
当然,这不是优化的限制。其他主要优化是计算度量的解析逆。对于一大类指标,您可以使用谢尔曼-莫里森公式[9]
一些需要记住的有用的事情
由于我们使用数值有限差分,因此结果实际上在很大程度上取决于浮点数的相对值。例如,远离坐标系中心,数值导数的精度会低很多,因此您需要改变 的大小eps以避免过多的数值噪声。此外,指标通常具有应该避免的数值奇点,除非您想获得 NaN 结果。
我通常避免使用球坐标中的度量,因为它们的极轴奇异性,它具有强烈的视觉效果,即使时间步长变化很小,也很难避免,尽管此类度量通常在数学上更简单,并且允许更大的时间步长而不破坏外观黑洞。对于球对称度量,例如非旋转黑洞和虫洞,有一个技巧可以完全避免极奇点!关于球对称的事情是,测地线总是在 2d 平面内移动,可以映射到坐标系的赤道平面,基本上将 3d + 时间问题简化为 2d + 时间。 (斯科特·曼利有一段视频解释了他如何渲染虫洞,在那里他使用了这个技巧+预先计算一个查找表来大大简化计算)