计算着色器(Computer Shader)中可以使用线程组并行进行计算,很适合用来计算波浪(水面、地形等)的顶点数据。在学习完DirectX11 With Windows 计算着色器:波浪(水波)后,要求完成FTT 海面模拟,并且可以使用 imgui 调节参数控制波浪大小。

FFTWaves

在《【学习笔记】Unity 基于GPU FFT海洋的实现-理论篇》FFT函数:

其中,

参数 是我们水平方向的坐标, t 是时间,函数 h 可以直接给我们返回在时间 t 时,

处的海面高度。

被定义为

为波矢量, 是海平面的大小,  N 和 M 是我们采样离散点的数量。当然 N 和 M取值越大我们得到的波形就更加细节(叠加的波就更多),当然计算时间也会大大的增加。

我们只需要计算出频谱然后按照 函数就可以得到我们海面的高度,现在我们来看一下频谱公式 

 

g 是引力常数 

 是两个相互独立服从均值为0,标准差为1的高斯随机数。

是我们的方向波谱,方向波谱一般描述为 ,这和我们前面的参数不太一样,其实他们之间可以相互转换,有兴趣可以看Empirical Directional Wave Spectra for Computer Graphics这篇论文。

方向波谱是非定向波普 和方向拓展函数 的乘积

W 是我们前面提到的角频率, 是波矢量相对于风向的角度

在Simulating Ocean Water-Jerry Tessendorf 中使用到的非定向波谱为 ,而方向拓展函数为 ,他们的乘积就是

在我们的实现中风向拓展函数使用的不是 ,而是Donelan-Banner定向传播,这里就先不贴这个公式了,免得 显得公式太多....

以上FFT海面高度的公式,接下来是水平偏移

可以看到这和我们的高度函数基本一样,只是我们需要把频谱进行改变一下。这是对 X 和 Z 总体的描述,我们将其拆开就可以得到对 X 和 Z 单独的描述

中间的其实就是两个复数相乘。

HLSL

在上一部分了解到,我们需要获得每个波的角频率w波长k海平面的大小L.x、L.y、高斯随机数风向的角度和系统的时间t。此外,为了在计算着色器中计算imgui ,我们还需要知道列线程组的数目。知道了需要,我们就可以在FFTWaves.hlsli中定义结构体:

struct FFTWave
{float g_WaveW;     // 角频率float g_WaveK;     // 波长float g_WaveX;   // 海平面的大小float g_WaveY;     // 海平面的大小float g_WaveR;     //高斯随机数float g_WaveI;     //高斯随机数float2 g_angle;    // 风向的角度float2 g_WaveD;    // 方向float2 g_pad;       // 打包
};
我们要计算顶点的数据,需要在FFTWaves.hlsli中定义顶点的结构体:
struct VertexPosNormalTex
{float3 PosL : POSITION;float3 NormalL : NORMAL;float2 Tex : TEXCOORD;
};
整个FFTWaves.hlsli如下:
//FFTWaves.hlsli
​
#define GroundThreadSize 16
#define WaveCount 3
​
static const float PI = 3.14159267f;
static const float g = 9.8f;
​
struct FFTWave {float g_WaveW;   // 角频率 float g_WaveK;   // 波长 float g_WaveX;   // 海平面的大小 float g_WaveY;   // 海平面的大小float g_WaveR;     //高斯随机数float g_WaveI;     //高斯随机数 float2 g_angle;   // 风向的角度float2 g_WaveD;    // 方向float2 g_pad;       // 打包
};
​
struct VertexPosNormalTex
{float3 PosL : POSITION;float3 NormalL : NORMAL;float2 Tex : TEXCOORD;
};
​
RWStructuredBuffer<VertexPosNormalTex> g_Input : register(u0);
RWStructuredBuffer<VertexPosNormalTex> g_Output : register(u1);
​
// 用于更新模拟
cbuffer cbUpdateSettings : register(b0)
{FFTWave g_fftData[WaveCount];   // 几个波叠加float g_TotalTime;   // 总时长float g_GroundCountX; // X方向上的线程团数float2 g_Pad;
}

RWStructuredBuffer是可读写的结构体缓冲区类型的无序访问视图,我们可以通过g_Input来读取顶点数据,通过计算后将新数据写进g_Output。关于各种着色器资源的特点以及用法,可以参考深入了解与使用缓冲区资源。

