Ocean.cs
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualBasic;
using Sandbox;
public struct WaterLayer
{
public int lengthScale;
public float cutoffLow;
public float cutoffHigh;
}
public struct ShaderSpectrumSettings
{
public float scale;
public float angle;
public float spreadBlend;
public float swell;
public float alpha;
public float peakOmega;
public float gamma;
public float shortWavesFade;
}
public struct EditorSpectrumSettings
{
[Range(0, 1)]
public float scale { get; set; }
public float windSpeed { get; set; }
public float windDirection { get; set; }
public float fetch { get; set; }
[Range(0, 1)]
public float spreadBlend { get; set; }
[Range(0, 1)]
public float swell;
public float peakEnhancement { get; set; }
public float shortWavesFade { get; set; }
}
public sealed class Ocean : Component
{
[RequireComponent]
public ModelRenderer waterRenderer { get; set; }
[Property]
public GameObject Sun { get; set; }
[Property] [Range( 0, 100000 )]
public int seed = 12345;
[Property] [Range( 0, 10 )]
public float timeScale = 1.0f;
private int resolution = 256;
[Property, InlineEditor]
public EditorSpectrumSettings spectrumSettings { get; set; } = new EditorSpectrumSettings
{
scale = 0.6f,
windSpeed = 20f,
windDirection = 20f,
fetch = 100000,
spreadBlend = 0.75f,
swell = 0.75f,
peakEnhancement = 3f,
shortWavesFade = 0.05f
};
[Property, InlineEditor]
public EditorSpectrumSettings secondSpectrumSettings { get; set; } = new EditorSpectrumSettings
{
scale = 0.1f,
windSpeed = 2f,
windDirection = 30f,
fetch = 1000,
spreadBlend = 0,
swell = 0,
peakEnhancement = 1f,
shortWavesFade = 0.01f
};
[Property]
public float depth = 500f;
[Property]
public bool enableLengthScale0 = true;
[Property]
public int lengthScale0 = 250;
[Property]
public bool enableLengthScale1 = true;
[Property]
public int lengthScale1 = 100;
[Property]
public bool enableLengthScale2 = true;
[Property]
public int lengthScale2 = 10;
[Property]
public bool recalculateInitials = true;
[Property]
public int planeLength = 1000;
[Property]
public int planeResolution = 1000;
[Property]
public Color waterColor = new( 0, 0.1f, 0.16f, 1f );
[Property]
public Color diffuseReflectance = new( 1, 1, 1, 0 );
[Property]
public Color specularReflectance = new( 1, 1, 1, 0 );
[Property]
public float fresnelShininess = 5;
[Property]
public float fresnelBias = 0;
[Property]
public float fresnelStrength = 0.3f;
private ComputeShader initialSpectrumShader = new ComputeShader( "Shaders/initial_jonswap" );
private ComputeShader packSpectrumConjugateShader = new ComputeShader( "Shaders/pack_spectrum_conjugate" );
private ComputeShader timeBasedSpectrumShader = new ComputeShader( "Shaders/time_based_phillips_spectrum" );
private ComputeShader inverseFFTShader = new ComputeShader( "Shaders/inverse_fft" );
private ComputeShader inverseFFTPostProcessShader = new ComputeShader( "Shaders/post_fft" );
private Material material = Material.Load( "Materials/fft_water.vmat" );
public Texture NoiseTexture { get; set; }
public Texture InitialSpectrumTexture { get; set; }
public Texture InitialConstantsTexture { get; set; }
public Texture TimeBasedSpectrumTextureDzDxDyDzx { get; set; }
public Texture TimeBasedSpectrumTextureDxxDyxDzyDyy { get; set; }
public Texture WaterAmplitudeTexture { get; set; }
public Texture WaterAmplitudeNormalTexture { get; set; }
public float LastUpdateTime { get; set; }
protected override void OnAwake()
{
InitialSpectrumTexture = Texture.CreateArray( resolution, resolution, 3)
.WithFormat( ImageFormat.RGBA32323232F )
.WithUAVBinding()
.Finish();
InitialConstantsTexture = Texture.CreateArray( resolution, resolution, 3)
.WithFormat( ImageFormat.RGBA32323232F )
.WithUAVBinding()
.Finish();
TimeBasedSpectrumTextureDzDxDyDzx = Texture.CreateArray( resolution, resolution, 3)
.WithFormat( ImageFormat.RGBA32323232F )
.WithUAVBinding()
.Finish();
TimeBasedSpectrumTextureDxxDyxDzyDyy = Texture.CreateArray( resolution, resolution, 3)
.WithFormat( ImageFormat.RGBA32323232F )
.WithUAVBinding()
.Finish();
WaterAmplitudeTexture = Texture.CreateArray( resolution, resolution, 3)
.WithFormat( ImageFormat.RGBA32323232F )
.WithUAVBinding()
.Finish();
WaterAmplitudeNormalTexture = Texture.CreateArray( resolution, resolution, 3)
.WithFormat( ImageFormat.RGBA32323232F )
.WithUAVBinding()
.Finish();
NoiseTexture = new GaussianNoiseGenerator( seed ).CreateNoiseTexture( resolution );
CreateWaterModel();
CalculateInitialState();
}
protected override void OnEnabled()
{
waterRenderer.SceneObject.Attributes.Set( "HeightMap", WaterAmplitudeTexture );
waterRenderer.SceneObject.Attributes.Set( "HeightMapNormals", WaterAmplitudeNormalTexture );
}
protected override void OnUpdate()
{
if ( recalculateInitials )
{
CalculateInitialState();
waterRenderer.SceneObject.Attributes.Set( "EnableLengthScale0", enableLengthScale0 );
waterRenderer.SceneObject.Attributes.Set( "LengthScale0", lengthScale0 );
waterRenderer.SceneObject.Attributes.Set( "EnableLengthScale1", enableLengthScale1 );
waterRenderer.SceneObject.Attributes.Set( "LengthScale1", lengthScale1 );
waterRenderer.SceneObject.Attributes.Set( "EnableLengthScale2", enableLengthScale2 );
waterRenderer.SceneObject.Attributes.Set( "LengthScale2", lengthScale2 );
waterRenderer.SceneObject.Attributes.Set( "AmbientColor", waterColor );
waterRenderer.SceneObject.Attributes.Set( "LightColor", Sun.GetComponent<DirectionalLight>().SkyColor );
waterRenderer.SceneObject.Attributes.Set( "SunColor", Sun.GetComponent<DirectionalLight>().LightColor );
waterRenderer.SceneObject.Attributes.Set( "LightDirection", Sun.LocalRotation.Forward );
waterRenderer.SceneObject.Attributes.Set( "DiffuseReflectance", diffuseReflectance );
waterRenderer.SceneObject.Attributes.Set( "FresnelShininess", fresnelShininess );
waterRenderer.SceneObject.Attributes.Set( "FresnelBias", fresnelBias );
waterRenderer.SceneObject.Attributes.Set( "FresnelStrength", fresnelStrength );
waterRenderer.SceneObject.Attributes.Set( "SpecularReflectance", specularReflectance );
}
CalculateTimeBasedPhillipsSpectrum();
CalculateAmplitudes();
LastUpdateTime = Time.Now;
}
protected override void OnDestroy()
{
InitialSpectrumTexture.Dispose();
InitialConstantsTexture.Dispose();
TimeBasedSpectrumTextureDzDxDyDzx.Dispose();
TimeBasedSpectrumTextureDxxDyxDzyDyy.Dispose();
WaterAmplitudeTexture.Dispose();
WaterAmplitudeNormalTexture.Dispose();
NoiseTexture.Dispose();
waterRenderer.Destroy();
}
private void CalculateAmplitudes()
{
// column pass
inverseFFTShader.Attributes.Set( "IsColumnPass", true );
inverseFFTShader.Attributes.Set( "InputOutputTexture", TimeBasedSpectrumTextureDzDxDyDzx );
inverseFFTShader.Dispatch( resolution, resolution, 3);
inverseFFTShader.Attributes.Set( "IsColumnPass", true );
inverseFFTShader.Attributes.Set( "InputOutputTexture", TimeBasedSpectrumTextureDxxDyxDzyDyy );
inverseFFTShader.Dispatch( resolution, resolution, 3);
// row pass
inverseFFTShader.Attributes.Set( "IsColumnPass", false );
inverseFFTShader.Attributes.Set( "InputOutputTexture", TimeBasedSpectrumTextureDzDxDyDzx );
inverseFFTShader.Dispatch( resolution, resolution, 3);
inverseFFTShader.Attributes.Set( "IsColumnPass", false );
inverseFFTShader.Attributes.Set( "InputOutputTexture", TimeBasedSpectrumTextureDxxDyxDzyDyy );
inverseFFTShader.Dispatch( resolution, resolution, 3);
// post process + pack
inverseFFTPostProcessShader.Attributes.Set( "InputTextureDzDxDyDzx", TimeBasedSpectrumTextureDzDxDyDzx );
inverseFFTPostProcessShader.Attributes.Set( "InputTextureDxxDyxDzyDyy", TimeBasedSpectrumTextureDxxDyxDzyDyy );
inverseFFTPostProcessShader.Attributes.Set( "OutputTexture", WaterAmplitudeTexture );
inverseFFTPostProcessShader.Attributes.Set( "OutputNormalTexture", WaterAmplitudeNormalTexture );
inverseFFTPostProcessShader.Attributes.Set( "Lambda", 1.0f );
inverseFFTPostProcessShader.Attributes.Set( "FoamBias", 0.85f );
inverseFFTPostProcessShader.Attributes.Set( "FoamDecayRate", 0.0175f );
inverseFFTPostProcessShader.Attributes.Set( "FoamAdd", 0.1f );
inverseFFTPostProcessShader.Attributes.Set( "FoamThreshold", 0.0f );
inverseFFTPostProcessShader.Dispatch( resolution, resolution, 3);
}
private void CalculateTimeBasedPhillipsSpectrum()
{
timeBasedSpectrumShader.Attributes.Set( "Time", (Time.Now * timeScale) );
timeBasedSpectrumShader.Attributes.Set( "InitialSpectrum", InitialSpectrumTexture );
timeBasedSpectrumShader.Attributes.Set( "Constants", InitialConstantsTexture );
timeBasedSpectrumShader.Attributes.Set( "TimeBasedSpectrumDzDxDyDzx", TimeBasedSpectrumTextureDzDxDyDzx );
timeBasedSpectrumShader.Attributes.Set( "TimeBasedSpectrumDxxDyxDzyDyy", TimeBasedSpectrumTextureDxxDyxDzyDyy );
timeBasedSpectrumShader.Dispatch( resolution, resolution, 3);
}
private void CalculateInitialState()
{
GpuBuffer<ShaderSpectrumSettings> spectrumSettingsBuffer = new(2);
spectrumSettingsBuffer.SetData( new[]
{
ConvertSpectrumSettings( spectrumSettings ),
ConvertSpectrumSettings( secondSpectrumSettings )
} );
float cutoff1 = 2 * MathF.PI / lengthScale1 * 6f;
float cutoff2 = 2 * MathF.PI / lengthScale2 * 6f;
GpuBuffer<WaterLayer> waterLayerBuffer = new(3);
waterLayerBuffer.SetData( new[]
{
new WaterLayer {
lengthScale = lengthScale0,
cutoffLow = 0.0001f,
cutoffHigh = cutoff1
},
new WaterLayer {
lengthScale = lengthScale1,
cutoffLow = cutoff1,
cutoffHigh = cutoff2
},
new WaterLayer {
lengthScale = lengthScale2,
cutoffLow = cutoff2,
cutoffHigh = 9999
}
} );
initialSpectrumShader.Attributes.Set( "GaussianNoise", NoiseTexture );
initialSpectrumShader.Attributes.Set( "Spectrum", InitialSpectrumTexture );
initialSpectrumShader.Attributes.Set( "Constants", InitialConstantsTexture );
initialSpectrumShader.Attributes.Set( "WaterLayers", waterLayerBuffer );
initialSpectrumShader.Attributes.Set( "Size", resolution );
initialSpectrumShader.Attributes.Set( "Depth", depth );
initialSpectrumShader.Attributes.Set( "SpectrumParameters", spectrumSettingsBuffer );
initialSpectrumShader.Dispatch( resolution, resolution, 3 );
packSpectrumConjugateShader.Attributes.Set( "Spectrum", InitialSpectrumTexture );
packSpectrumConjugateShader.Attributes.Set( "Size", resolution );
packSpectrumConjugateShader.Dispatch( resolution, resolution, 3 );
}
private void CreateWaterModel()
{
var mesh = new Mesh(material);
var modelBuilder = new ModelBuilder();
var halfLength = planeLength * 0.5f;
var sideVertexCount = planeResolution;
var vertices = new Vertex[(sideVertexCount + 1) * (sideVertexCount + 1)];
for ( int i = 0, x = 0; x <= sideVertexCount; ++x )
{
for ( int y = 0; y <= sideVertexCount; ++y, ++i )
{
var vertex = new Vertex(
new Vector3(((float)x / sideVertexCount * planeLength) - halfLength,
((float)y / sideVertexCount * planeLength) - halfLength,
0),
Vector3.Up,
Vector3.Forward,
new Vector4(
(float)x / sideVertexCount,
(float)y / sideVertexCount,
0,
0)
);
vertices[i] = vertex;
}
}
mesh.CreateVertexBuffer<Vertex>( vertices.Length, Vertex.Layout, vertices );
var triangles = new int[sideVertexCount * sideVertexCount * 6];
for ( int ti = 0, vi = 0, x = 0; x < sideVertexCount; ++vi, ++x )
{
for ( int y = 0; y < sideVertexCount; ti += 6, ++vi, ++y )
{
triangles[ti] = vi;
triangles[ti + 1] = vi + sideVertexCount + 2;
triangles[ti + 2] = vi + 1;
triangles[ti + 3] = vi;
triangles[ti + 4] = vi + sideVertexCount + 1;
triangles[ti + 5] = vi + sideVertexCount + 2;
}
}
mesh.CreateIndexBuffer(triangles.Length, triangles);
waterRenderer.Model = modelBuilder
.AddMesh( mesh )
.Create();
}
private float JonswapAlpha(float g, float fetch, float windSpeed)
{
return 0.076f * MathF.Pow(g * fetch / windSpeed / windSpeed, -0.22f);
}
private float JonswapPeakFrequency(float g, float fetch, float windSpeed)
{
return 22 * MathF.Pow(windSpeed * fetch / g / g, -0.33f);
}
private ShaderSpectrumSettings ConvertSpectrumSettings(EditorSpectrumSettings settings)
{
return new ShaderSpectrumSettings
{
scale = settings.scale,
angle = settings.windDirection / 180 * MathF.PI,
spreadBlend = settings.spreadBlend,
swell = Math.Clamp(settings.swell, 0.01f, 1),
alpha = JonswapAlpha(9.81f, settings.fetch, settings.windSpeed),
peakOmega = JonswapPeakFrequency(9.81f, settings.fetch, settings.windSpeed),
gamma = settings.peakEnhancement,
shortWavesFade = settings.shortWavesFade
};
}
}