随机数、随机方向、随机点生成算法

        关于随机数生成算法,这是所有其它随机生成算法必须用到的基础。
        在游戏中自定义一个随机生成函数是很有必要的,比如要做一个联网的扑克游戏,开始发牌的时候并不用把所有的牌都一张张发给玩家,只需要发送一个用于生成随机数的种子,玩家就可以知道完整的发牌顺序。这里有个前提,在种子相同时每个玩家生成的随机数序列必须是一样的,如果使用系统api自带的随机函数很难保证这一点,自定义随机函数就没问题了。

        再比如游戏中的粒子系统,粒子运动看似杂乱无章,但是只要给的随机种子一致并且更新步长也一致,两个粒子系统中的粒子是可以做到完全同步的,这就为联网游戏中同步粒子动画提供了一个思路。

        再如随机生成场景类游戏,联网的各玩家只需要一个随机种子就可以同步整个场景。

关于具体随机数生成算法,网上介绍很多,这里摘抄两版:

一种,快速但不是很精确:   

    static unsigned int rand_seed = 0;
    void   SrandFast(unsigned int seed)
    {
        rand_seed = seed;
    }
    unsigned int RandFast()
    {
        //return rand();
        //1 线性同余方法(LCG) Xn+1 ≡ (a* Xn + c )% m 弊端是相邻点之间的相关性  将相邻3个数作为坐标绘制点,会聚集到n个明显的平面
        rand_seed = (rand_seed * 16807) % ((1 << 31) - 1);
        return rand_seed%RandMax;
    }

  另一种,精确但不是很快速:        

    #define LOWER_MASK            0x7fffffff
    #define M                     397
    #define MATRIX_A              0x9908b0df
    #define N                     624
    #define TEMPERING_MASK_B      0x9d2c5680
    #define TEMPERING_MASK_C      0xefc60000
    #define TEMPERING_SHIFT_L(y)  (y >> 18)
    #define TEMPERING_SHIFT_S(y)  (y << 7)
    #define TEMPERING_SHIFT_T(y)  (y << 15)
    #define TEMPERING_SHIFT_U(y)  (y >> 11)
    #define UPPER_MASK            0x80000000

    static int              k = 1;
    static unsigned long    mag01[2] = {0x0, MATRIX_A};
    static unsigned long    ptgfsr[N]={0};
    static bool init = true;
    void   Srand(unsigned int seed)
    {
        if (seed == 0)
        {
            seed = 1;
        }
        ptgfsr[0] = seed;
        for(k = 1; k < N; k++)
        {
            ptgfsr[k] = 69069 * ptgfsr[k - 1];
        }
        k = 1;
        init = false;
    }

    unsigned int Rand()
    {
        if (init)
        {
            Srand(1);
            init = false;
        }
        //马特赛特旋转法
        int                    kk;
        unsigned long        y;
        if(k == N)
        {
            for(kk = 0; kk < N - M; kk++)
            {
                y = (ptgfsr[kk] & UPPER_MASK) | (ptgfsr[kk + 1] & LOWER_MASK);
                ptgfsr[kk] = ptgfsr[kk + M] ^ (y >> 1) ^ mag01[y & 0x1];
            }
            for(; kk < N - 1; kk++)
            {
                y = (ptgfsr[kk] & UPPER_MASK) | (ptgfsr[kk + 1] & LOWER_MASK);
                ptgfsr[kk] = ptgfsr[kk + (M - N)] ^ (y >> 1) ^ mag01[y & 0x1];
            }
            y = (ptgfsr[N - 1] & UPPER_MASK) | (ptgfsr[0] & LOWER_MASK);
            ptgfsr[N - 1] = ptgfsr[M - 1] ^ (y >> 1) ^ mag01[y & 0x1];
            k = 0;
        }
        y = ptgfsr[k++];
        y ^= TEMPERING_SHIFT_U(y);
        y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B;
        y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C;
        y ^= TEMPERING_SHIFT_L(y);
        return y%RAND_MAX;
    }