FFTWaves的计算着色器:

//FFTWaves_CS.hlsl#include "FFTWaves.hlsli"//计算高斯随机变量
[numthreads(8, 8, 1)]
void ComputeGaussianRandom(uint3 id: SV_DispatchThreadID)
{float2 g = gaussian(id.xy);GaussianRandomRT[id.xy] = float4(g, 0, 0);
}//计算高斯随机数
float2 gaussian(float2 id)
{//均匀分布随机数rngState = wangHash(id.y * N + id.x);float x1 = rand();float x2 = rand();x1 = max(1e-6f, x1);x2 = max(1e-6f, x2);//计算两个相互独立的高斯随机数float g1 = sqrt(-2.0f * log(x1)) * cos(2.0f * PI * x2);float g2 = sqrt(-2.0f * log(x1)) * sin(2.0f * PI * x2);return float2(g1, g2);
}
//随机种子
uint wangHash(uint seed)
{seed = (seed ^ 61) ^ (seed >> 16);seed *= 9;seed = seed ^ (seed >> 4);seed *= 0x27d4eb2d;seed = seed ^ (seed >> 15);return seed;
}
//计算均匀分布随机数[0,1)
float rand()
{// Xorshift算法rngState ^= (rngState << 13);rngState ^= (rngState >> 17);rngState ^= (rngState << 5);return rngState / 4294967296.0f;;
}//生成高度频谱
[numthreads(8, 8, 1)]
void CreateHeightSpectrum(uint3 id: SV_DispatchThreadID)
{float2 k = float2(2.0f * PI * id.x / N - PI, 2.0f * PI * id.y / N - PI);float2 gaussian = GaussianRandomRT[id.xy].xy;float2 hTilde0 = gaussian * sqrt(abs(phillips(k) * DonelanBannerDirectionalSpreading(k)) / 2.0f);float2 hTilde0Conj = gaussian * sqrt(abs(phillips(-k) * DonelanBannerDirectionalSpreading(-k)) / 2.0f);hTilde0Conj.y *= -1.0f;float omegat = dispersion(k) * Time;float c = cos(omegat);float s = sin(omegat);float2 h1 = complexMultiply(hTilde0, float2(c, s));float2 h2 = complexMultiply(hTilde0Conj, float2(c, -s));float2 HTilde = h1 + h2;HeightSpectrumRT[id.xy] = float4(HTilde, 0, 0);
}//计算phillips谱
float phillips(float2 k)
{float kLength = length(k);kLength = max(0.001f, kLength);// kLength = 1;float kLength2 = kLength * kLength;float kLength4 = kLength2 * kLength2;float windLength = length(WindAndSeed.xy);float  l = windLength * windLength / G;float l2 = l * l;float damping = 0.001f;float L2 = l2 * damping * damping;//phillips谱return  A * exp(-1.0f / (kLength2 * l2)) / kLength4 * exp(-kLength2 * L2);
}//Donelan-Banner方向拓展
float DonelanBannerDirectionalSpreading(float2 k)
{float betaS;float omegap = 0.855f * G / length(WindAndSeed.xy);float ratio = dispersion(k) / omegap;if (ratio < 0.95f){betaS = 2.61f * pow(ratio, 1.3f);}if (ratio >= 0.95f && ratio < 1.6f){betaS = 2.28f * pow(ratio, -1.3f);}if (ratio > 1.6f){float epsilon = -0.4f + 0.8393f * exp(-0.567f * log(ratio * ratio));betaS = pow(10, epsilon);}float theta = atan2(k.y, k.x) - atan2(WindAndSeed.y, WindAndSeed.x);return betaS / max(1e-7f, 2.0f * tanh(betaS * PI) * pow(cosh(betaS * theta), 2));
}
float dispersion(float2 k)
{return sqrt(G * length(k));
}//生成偏移频谱
[numthreads(8, 8, 1)]
void CreateDisplaceSpectrum(uint3 id: SV_DispatchThreadID)
{float2 k = float2(2 * PI * id.x / N - PI, 2 * PI * id.y / N - PI);k /= max(0.001f, length(k));float2 HTilde = HeightSpectrumRT[id.xy].xy;float2 KxHTilde = complexMultiply(float2(0, -k.x), HTilde);float2 kzHTilde = complexMultiply(float2(0, -k.y), HTilde);DisplaceXSpectrumRT[id.xy] = float4(KxHTilde, 0, 0);DisplaceZSpectrumRT[id.xy] = float4(kzHTilde, 0, 0);
}//横向FFT计算,只针对第m-1阶段,最后一阶段需要特殊处理
[numthreads(8, 8, 1)]
void FFTHorizontal(uint3 id: SV_DispatchThreadID)
{int2 idxs = id.xy;idxs.x = floor(id.x / (Ns * 2.0f)) * Ns + id.x % Ns;float angle = 2.0f * PI * (id.x / (Ns * 2.0f));float2 w = float2(cos(angle), sin(angle));float2 x0 = InputRT[idxs].xy;float2 x1 = InputRT[int2(idxs.x + N * 0.5f, idxs.y)].xy;float2 output = x0 + float2(w.x * x1.x - w.y * x1.y, w.x * x1.y + w.y * x1.x);OutputRT[id.xy] = float4(output, 0, 0);
}//生成偏移纹理
[numthreads(8, 8, 1)]
void TextureGenerationDisplace(uint3 id: SV_DispatchThreadID)
{float y = length(HeightSpectrumRT[id.xy].xy) / (N * N) * HeightScale;//高度float x = length(DisplaceXSpectrumRT[id.xy].xy) / (N * N) * Lambda;//x轴偏移float z = length(DisplaceZSpectrumRT[id.xy].xy) / (N * N) * Lambda;//z轴偏移HeightSpectrumRT[id.xy] = float4(y, y, y, 0);DisplaceXSpectrumRT[id.xy] = float4(x, x, x, 0);DisplaceZSpectrumRT[id.xy] = float4(z, z, z, 0);DisplaceRT[id.xy] = float4(x, y, z, 0);
}//生成法线和泡沫纹理
[numthreads(8, 8, 1)]
void TextureGenerationNormalBubbles(uint3 id: SV_DispatchThreadID)
{//计算法线float uintLength = OceanLength / (N - 1.0f);//两点间单位长度//获取当前点,周围4个点的uv坐标uint2 uvX1 = uint2((id.x - 1.0f + N) % N, id.y);uint2 uvX2 = uint2((id.x + 1.0f + N) % N, id.y);uint2 uvZ1 = uint2(id.x, (id.y - 1.0f + N) % N);uint2 uvZ2 = uint2(id.x, (id.y + 1.0f + N) % N);//以当前点为中心,获取周围4个点的偏移值float3 x1D = DisplaceRT[uvX1].xyz;//在x轴 第一个点的偏移值float3 x2D = DisplaceRT[uvX2].xyz;//在x轴 第二个点的偏移值float3 z1D = DisplaceRT[uvZ1].xyz;//在z轴 第一个点的偏移值float3 z2D = DisplaceRT[uvZ2].xyz;//在z轴 第二个点的偏移值//以当前点为原点,构建周围4个点的坐标float3 x1 = float3(x1D.x - uintLength, x1D.yz);//在x轴 第一个点的坐标float3 x2 = float3(x2D.x + uintLength, x2D.yz);//在x轴 第二个点的坐标float3 z1 = float3(z1D.xy, z1D.z - uintLength);//在z轴 第一个点的坐标float3 z2 = float3(z1D.xy, z1D.z + uintLength);//在z轴 第二个点的坐标//计算两个切向量float3 tangentX = x2 - x1;float3 tangentZ = z2 - z1;//计算法线float3 normal = normalize(cross(tangentZ, tangentX));//计算泡沫float3 ddx = x2D - x1D;float3 ddz = z2D - z1D;//雅可比行列式float jacobian = (1.0f + ddx.x) * (1.0f + ddz.z) - ddx.z * ddz.x;jacobian = saturate(max(0, BubblesThreshold - saturate(jacobian)) * BubblesScale);NormalRT[id.xy] = float4(normal, 0);BubblesRT[id.xy] = float4(jacobian, jacobian, jacobian, 0);
}v2f vert(appdata v)
{v2f o;o.uv = TRANSFORM_TEX(v.uv, _Displace);float4 displcae = tex2Dlod(_Displace, float4(o.uv, 0, 0));v.vertex += float4(displcae.xyz, 0);o.pos = UnityObjectToClipPos(v.vertex);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;return o;
}fixed4 frag(v2f i) : SV_Target
{fixed3 normal = UnityObjectToWorldNormal(tex2D(_Normal, i.uv).rgb);fixed bubbles = tex2D(_Bubbles, i.uv).r;fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));fixed3 reflectDir = reflect(-viewDir, normal);// reflectDir *= sign(reflectDir.y);//采样反射探头half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflectDir, 0);half3 sky = DecodeHDR(rgbm, unity_SpecCube0_HDR);//菲涅尔fixed fresnel = saturate(_FresnelScale + (1 - _FresnelScale) * pow(1 - dot(normal, viewDir), 5));half facing = saturate(dot(viewDir, normal));fixed3 oceanColor = lerp(_OceanColorShallow, _OceanColorDeep, facing);fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//泡沫颜色fixed3 bubblesDiffuse = _BubblesColor.rbg * _LightColor0.rgb * saturate(dot(lightDir, normal));//海洋颜色fixed3 oceanDiffuse = oceanColor * _LightColor0.rgb * saturate(dot(lightDir, normal));fixed3 halfDir = normalize(lightDir + viewDir);fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(normal, halfDir)), _Gloss);fixed3 diffuse = lerp(oceanDiffuse, bubblesDiffuse, bubbles);fixed3 col = ambient + lerp(diffuse, sky, fresnel) + specular;return fixed4(col, 1);
}

