电子游戏编程中的数学
Maths in Programing Video Games
我刚刚完成大学二年级的游戏课程,数学和游戏编程之间的关系一直困扰着我。到目前为止,我一直在游戏中使用Vectors
, Matrices
和Quaternions
,我可以理解它们如何适合游戏。
这是一篇关于数学和实时图形编程之间关系的General Question
,我很好奇数学是如何动态的。是否所有的公式和导数都是预定义的(半定义的)?
实时计算导数/积分是否可行?
这些是我不明白它们如何适合编程/数学作为一个例子。
-
MacLaurin/Talor Series
我可以看到这是有用的,但是你必须传递你的函数和它的导数,或者你可以传递一个函数,让它为你计算导数?MacLaurin(sin(X)); or MacLaurin(sin(x), cos(x), -sin(x));
-
Derivatives /Integrals
这与第一点有关。计算函数的y'
是在运行时动态完成的,还是在set函数中使用变量静态完成的?f = derive(x); or f = derivedX;
-
Bilnear Patches
我们知道这是一种可能生成小块景观的方法,可以"缝合"在一起,这是发生在游戏中的事情吗?我从未听说过这种方法(我的知识非常有限)与程序方法或其他方法一起使用。到目前为止,我所做的工作涉及到处理顶点信息的数组。
对不起,如果这是离题,但这里的社区似乎是正确的,在这种事情上。
谢谢。
Skizz的答案从字面上看是正确的,但是只需要做一点小小的改变就可以计算c++函数的导数。我们将skizz的函数f
修改为
template<class Float> f (Float x)
{
return x * x + Float(4.0f) * x + Float(6.0f); // f(x) = x^2 + 4x + 6
}
现在可以编写一个c++函数来计算f对x的导数。这里是一个完整的自包含程序来计算f的导数。它是精确的(机器精度),因为它没有使用像有限差分这样不准确的方法。我在我写的一篇论文中解释了它是如何工作的。它可以推广到高阶导数。注意,大部分工作是由编译器静态完成的。如果您进行了优化,并且您的编译器进行了适当的内联,那么它应该与您为简单函数手工编写的任何东西一样快。(有时更快!特别是,它很好地分摊了同时计算f和f'的成本,因为它使公共子表达式消除比为f和f'分别编写函数更容易被编译器发现。
using namespace std;
template<class Float>
Float f(Float x)
{
return x * x + Float(4.0f) * x + Float(6.0f);
}
struct D
{
D(float x0, float dx0 = 0) : x(x0), dx(dx0) { }
float x, dx;
};
D operator+(const D &a, const D &b)
{
// The rule for the sum of two functions.
return D(a.x+b.x, a.dx+b.dx);
}
D operator*(const D &a, const D &b)
{
// The usual Leibniz product rule.
return D(a.x*b.x, a.x*b.dx+a.dx*b.x);
}
// Here's the function skizz said you couldn't write.
float d(D (*f)(D), float x) {
return f(D(x, 1.0f)).dx;
}
int main()
{
cout << f(0) << endl;
// We can't just take the address of f. We need to say which instance of the
// template we need. In this case, f<D>.
cout << d(&f<D>, 0.0f) << endl;
}
打印结果6
和4
,正如您所期望的那样。尝试其他函数f
。一个很好的练习是尝试制定规则来允许减法,除法,三角函数等。
2)导数和积分通常不会在大型数据集上实时计算,因为它太昂贵了。相反,它们是预先计算好的。例如(在我的脑海中),为了渲染一个单一的散射介质,Bo Sun等人使用他们的"airlight模型",该模型由许多代数快捷方式组成,以获得一个预先计算的查找表。
3)流化大数据集是一个大课题,特别是在地形方面。
你在游戏中遇到的许多数学都是为了解决非常具体的问题,并且通常都很简单。线性代数的应用远远超过任何微积分。在图像(我最喜欢这一点)中,许多算法来自学术界的研究,然后由游戏程序员修改它们以提高速度:尽管现在甚至学术研究也将速度作为目标。
我推荐《Real time collision detection》和《Real time rendering》这两本书,它们包含了游戏引擎编程中使用的大多数数学和概念的核心。
我认为你对c++语言本身的理解有一个根本性的问题。c++中的函数与数学函数不同。因此,在c++中,您可以定义一个函数(我现在将其称为方法以避免混淆)来实现一个数学函数:
float f (float x)
{
return x * x + 4.0f * x + 6.0f; // f(x) = x^2 + 4x + 6
}
在c++中,除了获取给定x的f(x)的值之外,没有办法使用f方法做任何事情。数学函数f(x)可以很容易地转换,例如f'(x),在上面的例子中是f'(x) = 2x + 4。要在c++中做到这一点,你需要定义一个方法df (x):
float df (float x)
{
return 2.0f * x + 4.0f; // f'(x) = 2x + 4
}
你不能这样做:
get_derivative (f(x));
并让方法get_derivative
为您转换方法f(x)。
同样,你必须确保当你想求f的导数时你调用了df方法。如果你不小心调用了求g的导数的方法,你的结果将是错误的。
对于给定的x,我们可以近似f(x)的导数:
float d (float (*f) (float x), x) // pass a pointer to the method f and the value x
{
const float epsilon = a small value;
float dy = f(x+epsilon/2.0f) - f(x-epsilon/2.0f);
return epsilon / dy;
}
但这是非常不稳定和非常不准确的。
现在,在c++中你可以在这里创建一个类来提供帮助:
class Function
{
public:
virtual float f (float x) = 0; // f(x)
virtual float df (float x) = 0; // f'(x)
virtual float ddf (float x) = 0; // f''(x)
// if you wanted further transformations you'd need to add methods for them
};
并创建特定的数学函数:
class ExampleFunction : Function
{
float f (float x) { return x * x + 4.0f * x + 6.0f; } // f(x) = x^2 + 4x + 6
float df (float x) { return 2.0f * x + 4.0f; } // f'(x) = 2x + 4
float ddf (float x) { return 2.0f; } // f''(x) = 2
};
并将该类的实例传递给序列展开例程:
float Series (Function &f, float x)
{
return f.f (x) + f.df (x) + f.ddf (x); // series = f(x) + f'(x) + f''(x)
}
但是,我们仍然需要为函数的导数创建一个方法,但至少我们不会不小心调用错误的方法。
现在,正如其他人所说,游戏倾向于速度,所以许多数学都被简化了:插值,预计算表等
游戏中的大多数数学都被设计成尽可能便宜的计算,以速度为代价而不是准确性。例如,许多数字运算使用整数或单精度浮点数,而不是双精度浮点数。
不确定你的具体例子,但如果你可以事先为导数定义一个便宜的(计算)公式,那么这比在飞行中计算东西更可取。
在游戏中,性能是最重要的。你不会发现任何可以静态完成的事情都是动态完成的,除非它能显著提高视觉保真度。
您可能对编译时符号区分感兴趣。这(原则上)可以用c++模板完成。我不知道游戏是否会在实践中这么做(符号区分对于正确编程来说可能过于昂贵,而如此广泛的模板使用可能在编译时间上过于昂贵,我不知道)。
然而,我认为你可能会发现这个话题的讨论很有趣。在谷歌上搜索"c++模板符号派生"会得到一些文章。
如果你对符号计算和导数计算感兴趣,有很多很好的答案。
然而,作为理性检查,这种符号(分析)演算在游戏环境中是不实用的。
根据我的经验(在计算机视觉中更多的是3D几何而不是游戏),3D几何中的大多数微积分和数学都是通过离线计算的方式来实现的,然后通过编码来实现这些数学。你很少需要动态地象征性地计算东西,然后用这种方法得到动态的解析公式。
游戏程序员可以验证吗?
1), 2)
MacLaurin/Taylor级数(1)在任何情况下都由导数(2)构造。
是的,您不太可能需要在运行时象征性地计算其中的任何一个-但是如果您需要的话,user207442的答案肯定是很棒的。
你所发现的是你需要执行一个数学计算,你需要在合理的时间内完成它,或者有时非常快。要做到这一点,即使你重用了别人的解决方案,你也需要了解基本的分析。
如果你必须自己解决问题,好处是你通常只需要一个近似的答案。这意味着,例如,一个级数类型的展开可以很好地允许你将一个复杂的函数简化为一个简单的线性或二次函数,这将是非常快的。
对于积分,你通常可以用数值方法计算结果,但它总是比解析解慢得多。区别很可能就是实用与否的区别。
简而言之:是的,你需要学习数学,但是为了编写程序而不是让程序为你做。
- 有一个打印语句的函数是一种糟糕的编程实践吗
- 我是C++编程的新手,这些代码之间有什么区别,我应该使用哪一个
- 模板元编程:如何将参数包组合成新的参数包
- Qt Q串行端口未编程设备未关闭
- 模板元编程 - 尝试实现维度分析
- 我是编程新手
- C++编程从外部文本文件定义数组大小
- 了解算法的性能差异(如果以不同的编程语言实现)
- 使用 Gtkmm 以编程方式选择 Gtk::TextView 中的文本
- 如何将可变参数模板转换为多个单个模板?(C++竞争编程调试模板)
- 使用命名空间正确编程
- C++编程:运算符重载中的引用如何工作?
- Arduino 模块化编程与全局和设置
- C++ 运算符修改/元编程策略,用于不那么冗长的语法
- 在没有管理员权限的情况下,在 c++ 中以编程方式将程序添加到启动
- 如何以编程方式将音频从任何录制设备路由到任何播放设备
- 试图修复一个错误,该错误不会让我开始编程其余部分
- C++模板编程设计问题 - 根据输入文件返回不同的类型
- Frank Luna 在他的书"使用 DirectX12 进行 3D 游戏编程"的介绍中盒子示例的问题
- 电子游戏编程中的数学