其它衍生随机数生成:其中列举了一些易错点

    随机生成方向,对于2D的很简单,随机一个角度然后sin cos即可。
    对于3D的就不简单了, 首先想到的方法一,在立方体内随机取点然后归一化,结果是不准确的,仔细观察立方体八个角上概率会偏大,当然如果不介意些许误差也是可以用的。  
    void   RandDir(vec3& dir)
    {
        //不准确   加上判断球内就准确了
        dir = vec3(RandRange(-1.0f,1.0f),RandRange(-1.0f,1.0f),RandRange(-1.0f,1.0f));
        dir.Normalize();
    }
    又想到了方法二:先将向量a[0,0,1]绕y轴随机旋转0~360度,然后再绕x轴旋转0~360度。这个方法貌似正确,其实错的更离谱,仔细观察会发现靠近x轴正负1位置概率偏大。
    //float theta1 = RandRange(0.0f,TWOPI);
    //float theta2 = RandRange(0.0f,TWOPI);
    //float len = RandRange(-1.0,1.0);
    //float radLevel = cosf(theta1)*len;      //水平半径
    //float ex = radLevel * cosf(theta2);
    //float ey = sinf(theta1)*len;
    //float ez = radLevel * sinf(theta2);
    主要出错在第二步,因为正确做法需要的是绕新frame下的x轴旋转,算法变得复杂。关于下面公式是由自己写的小工具自动推导出来的,工具源码有空再贴出来。
    void   RandDir(vec3& dir)
    {
        float A = RandRange(0.0f,TWOPI);
        float B = RandRange(0.0f,TWOPI);
        float cosA = cos(A);
        float cosB = cos(B);
        float sinA = sin(A);
        float sinB = sin(B);
        dir.x =(((1-cosB)*cosA*cosA+cosB)*sinA-(1-cosB)*sinA*cosA*cosA); 
        dir.y = -(sinA*sinB*sinA+cosA*sinB*cosA)                       ; 
        dir.z =(((1-cosB)*sinA*sinA+cosB)*cosA-(1-cosB)*sinA*cosA*sinA); 
    }
    
    随机生成球内一个点:第一步先用上面的方法随机取一个方向,第二步在半径内随机取一个长度开三次方后付给方向,开三次方是必须的,否则靠近球心处概率偏大,当然如果正好需要这种边缘弱化的分布也可以不开方。(对于2D一般是开二次方)
    vec3   RandomPosInSphere(float rad)
    {
        vec3 pos = RandDir();
        pos *= pow(randRange(0.0f,rad),1.0f/3); 
        return pos;
    }
    
    
    随机生成在三角面上一个点的位置:
    void RandomPosOnTrigon(const vec3& pos0,const vec3& pos1,const vec3& pos2,vec3& res)
    {
        //错误的算法一:直接插值不是均匀分布,靠近重心密度更大
        //vec3 rat;
        //rat[0] = Rand()%1000;
        //rat[1] = Rand()%1000;
        //rat[2] = Rand()%1000;
        //float sum = (rat[0]+rat[1]+rat[2]);
        //if (sum==0)
        //{
        //    res = pos0;
        //  return;
        //}
        //sum = 1.0f/sum;
        //rat *= sum;
        //pos0 = pos0*rat[0] + pos1*rat[1] + pos2*rat[2];

        //错误的算法二:先插值一条边,再用得到的点和对顶点插值,不是均匀分布,靠近对顶点密度更大

        //正确的算法一:先插值一条边,再用得到的点和对顶点插值,插值时比率要开方, 因为微分法面积随距离平方而变大
        //此方法可以方便的扩展到3维, 取三棱锥内的随机点:先取底面随机点,再和最上面的顶点根据距离开三次方插值
        float rat1 = (Rand()%1000)/999.0f;//0~1
        //插值pos0, pos1
        res = pos0*rat1 + pos1*(1-rat1);
        float rat2 = sqrt((Rand()%1000)/999.0f);//sqrt (0~1)
        //插值posMid, pos2
        res = res*rat2 + pos2*(1-rat2);

        正确的算法二:取平行四边形内的随机点,如果不在三角形内则旋转对称
        //float rat1 = (Rand()%1000)/999.0f;//0~1
        //float rat2 = (Rand()%1000)/999.0f;
        //if (rat1+rat2>1)
        //{
        //    //不在三角形内 旋转对称
        //    rat1 = 1-rat1;
        //    rat2 = 1-rat2;
        //}
        //res = pos0 + (pos1-pos0)*rat1 + (pos2-pos0)*rat2;
    }

    
    有时可能需要的不是平均分布随机函数而是高斯分布:
    //正太分布随机函数:平均值0, 标准偏差1。 68.26%的概率在[-1,1]内
    float  RandGauss( )
    {
        static float U, V;
        static int phase = 0;
        float Z;

        if(phase == 0)
        {
            U = Rand() / (RandMax + 1.0);
            V = Rand() / (RandMax + 1.0);
            Z = sqrt(-2.0 * log(U))* sin(2.0 * _PI * V);
        }
        else
        {
            Z = sqrt(-2.0 * log(U)) * cos(2.0 * _PI * V);
        }
        phase = 1 - phase;
        return Z;
    }

    //正太分布随机函数:平均值m, 标准偏差sd 
    float  RandGauss(float m,float sd)
    {
        float X = m + sd* RandGauss();
        return X;
    }


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值