【快乐数学】雷神III中平方根倒数程序分析

获取代码

代码在github上
git clone https://github.com/raspberrypi/quake3.git
代码目录

在这款游戏中有一个经典的计算平方根的倒数的代码

float Q_rsqrt( float number )//函数 求平方根的倒数

即:
y = 1 x y=\frac{1}{\sqrt{x}} y=x 1

前言

在游戏中 模拟物理现象,例如计算光反射之类的常会用到
例如:雷神III的里面代码

code/renderer/tr_shade_calc.c

计算反射 光的alpha值 不重要 可以略过 goto 正文

/*
** RB_CalcSpecularAlpha
**
** Calculates specular coefficient and places it in the alpha channel
*/
vec3_t lightOrigin = { -960, 1980, 96 };		// FIXME: track dynamically

void RB_CalcSpecularAlpha( unsigned char *alphas ) {
	int			i;
	float		*v, *normal;
	vec3_t		viewer,  reflected;
	float		l, d;
	int			b;
	vec3_t		lightDir;
	int			numVertexes;

	v = tess.xyz[0];
	normal = tess.normal[0];

	alphas += 3;

	numVertexes = tess.numVertexes;
	for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4) {
		float ilength;

		VectorSubtract( lightOrigin, v, lightDir );
//		ilength = Q_rsqrt( DotProduct( lightDir, lightDir ) );
		VectorNormalizeFast( lightDir );

		// calculate the specular color
		d = DotProduct (normal, lightDir);
//		d *= ilength;

		// we don't optimize for the d < 0 case since this tends to
		// cause visual artifacts such as faceted "snapping"
		reflected[0] = normal[0]*2*d - lightDir[0];
		reflected[1] = normal[1]*2*d - lightDir[1];
		reflected[2] = normal[2]*2*d - lightDir[2];

		VectorSubtract (backEnd.or.viewOrigin, v, viewer);
		ilength = Q_rsqrt( DotProduct( viewer, viewer ) );
		l = DotProduct (reflected, viewer);
		l *= ilength;

		if (l < 0) {
			b = 0;
		} else {
			l = l*l;
			l = l*l;
			b = l * 255;
			if (b > 255) {
				b = 255;
			}
		}

		*alphas = b;
	}
}


正文:

代码:
code\tools\lcc\src\c.h

.....
typedef union {
	float f;
	int i;
	unsigned int ui;
} floatint_t
.....

code/qcommon/q_math.c

/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
	floatint_t t;
	float x2, y;
	const float threehalfs = 1.5F;

	x2 = number * 0.5F;
	t.f  = number;
	t.i  = 0x5f3759df - ( t.i >> 1 );               // what the fuck?
	y  = t.f;
	y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//	y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

	return y;
}

浮点数float存储值为I,浮点数真实值为val
I = ( − 1 ) S + 2 23 × E + M E = e + 127 M = m × 2 23 v a l = ( − 1 ) s × ( 1 + m ) × 2 e I={(-1)}^S+{2^{23}}\times{E}+M\\ E=e+127\\ M=m\times{2^{23}}\\ val=(-1)^s\times(1+m)\times{2^e} I=(1)S+223×E+ME=e+127M=m×223val=(1)s×(1+m)×2e
S是1bit (第31位)符号位 1/0
E是8bit (第23-30位)范围-128~127 即 正3需要加127 表示指数位
M是23bit(第0-22位)表示小数位
E与e以及M与m关系
E = e + B     ( B = 127 ) M = L × m     ( L = 2 23 ) E=e+B \ \ \ (B=127) \\ M=L\times{m} \ \ \ (L=2^{23}) E=e+B   (B=127)M=L×m   (L=223)

举个栗子:
8.25在内存中存储的方式
由 v a l = ( 1 + m ) × 2 e 得 到 8.25 = 1000.01 B = 1.00001 × 2 3 B ( B 表 示 是 二 进 制 ) 即 : e = 3 m = 0.00001 B   ( 去 掉 最 前 面 的 1 因 为 默 认 是 1 ) S = 0 E = 127 + 3 = 100   0001   0 B M = 000   0100   0000   0000   0000   0000 B I = ( − 1 ) S + 2 23 × E + M   ( 将 值 填 充 进 去 ) I = 0 /   100   0001   0 / 000   0100   0000   0000   0000   0000 B I = 0100   0001   0000   0100   0000   0000   0000   0000 B I = 0 X 4   1   0   4   0   0   0   0 = 0 X 41040000 由 val=(1+m)\times{2^e}\\ 得到 8.25 =1000.01B=1.00001\times{2^3}B(B表示是二进制)\\ 即:\\ e=3\\ m=0.00001B \ (去掉最前面的1因为默认是1)\\ S=0\\ E=127+3=100\ 0001\ 0B\\ M= 000\ 0100\ 0000\ 0000\ 0000\ 0000B\\ I={(-1)}^S+{2^{23}}\times{E}+M\ (将值填充进去)\\ I= 0 /\ 100\ 0001\ 0 /000\ 0100\ 0000\ 0000\ 0000\ 0000B\\ I=0100\ 0001\ 0000\ 0100\ 0000\ 0000\ 0000\ 0000B\\ I=0X4\ 1\ 0\ 4\ 0\ 0\ 0\ 0 = 0X41040000 val=(1+m)×2e8.25=1000.01B=1.00001×23B(B)e=3m=0.00001B (11)S=0E=127+3=100 0001 0BM=000 0100 0000 0000 0000 0000BI=(1)S+223×E+M ()I=0/ 100 0001 0/000 0100 0000 0000 0000 0000BI=0100 0001 0000 0100 0000 0000 0000 0000BI=0X4 1 0 4 0 0 0 0=0X41040000
即 8.25在内存中存储的结构为

