/* CPU side call from OnEnable, 파도에 관한 각종 속성 값을 세팅*/
if(GerstnerWavesJobs.Initialized == false && Application.isPlaying)
GerstnerWavesJobs.Init();
/* CPU side call from OnDisable */
if(Application.isPlaying) GerstnerWavesJobs.Cleanup();
using Unity.Jobs;
using Unity.Mathematics; // 유니티의 Mathmatics 패키지 설치 필요
namespace WaterSystem
{
public static class GerstnerWavesJobs
{
private static NativeArray<Wave> _waveData;
private static NativeArray<float3> _positions;
private static NativeArray<float3> _wavePos;
private static NativeArray<float3> _waveNormal;
private static JobHandle _waterHeightHandle;
/*
하나의 NativeArray에 계산되어야할 모든 게임 오브젝트들(각 group으로 나눠짐)이 담기며
(first int)=guid, guid 따른 그룹 인덱스 (second int2)=offset으로 관리 (x는 시작 인덱스, y는 끝 인덱스)
*/
static readonly Dictionary<int, int2> Registry = new Dictionary<int, int2>();
public static void Init()
{
// Water.cs 에서 설정된 Wave 데이터를 사용
_waveCount = Water.Instance._waves.Length;
_waveData = new NativeArray<Wave>(_waveCount, Allocator.Persistent);
for (var i = 0; i < _waveData.Length; i++)
{
_waveData[i] = Water.Instance._waves[i];
}
/*
넉넉히 4096 크기의 메모리 할당, position은 게임오브젝트 좌표 인풋
wavePos, waveNormal은 계산완료된 아웃풋 데이터
*/
_positions = new NativeArray<float3>(4096, Allocator.Persistent);
_wavePos = new NativeArray<float3>(4096, Allocator.Persistent);
_waveNormal = new NativeArray<float3>(4096, Allocator.Persistent);
}
public static void Cleanup()
{
/* Jobs 종료 */
_waterHeightHandle.Complete();
/* native arrays 해제는 Dispose 함수 */
_waveData.Dispose();
기타 array ...
}
// 계산 되어야할 게임오브젝트를 추가하는 작업
public static void UpdateSamplePoints(ref NativeArray<float3> samplePoints, int guid)
{
//새로 Job을 시작하기전 JobHandler 종료.
CompleteJobs();
//Job 시작전 _positions에 다시 최신의 좌표값을 넣는 함수.
// guid가 존재하면 offset 그대로 사용.
if (Registry.TryGetValue(guid, out var offsets))
{
for (var i = offsets.x; i < offsets.y; i++)
_positions[i] = samplePoints[i - offsets.x];
}
// guid가 가 없으면 Registry offset 추가 및 _positionCount+
// 제한사항 : guid의 samplePoint는 처음 한꺼번에 add되면 추가로 나중에 add될 수 없다. Offset이 이미 고정됨.
else
{
// 크기 4096넘지 않으면 add
if (_positionCount + samplePoints.Length >= _positions.Length) return;
offsets = new int2(_positionCount, _positionCount + samplePoints.Length);
Registry.Add(guid, offsets);
_positionCount += samplePoints.Length;
}
}
private static void CompleteJobs()
{
if (_firstFrame || _processing == false) return;
_waterHeightHandle.Complete();
_processing = false;
}
// 연산된 결과를 반환, guid로 offset 확인후 x~y 인덱스까지 가져옴.
public static void GetData(int guid, ref float3[] outPos, ref float3[] outNorm)
{
if (!Registry.TryGetValue(guid, out var offsets)) return;
_wavePos.Slice(offsets.x, offsets.y - offsets.x).CopyTo(outPos);
if(outNorm != null)
_waveNormal.Slice(offsets.x, offsets.y - offsets.x).CopyTo(outNorm);
}
// Height jobs for the next frame
public static void UpdateHeights()
{
if (_processing) return;
_processing = true;
// JobStruct를 생성, 아래 생성자에서 멤버 Array 변수 할당
var waterHeight = new GerstnerWavesJobs.HeightJob()
{
WaveData = _waveData,
Position = _positions,
OffsetLength = new int2(0, _positions.Length),
Time = Time.time,
OutPosition = _wavePos,
OutNormal = _waveNormal
};
/*
추후 _waterHeightHandle.complete를 위해 할당.
4096 이 아닌 _positionCount 만큼 void Execute(int i) i : 0 ~ _positionCount iterator
32 => innerloopBatchCount로 32개의 i만큼을 한번에 하나의 쓰레드에서 처리.
*/
_waterHeightHandle = waterHeight.Schedule(_positionCount, 32);
// JobBatch 실행... 생성된 waterHeight를 직접 실행하지 않음.
JobHandle.ScheduleBatchedJobs();
_firstFrame = false;
}
[BurstCompile] //?
private struct HeightJob : IJobParallelFor
{
[ReadOnly] public NativeArray<Wave> WaveData;
[ReadOnly] public NativeArray<float3> Position;
[WriteOnly] public NativeArray<float3> OutPosition;
[WriteOnly] public NativeArray<float3> OutNormal;
[ReadOnly] public float Time;
[ReadOnly] public int2 OffsetLength;
/* Job을 이용한 병열 처리로 아래 여러 웨이브를 수학적 중첩으로 계산 */
public void Execute(int i)
{
if (i < OffsetLength.x || i >= OffsetLength.y - OffsetLength.x) return;
var waveCountMulti = 1f / WaveData.Length;
var wavePos = new float3(0f, 0f, 0f);
var waveNorm = new float3(0f, 0f, 0f);
for (var wave = 0; wave < WaveData.Length; wave++)
{
var pos = Position[i].xz; //x와 z만으로 계산됨
// Wave data vars
var amplitude = WaveData[wave].amplitude;
var direction = WaveData[wave].direction;
var wavelength = WaveData[wave].wavelength;
var omniPos = WaveData[wave].origin;
// 수학적 계산은 코드를 참고
...
}
OutPosition[i] = wavePos;
OutNormal[i] = math.normalize(waveNorm.xzy);
}
}
}
}
private NativeArray<float3> _samplePoints;
GerstnerWavesJobs.UpdateSamplePoints(ref _samplePoints, _guid);
GerstnerWavesJobs.GetData(_guid, ref Heights, ref _normals);
continue...