FFTWavesRender

FFTWavesRender的设计如下:

FFTWavesRender类分三步构成。分别是初始化数据更新绘制

 初始化

从DirectX11 With Windows SDK--28 计算着色器:波浪(水波)中的WavesRenderFFTWaves中,可知我们需要初始化的数据包括:

 ①顶点数量(水平面网格)

 ②纹理坐标

 ③时间、空间步长

 ④结构体FFTWave的各项数据

需要初始化的缓冲区:顶点缓冲区、索引缓冲区、常量缓冲区。

计算着色器、无序访问视图、结构体缓冲区

FFTWavesRender类定义如下

class FFTWavesRender
{
public:template<class T>using ComPtr = Microsoft::WRL::ComPtr<T>;void SetMaterial(const Material& material);Transform& GetTransform();const Transform& GetTransform() const;UINT RowCount() const;UINT ColumnCount() const;struct FFTWave{float g_WaveW;     // 角频率float g_WaveK;     // 波长float g_WaveX;   // 海平面的大小float g_WaveY;     // 海平面的大小float g_WaveR;     //高斯随机数float g_WaveI;     //高斯随机数float2 g_angle;    // 风向的角度DirectX::XMFLOAT2 WaveD;    // 方向DirectX::XMFLOAT2 pad;      // 打包};public:FFTWavesRender() = default;~FFTWavesRender() = default;// 不允许拷贝,允许移动FFTWavesRender(const FFTWavesRender&) = delete;FFTWavesRender& operator= (const FFTWavesRender&) = delete;FFTWavesRender(FFTWavesRender&&) = default;FFTWavesRender& operator= (FFTWavesRender&&) = default;HRESULT InitResource(ID3D11Device* device,const std::wstring& texFileName,  // 纹理文件名UINT rows,                          // 顶点行数UINT cols,                          // 顶点列数float texU,                          // 纹理坐标U方向最大值float texV,                          // 纹理坐标V方向最大值float timeStep,                      // 时间步长float spatialStep,                  // 空间步长FFTWave* FFTData        // 数据);// 设置数据void SetData(FFTWave* FFTData);// 更新void Update(ID3D11DeviceContext* deviceContext, float t);// 绘制void Draw(ID3D11DeviceContext* deviceContext, BasicEffect& effect);// 设置DeBug名称void SetDebugObjectName(const std::string& name);private:void Init(UINT rows,                    // 顶点行数UINT cols,                    // 顶点列数float texU,                    // 纹理坐标U方向最大值float texV,                    // 纹理坐标V方向最大值float timeStep,                // 时间步长float spatialStep,             // 空间步长FFTWave* FFTData  // 数据);UINT m_NumRows = 0;                    // 顶点行数UINT m_NumCols = 0;                    // 顶点列数UINT m_VertexCount = 0;                // 顶点数目UINT m_IndexCount = 0;                // 索引数目Transform m_Transform = {};            // 水面变换DirectX::XMFLOAT2 m_TexOffset = {};    // 纹理坐标偏移float m_TexU = 0.0f;                // 纹理坐标U方向最大值float m_TexV = 0.0f;                // 纹理坐标V方向最大值Material m_Material = {};            // 水面材质FFTWave m_FFTwaveData[3] = {};float m_TimeStep = 0.0f;            // 时间步长float m_SpatialStep = 0.0f;            // 空间步长float m_AccumulateTime = 0.0f;        // 累积时间float m_TotalTime = 0.0f;           // 总时长private:ComPtr<ID3D11Buffer> m_pCurrVertex;                        // 保存当前模拟结果的顶点ComPtr<ID3D11UnorderedAccessView> m_pCurrVertexUAV;        // 缓存当前模拟结果的顶点 无序访问视图ComPtr<ID3D11Buffer> m_pVertex;                            // 初始顶点 缓冲区ComPtr<ID3D11UnorderedAccessView> m_pVertexUAV;            // 初始顶点 无序访问视图ComPtr<ID3D11Buffer> m_pVertexBuffer;                    // 顶点缓冲区ComPtr<ID3D11Buffer> m_pIndexBuffer;                    // 索引缓冲区ComPtr<ID3D11Buffer> m_pConstantBuffer;                    // 常量缓冲区ComPtr<ID3D11Buffer> m_pTempBuffer;                     // 用于顶点数据拷贝的缓冲区ComPtr<ID3D11ComputeShader> m_pWavesUpdateCS;            // 用于计算模拟结果的着色器ComPtr<ID3D11ShaderResourceView> m_pTextureDiffuse;        // 水面纹理struct {FFTWave FFTData[3];float TotalTime;    // 总时长float GroundCountX; // X方向上的线程团数DirectX::XMFLOAT2 Pad;} m_CBUpdateSettings = {};
};

类中定义了HLSLFFTWaces结构

FFTWavesRender类的InitResource代码如下:

HRESULT FFTWavesRender::InitResource(ID3D11Device* device, const std::wstring& texFileName,UINT rows, UINT cols, float texU, float texV, float timeStep, float spatialStep, FFTWave* FFTData)
{// 清空内存m_pVertexBuffer.Reset();m_pIndexBuffer.Reset();m_pConstantBuffer.Reset();m_pTempBuffer.Reset();m_pCurrVertex.Reset();m_pCurrVertexUAV.Reset();m_pVertex.Reset();m_pVertexUAV.Reset();m_pWavesUpdateCS.Reset();m_pTextureDiffuse.Reset();// 使用 16x16 的线程组// 指定行顶点数和列顶点数都为16的倍数if (rows % 16 || cols % 16)return E_INVALIDARG;// 初始化水波数据Init(rows, cols, texU, texV, timeStep, spatialStep, FFTData);auto meshData = Geometry::CreateTerrain<VertexPosNormalTex, DWORD>(XMFLOAT2((cols - 1) * spatialStep, (rows - 1) * spatialStep),XMUINT2(cols - 1, rows - 1));HRESULT hr;// 创建顶点缓冲区hr = CreateVertexBuffer(device, meshData.vertexVec.data(), (UINT)meshData.vertexVec.size() * sizeof(VertexPosNormalTex), m_pVertexBuffer.GetAddressOf(), true);if (FAILED(hr))return hr;// 创建索引缓冲区hr = CreateIndexBuffer(device, meshData.indexVec.data(), (UINT)meshData.indexVec.size() * sizeof(DWORD), m_pIndexBuffer.GetAddressOf());if (FAILED(hr))return hr;// 创建常量缓冲区hr = CreateConstantBuffer(device, nullptr, sizeof(m_CBUpdateSettings), m_pConstantBuffer.GetAddressOf());if (FAILED(hr))return hr;// 创建计算着色器ComPtr<ID3DBlob> blob;hr = CreateShaderFromFile(L"HLSL\\FFTWaves_CS.cso", L"HLSL\\FFTWaves_CS.hlsl", "CS", "cs_5_0", blob.GetAddressOf());if (FAILED(hr))return hr;hr = device->CreateComputeShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pWavesUpdateCS.GetAddressOf());if (FAILED(hr))return hr;// 创建GPU结构体缓冲区hr = CreateStructuredBuffer(device, meshData.vertexVec.data(), (UINT)meshData.vertexVec.size() * sizeof(VertexPosNormalTex), (UINT)sizeof(VertexPosNormalTex), m_pCurrVertex.GetAddressOf(), false, true);if (FAILED(hr))return hr;hr = CreateStructuredBuffer(device, meshData.vertexVec.data(), (UINT)meshData.vertexVec.size() * sizeof(VertexPosNormalTex), (UINT)sizeof(VertexPosNormalTex), m_pVertex.GetAddressOf(), false, true);if (FAILED(hr))return hr;// 创建无序访问视图D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;uavDesc.Format = DXGI_FORMAT_UNKNOWN;uavDesc.Buffer.FirstElement = 0;uavDesc.Buffer.NumElements = (UINT)meshData.vertexVec.size();uavDesc.Buffer.Flags = 0;hr = device->CreateUnorderedAccessView(m_pCurrVertex.Get(), &uavDesc, m_pCurrVertexUAV.GetAddressOf());if (FAILED(hr))return hr;hr = device->CreateUnorderedAccessView(m_pVertex.Get(), &uavDesc, m_pVertexUAV.GetAddressOf());if (FAILED(hr))return hr;// 用于顶点数据拷贝的缓冲区D3D11_BUFFER_DESC bdesc;ZeroMemory(&bdesc, sizeof bdesc);m_pCurrVertex.Get()->GetDesc(&bdesc);bdesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;bdesc.Usage = D3D11_USAGE_STAGING;bdesc.BindFlags = 0;bdesc.MiscFlags = 0;hr = device->CreateBuffer(&bdesc, nullptr, m_pTempBuffer.GetAddressOf());if (FAILED(hr))return hr;// 读取纹理if (texFileName.size() > 4){// 判断纹理文件类型if (texFileName.substr(texFileName.size() - 3, 3) == L"dds"){hr = CreateDDSTextureFromFile(device, texFileName.c_str(), nullptr,m_pTextureDiffuse.GetAddressOf());}else{hr = CreateWICTextureFromFile(device, texFileName.c_str(), nullptr,m_pTextureDiffuse.GetAddressOf());}}return hr;
}

数据更新

