我正在做一个小型游戏物理网络项目,我正在尝试使用本指南优化我发送的数据包:
https://gafferongames.com/post/snapshot_compression/
在“优化四元数”部分中,它说:
由于数值精度问题,不要总是丢弃相同的组件。相反,找到具有最大绝对值的组件并使用两位[0,3](0 = x,1 = y,2 = z,3 = w)对其索引进行ENCODE,然后发送最大组件的索引和网络上最小的三个组件
现在我的问题是,我如何编码一个低至2位的整数...或者我误解了这个任务?
我对压缩数据知之甚少,但将4字节整数(32位)减少到只有2位似乎对我来说有点疯狂。这甚至可能,还是我完全误解了一切?
编辑: 这是我到目前为止的一些代码:
void HavNetConnection::sendBodyPacket(HavNetBodyPacket bp)
{
RakNet::BitStream bsOut;
bsOut.Write((RakNet::MessageID)ID_BODY_PACKET);
float maxAbs = std::abs(bp.rotation(0));
int maxIndex = 0;
for (int i = 1; i < 4; i++)
{
float rotAbs = std::abs(bp.rotation(i));
if (rotAbs > maxAbs) {
maxAbs = rotAbs;
maxIndex = i;
}
}
bsOut.Write(bp.position(0));
bsOut.Write(bp.position(1));
bsOut.Write(bp.position(2));
bsOut.Write(bp.linearVelocity(0));
bsOut.Write(bp.linearVelocity(1));
bsOut.Write(bp.linearVelocity(2));
bsOut.Write(bp.rotation(0));
bsOut.Write(bp.rotation(1));
bsOut.Write(bp.rotation(2));
bsOut.Write(bp.rotation(3));
bsOut.Write(bp.bodyId.toRawInt(bp.bodyId));
bsOut.Write(bp.stepCount);
// Send body packets over UDP (UNRELIABLE), priority could be low.
m_peer->Send(&bsOut, MEDIUM_PRIORITY, UNRELIABLE,
0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
}
答案 0 :(得分:2)
我猜他们希望你将2位装入你已经发送的某个值,它不需要所有可用位,或者将几个小位字段打包到一个int中进行传输
你可以这样做:
int address_list;
// set address to w (11)
address_list = w;
// 0000 0011
// bit shift left by 2 bits
address_list = address_list << 2;
// 0000 1100
// now add address x (00)
address_list |= x;
// 0000 1100
// bit shift left 2 more bits
address_list = address_list << 2;
// 0011 0000
// add the address y (01)
address_list |= y;
// 0011 0001
// bit shift left 2 more bits
address_list = address_list << 2;
// 1100 0100
// add the address z. (10)
address_list |= z;
// 1100 0110
// w x y z are now in the lower byte of 'address_list'
在接收端:
{{1}}
你可以打包&#39;以这种方式位,一次任意数量的位。
{{1}}
这将4个地址打包到&#39; address_list&#39 ;;
的低位字节你只需要在另一端进行拆包。
这有一些实现细节可供解决。现在只有30位用于该值,而不是32.如果数据是带符号的int,则还有更多工作要做,以避免将符号位向左移,等等。
但是,从根本上说,这就是你可以将位模式填充到你发送的数据中的方式。
显然,这假设发送比将比特打包成字节和整数等工作更昂贵。通常情况下,特别是在涉及低波特率的情况下,如串行端口。
答案 1 :(得分:2)
解决您问题的最简单方法是使用bitfields:
// working type (use your existing Quaternion implementation instead)
struct Quaternion{
float w,x,y,z;
Quaternion(float w_=1.0f, float x_=0.0f, float y_=0.0f, float z_=0.0f) : w(w_), x(x_), y(y_), z(z_) {}
};
struct PacketQuaternion
{
enum LargestElement{
W=0, X=1, Y=2, Z=3,
};
LargestElement le : 2; // 2 bits;
signed int i1 : 9, i2 : 9, i3 : 9; // 9 bits each
PacketQuaternion() : le(W), i1(0), i2(0), i3(0) {}
operator Quaternion() const { // convert packet quaternion to regular quaternion
const float s = 1.0f/float(1<<8); // scale int to [-1, 1]; you could also scale to [-sqrt(.5), sqrt(.5)]
const float f1=s*i1, f2 = s*i2, f3 = s*i3;
const float f0 = std::sqrt(1.0f - f1*f1-f2*f2-f3*f3);
switch(le){
case W: return Quaternion(f0, f1, f2, f3);
case X: return Quaternion(f1, f0, f2, f3);
case Y: return Quaternion(f1, f2, f0, f3);
case Z: return Quaternion(f1, f2, f3, f0);
}
return Quaternion(); // default, can't happen
}
};
如果您看一下这个生成的汇编程序代码,您会看到一些转移,以便将le
和i1
提取到i3
- 基本上与您编写的代码相同手动也是。
你的PacketQuaternion
结构总是会占用整个字节数,因此(在任何非常规平台上)你仍然会浪费3位(你可以在这里使用每个整数字段10位,除非你有其他用于那些位。)
我遗漏了从常规四元数转换为PacketQuaternion
的代码,但这也应该相对简单。
一般情况下(与涉及网络时一样),要特别注意数据在各个方向正确转换,尤其是涉及不同的架构或不同的编译器时!
此外,正如其他人所指出的那样,确保在进行积极优化之前,网络带宽确实是瓶颈。
答案 2 :(得分:-1)
这里有很多可能的理解和误解。 ttemple解决了发送少于一个字节的技术问题。 我想重申更多的理论观点。
你最初误解了引用的段落。 我们不使用两位来说“不发送2121387”, 但要说“不发送z-component”。 这些完全匹配应该很容易看到。
如果要发送一个32位整数,可能会占用2 ^ 32个可能的值, 你需要至少 32位。 由于n位最多可以表示2 ^ n个状态, 任何少量的比特都是不够的。
超出实际问题: 当我们放松要求我们将始终使用2位 并且有足够强大的假设 关于价值的概率分布, 我们可以得到比特数的期望值。 像这样的想法会在链接文章中的所有地方使用。
设c为几乎所有时间都为0的整数(比如97%) 并且可以在剩余的时间内取任何价值(3%)。 然后我们可以用一点来说“c是否为零” 而且大部分时间都不需要进一步的位。 在c不为零的情况下, 我们花了另外32位来定期编码。 总的来说,我们平均需要0.97 * 1 + 0.03 *(1 + 32)= 1.96位 。 但我们需要33位有时, 这使我与之前的不可能性断言兼容。
根据你的背景(数学,比特等),它可能看起来像一块巨大的,不可知的黑魔法。 (事实并非如此。你可以学习这些东西。) 你似乎没有完全迷失和快速学习 但我同意Remy Lebeau 你似乎超出了自己的深度。
你真的需要这样做吗? 或者你是否过早优化? 如果它运行良好,让它运行。 专注于重要的事情。