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