Cogs.Core
WaveSpectrum.cpp
1#include "ClipmapTerrainTypes.h"
2#include "RenderContext.h"
3#include "WaveSpectrum.h"
4
5#include "Rendering/ITextures.h"
6#include "Rendering/IRenderTargets.h"
7#include "Rendering/IGraphicsDevice.h"
8#include "Rendering/IBuffers.h"
9#include "Rendering/CommandGroupAnnotation.h"
10
11#include <algorithm>
12#include <cmath>
13#include <complex>
14#include <cassert>
15
16using std::complex;
17using std::sqrt;
18using std::min;
19using std::max;
20using std::pow;
21using std::exp;
22using std::tgamma;
23using std::sin;
24using std::cos;
25using std::acos;
26using std::log;
27using std::polar;
28using std::isfinite;
29
30using glm::vec2;
31using glm::vec3;
32using glm::vec4;
33using glm::ivec2;
34using glm::length;
35using glm::dot;
36using glm::cross;
37
38namespace {
39
40 const float pi = float(M_PI);
41 const float twoPi = float(2.0*M_PI);
42 const float g = 9.80665f; // gravity of earth
43
44 struct DispersionParameters{
45 int N;
46 float twoPiOverSideLength;
47 float dt;
48 };
49
50 struct PackParamters{
51 int N_minus_1;
52 float L_over_twoN;
53 };
54
55 float waveSpectrumPhillips(const float omega,
56 const float alpha = 0.0081f)
57 {
58 const float omega2 = omega*omega;
59 const float omega4 = omega2*omega2;
60 const float omega5 = omega4*omega;
61
62 return (alpha*g*g) / omega5;
63 }
64
65 float waveSpectrumPiersonMoskowitz(const float omega,
66 const float omega_p, // frequency of the spectrum peak
67 const float alpha = 0.0081f)
68 {
69 const float omega_p_over_omega = omega_p / omega;
70 const float omega_p_over_omega4 = (omega_p_over_omega*omega_p_over_omega)*(omega_p_over_omega*omega_p_over_omega);
71
72 return waveSpectrumPhillips(omega, alpha)*exp(float(-5.0 / 4.0)*omega_p_over_omega4);
73 }
74
75 float dispersionLonguetHiggins(const float omega,
76 const float omega_p,
77 const float U10,
78 const float windWaveAngle) // in [-pi,pi]
79 {
80
81 // Sharpness of directional spreading (Mitsuyasu et al., 1975)
82 float mu = omega <= omega_p ? 5.f : -2.5f;
83 float s_omega = 11.5f*pow(g / (omega_p*U10), 2.5f)*pow(omega / omega_p, mu);
84 assert(isfinite(s_omega));
85
86 // Normalization factor of dispersion relation
87
88 // Part of this is the ratio gamma(x+1)/gamma(x+0.5). Evaluating this
89 // directly is begging for overflow since gamma rapidly becomes
90 // ridiculously large, e.g. gamma(30)=8.8x10^30. However, since we are
91 // interested in the ratio, the following asymptotic series,
92 //
93 // gamma(J+1/2)/gamma(J) = sqrt(gamma)*(1 - 1/8J + 1/128J^2
94 // 5/1024J^3 - 21/32768J^4 + ... )
95 //
96 // is handy, as if we define J = s_omega + 0.5, we can use it directly
97 // to evaluate the ratio.
98 float J = s_omega + 0.5f;
99 float gammaRatio = sqrt(J)*(1.f - 1.f / (8.f*J) + 1.f / (128.f*J*J) + 5.f / (1024.f*J*J*J) - 21.f / (32768.f*J*J*J*J));
100 float N_s_omega = float(1.0 / (2.0*sqrt(M_PI)))*(gammaRatio);
101 assert(isfinite(N_s_omega));
102
103 // x!(x / ((1 / 2 (2 x + 1))!) + 1 / (2 (1 / 2 (2 x + 1))!))
104
105 // Wind-wave direction angle halved
106 // float theta_half = 0.5f*(waveDirection);
107
108 // Dispersion relation (Longuet-Higgins, 1962)
109 float D = N_s_omega*pow(max(0.0f, cos(0.5f*windWaveAngle)), 2.f*s_omega);
110 assert(isfinite(D));
111 return D;
112 }
113
114} // of anonymous namespace
115
117 int N,
118 float L,
119 float waveNumberMute,
120 float waveNumberPass,
121 float windSpeed,
122 float windDirection,
123 float dominantWavePeriod,
124 float scale)
125{
126
127 if (windDirection >= pi) {
128 windDirection -= twoPi;
129 }
130 if (windSpeed < 2.f) {
131 windSpeed = 2.f;
132 }
133
134 float dominantAngularVelocity = float(2.0*M_PI) / dominantWavePeriod;
135 float sumS = 0.f;
136
137 glm::vec2 windDir(glm::cos(windDirection), glm::sin(windDirection));
138
139 for (int j = 0; j < N; j++) {
140 for (int i = 0; i < N; i++) {
141 if ((i == 0) && (j == 0)) {
142 continue;
143 }
144 // [0,N/2] -> [0,N/2]
145 // [N/2+1,N-1] -> [-N/2+1,-1]
146 ivec2 ij(i <= N / 2 ? i : i - N,
147 j <= N / 2 ? j : j - N);
148
149 const vec2 K = (twoPi / L)*vec2(ij); // K: Wave vector
150 float k = length(K); // k: Wave number
151
152 float angularVelocity = sqrt(g*k); // omega: Angular velocity
153 //float waveDirection = std::atan2(K.y, K.x); // theta_w: Wave direction
154
155 float cosWindWaveAngle = std::max(-1.f, std::min(1.f, (1.f / k)*dot(K, windDir)));
156 float windWaveAngle = std::acos(cosWindWaveAngle);
157 assert(std::isfinite(windWaveAngle));
158
159 float pass = 1.f;
160 if (waveNumberMute != waveNumberPass) {
161 pass = max(0.f, min(1.f, (k - waveNumberMute) / (waveNumberPass - waveNumberMute)));
162 }
163
164 const float S = waveSpectrumPiersonMoskowitz(angularVelocity,
165 dominantAngularVelocity,
166 0.0081f);
167 const float D = dispersionLonguetHiggins(angularVelocity,
168 dominantAngularVelocity,
169 windSpeed,
170 windWaveAngle);
171
172 const float chainFactor = (1.f / (2.f*k))*sqrt(g / k);
173
174 sumS += chainFactor*pass*S;
175 E[N*j + i] = chainFactor*pass*S*D;
176 }
177 }
178 E[0] = 0.f;
179
180 // Adjust spectrum to match significant wave height
181 float w = 1.f / sumS;// (significantWaveHeight*significantWaveHeight) / (sumS);
182 if (std::numeric_limits<float>::epsilon() < std::abs(scale)) {
183 w = scale;
184 }
185 for (int i = 0; i < N*N; i++) {
186 E[i] = w*E[i];
187 }
188 return 1.f / sumS;
189}
190#define MINSTD_RAND_MAX ((1u<<31)-2u)
191static uint32_t minstd_rand(uint32_t &seed)
192{
193 seed = ((uint64_t)seed * 48271u) % ((1u<<31)-1u);
194 return seed;
195}
196void Cogs::WaveSpectrum::createRandomizedWaveSpectrumInstance(std::vector<glm::vec2>& H,
197 const std::vector<float>& E,
198 uint32_t seed,
199 const size_t N)
200{
201 for (size_t j = 0; j < N; j++) {
202 for (size_t i = 0; i < N; i++) {
203
204 // Check the sanity of this trying to comply with normal distribution
205
206 float U0 = (float)((double)(minstd_rand(seed) + 1) / (double)(MINSTD_RAND_MAX + 2));
207 float U1 = (float)((2.0*M_PI*(double)minstd_rand(seed)) / (double)(MINSTD_RAND_MAX + 1));
208
209 // Box-Muller transform to create normal distributed numbers, mean 0, var 1.
210 complex<float> eta = sqrt(-2.f*log(U0))*complex<float>(cos(U1), sin(U1));
211
212 complex<float> res = sqrt(E[N*j + i] / 2.f) * eta;
213
214 H[N*j + i] = vec2(res.real(), res.imag());
215 }
216 }
217}
218
219float Cogs::WaveSpectrum::setConditions(const float tileExtent,
220 const float waveNumberMute,
221 const float waveNumberPass,
222 const float significantWavePeriod,
223 const float windSpeed,
224 const float /*windDirection*/,
225 const float scale,
226 const unsigned int /*seed*/)
227{
228 this->tileExtent = tileExtent;
229
230 float significantWaveLength = (g*significantWavePeriod*significantWavePeriod) / (2.f*glm::pi<float>());
231
232 int harmonic = std::max(1, static_cast<int>(std::round(std::log2(tileExtent / significantWaveLength))));
233
234 tileExtentAdjust = ((1 << harmonic)*significantWaveLength) / tileExtent;
235
236 float rv = createDirectionalWaveSpectrum(frequencyDomain.E,
237 N,
238 tileExtentAdjust*tileExtent,
239 waveNumberMute,
240 waveNumberPass,
241 windSpeed,
242 0.f,
243 significantWavePeriod,
244 scale);
245 createRandomizedWaveSpectrumInstance(frequencyDomain.a, frequencyDomain.E, 42, N);
246
247 ITextures* textures = device->getTextures();
248 frequencyDomain.aTex = textures->loadTexture(reinterpret_cast<unsigned char*>(frequencyDomain.a.data()), N, N, TextureFormat::R32G32_FLOAT);
249 textures->annotate(frequencyDomain.aTex, "Initial spectrum instance.");
250
251 return rv;
252}
253
254void Cogs::WaveSpectrum::initialize(IGraphicsDevice* device)
255{
256 this->frame = 0;
257 this->device = device;
258
259 gpgpuQuadRenderer.initialize(device);
260 fourierTransform.initialize(device, gpgpuQuadRenderer);
261
262 auto ie = device->getEffects();
263
264 {
265 PreprocessorDefinitions definitions;
266 // definitions.push_back(PreprocessorDefinition("TWO_PI_OVER_L", float(2.0*M_PI/L)));
267
268 disperse.effect = ie->loadEffect("Terrain/GPGPUPassThroughVS.hlsl",
269 "Terrain/WaveSpectrumDispersionPS.hlsl",
270 definitions);
271 assert(ie->checkEffect(disperse.effect) == Cogs::ResourceStatus::Ready && "Expects synchronous effect loading");
272 VertexFormatHandle handle = gpgpuQuadRenderer.vertexFormat();
273 disperse.il = device->getBuffers()->loadInputLayout(&handle, 1, disperse.effect);
274 disperse.constantBuffer = device->getBuffers()->loadBuffer(nullptr, sizeof(DispersionParameters), Usage::Dynamic, AccessMode::Write, BindFlags::ConstantBuffer);
275 }
276
277 {
278 PreprocessorDefinitions definitions;
279
280 texturePack.effect = ie->loadEffect("Terrain/GPGPUPassThroughVS.hlsl",
281 "Terrain/OceanBuildTexPositionPS.hlsl",
282 definitions);
283 assert(ie->checkEffect(texturePack.effect) == Cogs::ResourceStatus::Ready && "Expects synchronous effect loading");
284 VertexFormatHandle handle = gpgpuQuadRenderer.vertexFormat();
285 texturePack.il = device->getBuffers()->loadInputLayout(&handle, 1, texturePack.effect);
286 texturePack.constantBuffer = device->getBuffers()->loadBuffer(nullptr, sizeof(PackParamters), Usage::Dynamic, AccessMode::Write, BindFlags::ConstantBuffer);
287 }
288}
289
290void Cogs::WaveSpectrum::setSize(const int NLog2)
291{
292 fourierTransform.setSize(NLog2);
293
294 this->NLog2 = NLog2;
295 N = 1u << NLog2;
296
297 std::vector<float> zeros(N*N);
298 frequencyDomain.E.resize(N*N);
299 frequencyDomain.a.resize(N*N);
300
301 ITextures* textures = device->getTextures();
302 IRenderTargets* renderTargets = device->getRenderTargets();
303
304 packed.xyzTex = textures->loadTexture(nullptr, N, N, TextureFormat::R32G32B32A32_FLOAT, TextureFlags::RenderTarget | TextureFlags::GenerateMipMaps);
305 packed.dxdu_dydv_dzdu_dzdvTex = textures->loadTexture(nullptr, N, N, TextureFormat::R32G32B32A32_FLOAT, TextureFlags::RenderTarget | TextureFlags::GenerateMipMaps);
306
307 for (int i = 0; i < 2; i++) {
308 phaseTex[i] = textures->loadTexture(reinterpret_cast<uint8_t*>(zeros.data()), N, N, TextureFormat::R32_FLOAT, TextureFlags::RenderTarget);
309 }
310
311 frequencyDomain.xyTex = textures->loadTexture(nullptr, N, N, TextureFormat::R32G32B32A32_FLOAT, TextureFlags::RenderTarget);
312 frequencyDomain.zTex = textures->loadTexture(nullptr, N, N, TextureFormat::R32G32_FLOAT, TextureFlags::RenderTarget);
313 frequencyDomain.dzdu_dzdv_Tex = textures->loadTexture(nullptr, N, N, TextureFormat::R32G32B32A32_FLOAT, TextureFlags::RenderTarget);
314
315 spatialDomain.xyTex = textures->loadTexture(nullptr, N, N, TextureFormat::R32G32B32A32_FLOAT, TextureFlags::RenderTarget);
316 spatialDomain.zTex = textures->loadTexture(nullptr, N, N, TextureFormat::R32G32_FLOAT, TextureFlags::RenderTarget);
317 spatialDomain.dzdu_dzdv_Tex = textures->loadTexture(nullptr, N, N, TextureFormat::R32G32B32A32_FLOAT, TextureFlags::RenderTarget);
318
319 // Create render targets
320 spatialDomain.xyTarget = renderTargets->createRenderTarget(spatialDomain.xyTex);
321 spatialDomain.zTarget = renderTargets->createRenderTarget(spatialDomain.zTex);
322 spatialDomain.dzduTarget = renderTargets->createRenderTarget(spatialDomain.dzdu_dzdv_Tex);
323
324 for (int i = 0; i < 2; i++) {
325 TextureHandle texs[4] = {
326 phaseTex[i],
327 frequencyDomain.xyTex,
328 frequencyDomain.zTex,
329 frequencyDomain.dzdu_dzdv_Tex
330 };
331 dispersionTarget[i] = renderTargets->createRenderTarget(texs, 4);
332 }
333
334 TextureHandle packedTexs[2] = {
335 packed.xyzTex ,
336 packed.dxdu_dydv_dzdu_dzdvTex
337 };
338 packed.packTarget = renderTargets->createRenderTarget(packedTexs, 2);
339
340}
341
342bool Cogs::WaveSpectrum::update(RenderContext& renderContext, const float dt)
343{
344 IContext* context = renderContext.context;
345
346 {
347 CommandGroupAnnotation preGroup(renderContext.context, "WaveSpectrum::Disperse");
348
349
350 context->setRenderTarget(dispersionTarget[frame], DepthStencilHandle::InvalidHandle);
351 context->setViewport(0, 0, float(N), float(N));
352 context->setEffect(disperse.effect);
353 context->setInputLayout(disperse.il);
354
355 gpgpuQuadRenderer.bind(context);
356
357 context->setTexture("aTex", 0, frequencyDomain.aTex);
358 context->setTexture("phaseTex", 0, phaseTex[(frame + 1) & 1]);
359
360 {
361 MappedBuffer<DispersionParameters> constants(context, disperse.constantBuffer, MapMode::WriteDiscard);
362 if (constants) {
363 constants->N = N;
364 constants->twoPiOverSideLength = float(2.0*M_PI / (tileExtentAdjust*tileExtent));
365 constants->dt = dt;
366 }
367 }
368 context->setConstantBuffer("DispersionParameters", disperse.constantBuffer);
369
370 gpgpuQuadRenderer.draw(context);
371 }
372
373 {
374 CommandGroupAnnotation preGroup(renderContext.context, "WaveSpectrum::iFFT");
375
376 fourierTransform.inverseFourierTransform(renderContext, gpgpuQuadRenderer, spatialDomain.xyTarget, frequencyDomain.xyTex, true);
377
378 fourierTransform.inverseFourierTransform(renderContext, gpgpuQuadRenderer, spatialDomain.zTarget, frequencyDomain.zTex, false);
379
380 fourierTransform.inverseFourierTransform(renderContext, gpgpuQuadRenderer, spatialDomain.dzduTarget, frequencyDomain.dzdu_dzdv_Tex, true);
381 }
382
383 {
384 CommandGroupAnnotation preGroup(renderContext.context, "WaveSpectrum::Pack");
385
386 context->setRenderTarget(packed.packTarget, DepthStencilHandle::InvalidHandle);
387 context->setViewport(0, 0, float(N), float(N));
388
389 context->setEffect(texturePack.effect);
390 {
391 MappedBuffer<PackParamters> constants(context, texturePack.constantBuffer, MapMode::WriteDiscard);
392 if (constants) {
393 constants->N_minus_1 = N - 1;
394 constants->L_over_twoN = tileExtent / (2.f*N);
395 }
396 }
397 context->setConstantBuffer("PackParamters", texturePack.constantBuffer);
398 context->setTexture("waveXY", 0, spatialDomain.xyTex);
399 context->setTexture("waveZ", 1, spatialDomain.zTex);
400 context->setTexture("wavedZdu_dZdv", 2, spatialDomain.dzdu_dzdv_Tex);
401
402 gpgpuQuadRenderer.bind(context);
403 context->setInputLayout(texturePack.il);
404 gpgpuQuadRenderer.draw(context);
405
406 auto textures = device->getTextures();
407 textures->generateMipmaps(packed.xyzTex);
408 textures->generateMipmaps(packed.dxdu_dydv_dzdu_dzdvTex);
409 }
410
411 frame = (frame + 1) & 1;
412
413 return true;
414}
static float createDirectionalWaveSpectrum(std::vector< float > &E, int N, float L, float freqPassZero, float freqPassOne, float windSpeed, float windDirection, float dominantWavePeriod, float scale=0.f)
@ Ready
The resource has loaded successfully and is ready for use.
std::vector< PreprocessorDefinition > PreprocessorDefinitions
A set of preprocessor definitions.
Definition: IEffects.h:20
@ Write
The buffer can be mapped and written to by the CPU after creation.
Definition: Flags.h:50
@ ConstantBuffer
The buffer can be bound as input to effects as a constant buffer.
Definition: Flags.h:72
static const Handle_t InvalidHandle
Represents an invalid handle.
Definition: Common.h:81
@ WriteDiscard
Write access. When unmapping the graphics system will discard the old contents of the resource.
Definition: Flags.h:103
@ RenderTarget
The texture can be used as a render target and drawn into.
Definition: Flags.h:120
@ GenerateMipMaps
The texture supports automatic mipmap generation performed by the graphics device.
Definition: Flags.h:124
@ Dynamic
Buffer will be loaded and modified with some frequency.
Definition: Flags.h:30