推到
y = 1 x log ⁡ 2 y = log ⁡ 2 x − 1 2 ∵   y ≥ 0   x ≥ 0 且 y = ( 1 + m y ) × 2 e y x = ( 1 + m x ) × 2 e x ∴ log ⁡ 2 ( ( 1 + m y ) × 2 e y ) = − 1 2 × ( log ⁡ 2 ( ( 1 + m x ) × 2 e x ) ) l o g 2 ( 1 + m y ) + e y = − 1 2 × ( l o g 2 ( 1 + m x ) + e x )    ( 公 式 1 ) y=\frac{1}{\sqrt{x}}\\ \log_{2}{y} =\log_{2}{x^{-\frac{1}{2}}}\\ \because \ y\ge0 \ x\ge0\\ 且\\ y=(1+m_y)\times{2^{e_y}}\\ x=(1+m_x)\times{2^{e_x}}\\ \therefore \\ \log_{2}{((1+m_y)\times{2^{e_y}})} =-\frac{1}{2}\times( \log_{2}{((1+m_x)\times{2^{e_x}}}))\\ log_2{(1+m_y)}+{e_y}=-\frac{1}{2}\times(log_2{(1+m_x)}+{e_x} )\ \ (公式 1) y=x 1log2y=log2x21 y0 x0y=(1+my)×2eyx=(1+mx)×2exlog2((1+my)×2ey)=21×(log2((1+mx)×2ex))log2(1+my)+ey=21×(log2(1+mx)+ex)  (1)
研究公式:
f ( x ) = l o g 2 ( 1 + x )   x ϵ [ 0 , 1 ] 与 之 拟 合 的 函 数 f ( x ) = x + b f_{(x)}=log_2{(1+x)} \ x\epsilon{[0,1]}\\ 与之拟合的函数\\ f_{(x)}=x+b f(x)=log2(1+x) xϵ[0,1]f(x)=x+b
公式1:
l o g 2 ( 1 + m y ) + e y = − 1 2 × ( l o g 2 ( 1 + m x ) + e x ) b + m y + e y = − 1 2 ( b + m x + e x ) ∵ e = E − B   且   m = M L b + M y L + E y − B = − 1 2 × ( b + M x L + E x − B ) log_2{(1+m_y)}+{e_y}=-\frac{1}{2}\times(log_2{(1+m_x)}+{e_x} ) \\ b+m_y+{e_y}=-\frac{1}{2}{(b+m_x+{e_x} )}\\ \because e=E-B \ 且\ m=\frac{M}{L}\\ b+\frac{M_y}{L}+{E_y}-B=-\frac{1}{2}\times{(b+\frac{M_x}{L}+{E_x}-B)} log2(1+my)+ey=21×(log2(1+mx)+ex)b+my+ey=21(b+mx+ex)e=EB  m=LMb+LMy+EyB=21×(b+LMx+ExB)
整理公式得到
M y + L × E y = 3 2 × ( B − b ) − 1 2 × ( M x + L × E x ) 即 I y = k − ( I x > > 2 ) M_y+L\times{E_y}=\frac{3}{2}\times{(B-b)}-\frac{1}{2}\times{(M_x+L\times{E_x})}\\ 即\\ I_y=k-(I_x>>2) My+L×Ey=23×(Bb)21×(Mx+L×Ex)Iy=k(Ix>>2)

迭代:

验证部分拟合

利用windows自带的计算器进行画图得到图像
f ( x ) = l o g 2 ( 1 + x )    x ϵ [ 0 , 1 ] f ( x ) = x + b    x ϵ [ 0 , 1 ]    f_{(x)}=log_2{(1+x)} \ \ x\epsilon{[0,1]} \\ f_{(x)}=x+b \ \ x\epsilon{[0,1]} \ \ f(x)=log2(1+x)  xϵ[0,1]f(x)=x+b  xϵ[0,1]  
设置b为值0.0450615 经验
在这里插入图片描述
拟合情况很近似
关于b的取值0.0450165的值

https://zhuanlan.zhihu.com/p/79699768

验证浮点数

#include <stdio.h>
#include <stdlib.h>



int main(int argc, char * argv[])
{
    float  n = 8.25;

    printf("0x%x \n", *(unsigned long *)&n);
    
    return 0;
}

在这里插入图片描述

扩展发散:

求平方根

李永乐老师的教授的求平方根的办法:

s = a + b 2 a + b 2 a + b 2 a + b 2 a + . . . \sqrt{s}=a+\frac{b}{2a+\frac{b}{2a+\frac{b}{2a+\frac{b}{2a+...}}}} s =a+2a+2a+2a+2a+...bbbb

#include <stdio.h>
#include <stdlib.h>

#define SPEC 10 //迭代10次的精度

float q_sqrt(float num)
{
    int  i = 0;
    int  lim = (int) num/2;
    
    for(i = 0; i < lim ; i++)
    {
    
        if( i * i > num)
        {
            i --;
            break;
        }
    }

    int     a = i;
    float   b = num - i*i ;
    float   t = b / (2*a) ;
    
    for (i = 0; i < SPEC; i++)
    {
        t = b/(2*a + t);
    }
    
    return a+t;
}


int main(int argc, char * argv[])
{
    float ret = 0.0;
    
    ret = q_sqrt(3.0);

    printf("%f \n", ret);

    return 0;
}

雷神之锤

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值