        更新FFTWavesRender类中的两个函数:

// 设置数据
void SetData(FFTWave* fftData);// 更新
void Update(ID3D11DeviceContext* deviceContext, float t);

每次先通过SetData将FFTWave的参数传入,再通过Update在指定的时间间隔后更新数据

代码如下:

void FFTWavesRender::SetData(FFTWave* fftData)
{for (int i = 0; i < sizeof(m_fftwaveData) / sizeof(FFTWave); ++i){m_fftwaveData[i] = *(fftData + i);}
}void FFTWavesRender::Update(ID3D11DeviceContext* deviceContext, float t)
{// 时间累加m_AccumulateTime += t;m_TotalTime += t;// 纹理位移for (int i = 0; i < sizeof(m_fftwaveData) / sizeof(FFTWave); ++i){float DirSide = sqrt(m_fftwaveData[i].WaveD.x * m_fftwaveData[i].WaveD.x + m_fftwaveData[i].WaveD.y * m_fftwaveData[i].WaveD.y);m_TexOffset.x -= m_fftwaveData[i].WaveSpeed * m_fftwaveData[i].WaveD.x / DirSide * t * 0.02f;m_TexOffset.y -= m_fftwaveData[i].WaveSpeed * m_fftwaveData[i].WaveD.y / DirSide * t * 0.02f;}if (m_AccumulateTime > m_TimeStep){// 更新常量缓冲区D3D11_MAPPED_SUBRESOURCE data;m_CBUpdateSettings.fftData[0] = m_fftwaveData[0];m_CBUpdateSettings.fftData[1] = m_fftwaveData[1];m_CBUpdateSettings.fftData[2] = m_fftwaveData[2];m_CBUpdateSettings.TotalTime = m_TotalTime;m_CBUpdateSettings.GroundCountX = m_NumCols / 16;deviceContext->Map(m_pConstantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &data);memcpy_s(data.pData, sizeof m_CBUpdateSettings, &m_CBUpdateSettings, sizeof m_CBUpdateSettings);deviceContext->Unmap(m_pConstantBuffer.Get(), 0);// 设置计算资源deviceContext->CSSetShader(m_pWavesUpdateCS.Get(), nullptr, 0);deviceContext->CSSetConstantBuffers(0, 1, m_pConstantBuffer.GetAddressOf());ID3D11UnorderedAccessView* pUAVs[2] = { m_pVertexUAV.Get() ,m_pCurrVertexUAV.Get() };deviceContext->CSSetUnorderedAccessViews(0, 2, pUAVs, nullptr);// 开始调度deviceContext->Dispatch(m_NumCols / 16, m_NumRows / 16, 1);// 清除绑定pUAVs[0] = pUAVs[1] = nullptr;deviceContext->CSSetUnorderedAccessViews(0, 2, pUAVs, nullptr);// 数据copy// 读取deviceContext->CopyResource(m_pTempBuffer.Get(), m_pCurrVertex.Get());D3D11_MAPPED_SUBRESOURCE rsSrc;VertexPosNormalTex* dataSrc;deviceContext->Map(m_pTempBuffer.Get(), 0, D3D11_MAP_READ, 0, &rsSrc);dataSrc = (VertexPosNormalTex*)rsSrc.pData;deviceContext->Unmap(m_pTempBuffer.Get(), 0);// 写入D3D11_MAPPED_SUBRESOURCE rsDest;deviceContext->Map(m_pVertexBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &rsDest);memcpy_s(rsDest.pData, m_VertexCount * sizeof(VertexPosNormalTex), dataSrc, m_VertexCount * sizeof(VertexPosNormalTex));deviceContext->Unmap(m_pVertexBuffer.Get(), 0);m_AccumulateTime = 0.0f;        // 重置时间}

注意: StructuredBuffer不能直接作为顶点缓冲区绑定到渲染管线上,因为IASetVertexBuffers的缓冲区必须有D3D11_BIND_VERTEX_BUFFER标记,StructuredBuffer已经有D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS标记,当尝试添加D3D11_BIND_VERTEX_BUFFER时,发现无法创建出缓冲区,也就是说,一个缓冲区不能同时拥有这三个属性。因此我们需要从m_pCurrVertex读取顶点数据,再写人m_pVertexBuffer。

绘制

代码如下:

UINT strides[1] = { sizeof(VertexPosNormalTex) };
UINT offsets[1] = { 0 };
​
// 设置绘制所有的顶点缓冲区(当前顶点缓冲区)
deviceContext->IASetVertexBuffers(0, 1, m_pCurrVertex.GetAddressOf(), strides, offsets);
// 设置绘制所有的引索缓冲区
deviceContext->IASetIndexBuffer(m_pIndexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);
​
// 关闭波浪绘制,因为这里的波浪绘制时教程中用来计算法线的,我们不需要
effect.SetWavesStates(false);
// 设置材质
effect.SetMaterial(m_Material);
// 设置纹理
effect.SetTextureDiffuse(m_pTextureDiffuse.Get());
// 设置世界矩阵
effect.SetWorldMatrix(m_Transform.GetLocalToWorldMatrixXM());
// 设置纹理位移(偏移)
effect.SetTexTransformMatrix(XMMatrixScaling(m_TexU, m_TexV, 1.0f) * XMMatrixTranslationFromVector(XMLoadFloat2(&m_TexOffset)));
effect.Apply(deviceContext);
// 绘制
deviceContext->DrawIndexed(m_IndexCount, 0, 0);// 解除当前顶点缓冲区的绑定
deviceContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), strides, offsets);
effect.Apply(deviceContext);

 

