20.3 随机性

在粒子系统中,我们希望粒子的行为相似,但又不完全相同;换句话说,我们希望为粒子系统加入一些随机性。例如,当模拟雨景时,我们不希望所有的雨点都按照完全相同的方式下落;我们希望它们从不同的位置下落,而且角度和速度都稍微有一点儿不同。为了实现粒子系统的随机性,我们在MathHelper.h/.cpp文件中定义了RandFRandUnitVec3函数:

// 返回[0, 1)区间的浮点随机数.
static float RandF()
{
	return (float)(rand()) / (float)RAND_MAX;
}

// 返回[a, b)区间的浮点随机数.
static float RandF(float a, float b)
{
	return a + RandF()*(b-a);
} 

XMVECTOR MathHelper::RandUnitVec3()
{
	XMVECTOR One  = XMVectorSet(1.0f, 1.0f, 1.0f, 1.0f);
	XMVECTOR Zero = XMVectorZero();

	// 持续尝试直到获得一个半球体上或之内的点.
	while(true)
	{
		// 生成一个位于立方体[-1,1]^3之内的随机点.
		XMVECTOR v = XMVectorSet(MathHelper::RandF(-1.0f, 1.0f), 
                                 MathHelper::RandF(-1.0f, 1.0f), MathHelper::RandF(-1.0f, 1.0f), 0.0f);

		// Ignore points outside the unit sphere in order to get an even distribution 
		// over the unit sphere.  Otherwise points will clump more on the sphere near 
		// the corners of the cube.

		if( XMVector3Greater( XMVector3LengthSq(v), One) )
			continue;

		return XMVector3Normalize(v);
	}
}

上面的函数用于C++代码,但是我们还需要着色器代码中的随机数。在着色器代码中生成随机数是一件比较棘手的事情,因为HLSL没有随机数生成器。所以我们要做的是:创建一幅带有4个浮点分量的1D纹理(DXGI_FORMAT_R32G32B32A32_FLOAT)。然后使用随机4D向量填充该纹理,这些向量的每个分量都在[1,−1]区间内。该纹理使用重复(wrap)寻址模式进行采样,这样我们便可以使用超出[0,1]区间的无限制的纹理坐标了。着色器代码只要对该纹理进行采样就可以获得随机数。对随机纹理进行采样的方式很多。如果每个粒子都有一个不同的x坐标,那么我们可以考虑把x坐标作为纹理坐标来获取随机数。不过,当很多粒子具有相同的x坐标时,这种方式就失效了,因为它们都对纹理中的同一个元素进行采样,随机性已不复存在。另一种方式是把当前的游戏时间作为纹理坐标。通过这一方式,在不同时间生成的粒子可以得到不同的随机数。不过,这意味着在同一时间生成的粒子会得到相同的随机数。如果粒子系统一次发射多个粒子,那么这种方式会出现问题。当同时生成多个粒子时,我们可以给游戏时间添加一个纹理坐标偏移值,使粒子对纹理贴图上的不同的点进行采样,进而获取不同的随机数。例如,我们循环20次,生成20个粒子,那么可以把循环索引(在适当的调整之后)作为纹理坐标偏移值。通过一方式,我们可以获得20个不同的随机数。

下面的代码示范了如何生成随机纹理:

ID3D11ShaderResourceView* d3dHelper::CreateRandomTexture1DSRV(ID3D11Device* device)
{
	// 
	// 创建随机数据.
	//
	XMFLOAT4 randomValues[1024];

	for(int i = 0; i < 1024; ++i)
	{
		randomValues[i].x = MathHelper::RandF(-1.0f, 1.0f);
		randomValues[i].y = MathHelper::RandF(-1.0f, 1.0f);
		randomValues[i].z = MathHelper::RandF(-1.0f, 1.0f);
		randomValues[i].w = MathHelper::RandF(-1.0f, 1.0f);
	}

    D3D11_SUBRESOURCE_DATA initData;
    initData.pSysMem = randomValues;
	initData.SysMemPitch = 1024*sizeof(XMFLOAT4);
    initData.SysMemSlicePitch = 0;

	//
	// 创建纹理.
	//
    D3D11_TEXTURE1D_DESC texDesc;
    texDesc.Width = 1024;
    texDesc.MipLevels = 1;
    texDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    texDesc.Usage = D3D11_USAGE_IMMUTABLE;
    texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    texDesc.CPUAccessFlags = 0;
    texDesc.MiscFlags = 0;
    texDesc.ArraySize = 1;

	ID3D11Texture1D* randomTex = 0;
    HR(device->CreateTexture1D(&texDesc, &initData, &randomTex));

	//
	// 创建资源视图.
	//
    D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc;
	viewDesc.Format = texDesc.Format;
    viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
    viewDesc.Texture1D.MipLevels = texDesc.MipLevels;
	viewDesc.Texture1D.MostDetailedMip = 0;
	
	ID3D11ShaderResourceView* randomTexSRV = 0;
    HR(device->CreateShaderResourceView(randomTex, &viewDesc, &randomTexSRV));

	ReleaseCOM(randomTex);

	return randomTexSRV;
}

注意,随机纹理只需要一个多级渐近纹理层。我们使用SampleLevel内置函数对纹理仅有的一个多级渐近纹理进行采样。该函数允许我们明确地指定所要采样的多级渐近纹理层。它的第1个参数是采样器对象,第2个参数是纹理坐标(1D纹理只有一个纹理坐标),第3个参数是多级渐近纹理层(当纹理只包含一个多级渐近纹理层时,该参数应设为0)。

下面的着色器函数用于获取一个在单位球体上的随机向量:

float3 RandUnitVec3(float offset)
{
	// 使用游戏时间在采样的随机纹理之上添加一个偏移.
	float u = (gGameTime + offset);
	
	// coordinates in [-1,1]
	float3 v = gRandomTex.SampleLevel(samLinear, u, 0).xyz;
	
	// 规范化到一个单位球上
	return normalize(v);
}
文件下载(已下载 757 次)

发布时间:2014/8/29 下午7:27:02  阅读次数:4127

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号