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