演示

FTT 海面模拟(DirectX11)相关推荐

  1. DirectX11实现FFT海面模拟

    FFT_Ocean Simulation with DirectX11 概述: 模拟海洋的方法主要就是两种: 利用Gerstner波(即通过在水平方向上挤压正弦波叠加形成的海洋波谱,来形成形成较尖的浪 ...

  2. 快速傅立叶变换(FFT)的海面模拟

    快速傅立叶变换(FFT)的海面模拟 在这篇文章中,我们将根据Tessendorf的论文[1]中的方程来实现统计波浪模型,以模拟海洋水.  使用快速傅立叶变换,我们将能够实现实时交互的帧速率.以下提供两 ...

  3. matlab 海面反射,海面波浪模拟 MATLAB

    数学建模美赛集训的时候要用到一个海面模拟,分享一下海面模拟的MATLAB代码 先贴一下结果图: 下面是源代码~~~ function waterwave n = 64; % grid size g = ...

  4. 海面波浪模拟 MATLAB

    数学建模美赛集训的时候要用到一个海面模拟,分享一下海面模拟的MATLAB代码 先贴一下结果图: 下面是源代码~~~ 1 function waterwave 2 3 4 n = 64; % grid ...

  5. 二维粗糙海面matlab,三维随机粗糙海面的Monte-Carlo仿真

    第 35 卷第 10 期 2013 年 10 月 舰 船 科 学 技 术 SHIP SCIENCE AND TECHNOLOGY Vol. 35,No. 10 Oct. ,2013 三维随机粗糙海面的 ...

  6. 快速傅立叶变换(FFT)算法(原来这就是蝶形变换)

    快速傅立叶变换(FFT)算法(原来这就是蝶形变换) 为了实现FFT的海面模拟,不得不先撸个FFT算法实现. 离散傅立叶变换(DFT) 学习FFT之前,首先要先了解什么是DFT,我们都知道傅立叶变换是将 ...

  7. 雅可比行列式_二重积分换元法、雅可比行列式

    在fft海面模拟求浪尖泡沫区域时,需要用到雅可比行列式(见:杨超:fft海面模拟(一)),故温习一下. 二重积分换元法同济高数下册有讲,当时没细看证明,近来用到搜了一下,感觉下面这样推比较直观: 同理 ...

  8. Jetson TX2刷机踩坑总结

    Jetson TX2刷机踩坑总结--wiznote笔记 目录 坑1: 在双系统的Ubuntu16.04下第一次刷机时卡在determing The IP address of target,造成TX2 ...

  9. Unity FFT海水渲染效果展示

    最近研究了一下FFT的海水渲染,发现这东西真的涉及到好多好多的数学知识,我基本就是囫囵吞枣,现在基本也忘得差不多了.我在这里挂几个大佬的文章,想研究的可以去看一下,我这个数学渣渣就不强行解释了. ff ...

最新文章

  1. centos7离线安装tengine所需依赖
  2. 云联会企业认证_今日新鲜事:沉浸式交互购车新体验 2020首届中国春季云车展启幕...
  3. <script>放在head内和body内有什么区别
  4. OpenFileDialog对话框Filter属性
  5. T T[] toArray(T[] a);
  6. 【转】java编程思想第20章的注解例子用到的com.sun.mirror的jar包
  7. 关于 HTTP 请求头的内容
  8. java工作笔记018---java中BigDecimal小数位数的四舍五入等操作
  9. Lesson 3.5 - Maya Commands: getAttr
  10. scatter python_Python数据可视化之scatter( )函数
  11. ORACLE 中的 ROW_NUMBER() OVER() 分析函数的用法
  12. MT4指标安装方法,以MACD红绿柱黄白线双线macd为例
  13. PXE配置-Tftpd64
  14. Excel如何删除表格中的空白列
  15. zabbix监控华为USG6000防火墙
  16. linux , Shell 文件合并的命令
  17. 内核查找符号指针函数kallsyms_lookup_name
  18. 金华驾驶员考试中心 科目二、科目三和科目四
  19. IEEE会议投稿资料汇总http://cadcg2015.nwpu.edu.cn/index.htm
  20. Linux服务器开发,开源框架log4cpp和日志模块实现

热门文章

  1. html pc端单位转换,pc是什么单位?
  2. Termux搭建图形化环境及tk开发
  3. redis之python(二):zadd命令出现错误:AttributeError: 'str' object has no attribute 'iteritems'
  4. 按Backspace键删除时,会出现^H
  5. 【牛客网华为机试】HJ32 密码截取
  6. 软件开发与软件研发的区别
  7. 使用 prometheus 监控 MySQL
  8. 英语作文计算机的利弊,电脑游戏的坏处英语作文
  9. 安装deepin系统
  10. web常见的 HTTP 5xx 状态汇总