Sphere mapping 和 Cube mapping

Sphere mapping 和 Cube mapping也是常见的技术。 Sphere map更早,是较早提出的环境映射的方法。

 Sphere mapping 是基于这样一个事实:将一个理想高反射的球体置于场景中央,从一个角度无穷远处拍摄此球体,将得到一张全景图。例如:

 ---------------

 

 

 --------------------

当然说是全景图,但是在球体背对拍摄方向的场景是看不到的,所以这个全景图是言过其实的,但是的实际效果上是不错的。

通常我们在场景建模中朝z轴正方向,利用正交投影模拟无穷远处进行渲染就可以得到这个纹理图 或者其它方法。

 

如何将这个纹理应用与其他物体上呢?

设想我们面对一个茶壶,当我们眼睛(E点)看茶壶某点(A点)时,假设从我们眼睛发射出一条光线(A - E),那么这个光线在碰到A点时将被反射,该反射光线碰到周围场景一点B,则B点的颜色就应该是茶壶上那个点(A)的颜色。反射光线的向量值很容易计算(稍后)。

仔细考虑会发现,我们根据反射光线,在纹理图中找这样一个点,称为C点。 C点的反射光线的方向与我们知道的反射光线的方向一样,那么就应该将纹理图中的C点的颜色就应该被用于茶壶A点。

 

知道这一点之后,我们慢慢牵出更多的东西。

 

为避免疑惑,先介绍反射向量的求法。

反射光线这样计算:我们知道某顶点的坐标(A),知道眼睛的坐标(E),知道某顶点的法线:n

那么顶点A到E的向量: v = E - A;

则反射向量: r = 2 * (v · n)*n - v

其中v·n是v与n向量做点积

 

纹理图中的C点有反射光线吗?

当然有,需要你想象。想象在拍摄该纹理的时候,是从某角度无穷远处拍摄的。那么就相当于有一条来自某角度无穷远处的光线,碰到这个球体F点并被反射,反射光线碰到场景某点。如果这个反射光线与我们前面眼睛与茶壶形成的光线的反射光线一致,那么球体F点的颜色就可以被用于茶壶A点。而F点就记录在纹理图中。而且F点与纹理图中的点肯定存在一定的映射关系。

 

所以只要我们知道反射光线,然后找出与该反射光线的一致的球面的点就可以了。

 

知道一个反射光线,如何能在纹理图中找到对应的点呢?

将设将球体是单位球体,半径为1,置于(0,0,0)点,那么球体表面坐标点的值与球心到该点的向量值是一样的,而这个向量也就是球面该点的法线值。所以知道法线值就能知道球体坐标。这点很重要法线的值就是坐标值。

知道球体坐标就肯定能知道纹理图上的点。

 

法线很容易计算,我们知道此纹理是从(0,0,1)方向拍摄的,所以将知道的反射光线和(0,0,-1)(拍摄的反方向)相加,除以2就是得出了对应的法线值。

 

说的好绕,呵呵。

把上面的总结起来就一句话:

我们计算出某顶点的反射向量,然后通过和(0,0-1)相加再除以2得到球面的法线,法线的值就是球面坐标,然后通过球面坐标对应到纹理相应的点。

 

球面坐标到纹理坐标也很简单。假设球面坐标 S(a,b,c)

则纹理坐标 T(u,v):

u = s / 2 +0.5;

v = -b / 2 +0.5;

 

首先由于球面是单位球体,所以a,b,c的范围[-1,1],而我们的纹理坐标范围是[0,1],所以将坐标范围缩小一半,并右移0.5就得到纹理坐标范围。

又由于纹理坐标y朝下,而3d世界坐标轴y朝上,所以b要取反。

 

Effect文件代码:

float4x4 matWorld;
float4x4 matView;
float4x4 matWorldViewProjection;
float4 Eye;

texture SphereMap;

sampler SphereSampler = sampler_state
{
	Texture = <SphereMap>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
	MipFilter = LINEAR;
	AddressU = CLAMP;
	AddressV = CLAMP;
};

struct VS_INPUT
{
	float4 Pos : POSITION;
	float3 Norm : NORMAL;
};

struct VS_OUTPUT
{
	float4 Pos : POSITION;
	float2 Tex : TEXCOORD;
};

struct PS_INPUT
{
	float2 Tex : TEXCOORD;
};

VS_OUTPUT vs( VS_INPUT Input )
{
	VS_OUTPUT Output = ( VS_OUTPUT )0;
	Output.Pos = mul( Input.Pos, matWorldViewProjection );
	
	float3 WorldPos = mul( Input.Pos, matWorld );
	float3 WorldNorm = normalize( mul( Input.Norm, matWorld ) );
	
	float3 View = normalize( WorldPos - Eye );
	
	float3 Reflection = normalize( reflect( View, WorldNorm ) );
	float3 PhotoView = { 0, 0, -1 };
	float3 SphereNorm = normalize( Reflection + PhotoView );
	Output.Tex.x = SphereNorm.x * 0.5f + 0.5f;
	Output.Tex.y = -SphereNorm.y * 0.5f + 0.5f;
	
	return Output;
}

float4 ps( PS_INPUT Input ) : COLOR
{
	return tex2D( SphereSampler, Input.Tex );
}

technique T0
{
	pass P0
	{
		vertexshader = compile vs_2_0 vs();
		pixelshader = compile ps_2_0 ps();
	}
}

写这么长时间,哎,以后要精简啊。

 

Cube mapping


现在有了DirectX的支持实现Cube mapping已经是一件很简单的的事情了。

 

大体思想是这样的:通过相机在场景中央,朝着x,-x,y,-y,z,-z方向将场景渲染出6张纹理。然后用6张纹理组成一个立方体的6个面。这样一个真正的全景图组成了。然后就是通过计算顶点处反射向量(眼睛到顶点的向量的反射向量),去访问立方图。

 

顶点的反射向量在Sphere mapping已经说明了计算方法,计算出来了之后就这个反射向量去访问立方图就可以了。

在DirectX中上面的各种操作已经都有相应的接口去实现,如制作立方图有相应的IDirect3DCubeTexture9(立方图接口),ID3DXRendertoEnvMap(渲染环境图接口)。

在这里就不多说了,在早起的DirectX案例中有例子。

 

就说一说这个反射向量访问立方图的数学方法:

设反射向量是v,则从v中找到绝对值最大的分量,假如v(0.7,0,4,0.6),则从+x方向那个面的纹理中找,

 

然后直接通过向量延伸,计算+x面相交的交点,然后将交点映射到[0,1]的纹理范围内即可。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值