유니티 2018.1 버전에서 공개된
잡 시스템 (Job System)에 대해서
공부도 할 겸 테스트 진행해 보았습니다.
간단하게 말하면 멀티 스레드를 쉽게 이용할 수 있게
만들어 놓은 시스템 정도로 생각하면 될 것 같습니다.
스레드를 사용하려고 하면 Race Condition이나
Context Switching 등등 신경 써야 될 것들이 많은데
그런 것 신경 안 쓰고 멀티 스레드 기능을 활용할 수 있으면
훨씬 편하고 좋을 것 같긴 하네요.
일단 아래 사이트를 참조해서 테스트했습니다.
(샘플용 메시도 제공해 주고 설명도 아주 잘 되어 있습니다)
https://www.raywenderlich.com/7880445-unity-job-system-and-burst-compiler-getting-started
유니티 2021.2.5f1 버전
기준 입니다.
일단.. 기본 프로젝트 세팅을 해주고
파도 느낌을 만들 샘플 메시를 불러옵니다.
그 다음.. 잡 시스템을 사용하기 위해서는
아래의 3가지 패키지가 필요합니다.
Burst
Jobs
Mathematics
Window -> Package Manager
로 가서 추가해 줍니다.
Burst, Mathematics 추가했고
현재 테스트 중인데 2021 기준으로 Jobs는
이미 포함되어 있어서 건너뜁니다.
이제 다시 돌아와서 처음 추가한 메시의 와이어 프레임을 보면
엄청 많은 버텍스들이 있는 걸 볼 수 있습니다.
요 버텍스 들의 위치들을 변경해서
파도 느낌을 줄 수 있게 해줄 예정입니다.
처음으로 잡 시스템을 쓰지 않고
그냥 Update에서 계산해서 돌려 보도록 코드를 짜봅니다.
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
public class WaveGeneratorUpdate : MonoBehaviour
{
[Header("Wave Parameters")]
public float waveScale;
public float waveOffsetSpeed;
public float waveHeight;
[Header("References and Prefabs")]
public MeshFilter waterMeshFilter;
private Mesh waterMesh;
private List<Vector3> waterVertices;
private List<Vector3> waterNormals;
private void Start()
{
waterMesh = waterMeshFilter.mesh;
waterMesh.MarkDynamic();
waterVertices = new List<Vector3>(waterMesh.vertices);
waterNormals = new List<Vector3>(waterMesh.normals);
}
private void Update()
{
MakeNoise();
waterMesh.SetVertices(waterVertices);
waterMesh.RecalculateNormals();
}
private void MakeNoise()
{
for (int ii = 0; ii < waterVertices.Count; ii++)
{
if (waterNormals[ii].z <= 0f)
continue;
var vertex = waterVertices[ii];
var x = vertex.x * waveScale + waveOffsetSpeed * Time.time;
var y = vertex.y * waveScale + waveOffsetSpeed * Time.time;
float2 pos = math.float2(x, y);
float noiseValue = noise.snoise(pos);
waterVertices[ii] = new Vector3(vertex.x, vertex.y, noiseValue * waveHeight + 0.3f);
}
}
}
파도의 운동은 여러 입자들이 순차적으로
x좌표는 Sin값, y좌표는 Cos값 정도 넣어서 원운동을 연속적으로 하면서
느낌을 내줄 수 있는데 위 예제에서는 간단히 z 값만 바꿔줬네요.
하여튼.. 이렇게 하고 실행해 보면
대략 30~35 프레임 정도 나오는 걸 볼 수 있습니다.
이제 비교를 위해 잡 시스템을 사용한 코드로 변경해 줍니다.
/*
* Copyright (c) 2020 Razeware LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
* distribute, sublicense, create a derivative work, and/or sell copies of the
* Software in any work that is designed, intended, or marketed for pedagogical or
* instructional purposes related to programming, coding, application development,
* or information technology. Permission for such use, copying, modification,
* merger, publication, distribution, sublicensing, creation of derivative works,
* or sale is expressly withheld.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Jobs;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
public class WaveGeneratorJobs : MonoBehaviour
{
[Header("Wave Parameters")]
public float waveScale;
public float waveOffsetSpeed;
public float waveHeight;
[Header("References and Prefabs")]
public MeshFilter waterMeshFilter;
private Mesh waterMesh;
private NativeArray<Vector3> waterVertices;
private NativeArray<Vector3> waterNormals;
private JobHandle meshModificationJobHandle;
private UpdateMeshJob meshModificationJob;
private void Start()
{
waterMesh = waterMeshFilter.mesh;
waterMesh.MarkDynamic();
waterVertices = new NativeArray<Vector3>(waterMesh.vertices, Allocator.Persistent);
waterNormals = new NativeArray<Vector3>(waterMesh.normals, Allocator.Persistent);
}
private void Update()
{
meshModificationJob = new UpdateMeshJob()
{
vertices = waterVertices,
normals = waterNormals,
offsetSpeed = waveOffsetSpeed,
time = Time.time,
scale = waveScale,
height = waveHeight
};
meshModificationJobHandle = meshModificationJob.Schedule(waterVertices.Length, 64);
}
private void LateUpdate()
{
meshModificationJobHandle.Complete();
waterMesh.SetVertices(meshModificationJob.vertices);
waterMesh.RecalculateNormals();
}
private void OnDestroy()
{
waterVertices.Dispose();
waterNormals.Dispose();
}
// 실제 Job 이 실행되는 구조체
private struct UpdateMeshJob : IJobParallelFor
{
public NativeArray<Vector3> vertices;
[ReadOnly]
public NativeArray<Vector3> normals;
public float offsetSpeed;
public float scale;
public float height;
public float time;
private float Noise(float x, float y)
{
float2 pos = math.float2(x, y);
return noise.snoise(pos);
}
public void Execute(int i)
{
if (normals[i].z > 0f)
{
var vertex = vertices[i];
float noiseValue = Noise(vertex.x * scale + offsetSpeed * time, vertex.y * scale + offsetSpeed * time);
vertices[i] = new Vector3(vertex.x, vertex.y, noiseValue * height + 0.3f);
}
}
}
}
이렇게 적용하고 실행해 보면..
대략 80 정도의 프레임이 나오면서
확실히 성능 향상이 있는 걸 보여줍니다.
유니티 메인 스레드 하나를 쓰면서 병목현상을
일으키는 것보다는 멀티 스레드로 분산해서 처리하니
확실히 효율적이긴 한 듯합니다.
추가적으로 성능 향상을 시켜주는 기능이 있는데
버스트 컴파일러를 이용하는 것입니다.
LLVM 컴파일러 프레임워크를 바탕으로
고급 최적화를 사용해서 성능을 효율적으로
활용하게 해주는 기술입니다.
그럼 이제 버스트 컴파일러를 사용하도록 코드를 짜줍니다.
/*
* Copyright (c) 2020 Razeware LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
* distribute, sublicense, create a derivative work, and/or sell copies of the
* Software in any work that is designed, intended, or marketed for pedagogical or
* instructional purposes related to programming, coding, application development,
* or information technology. Permission for such use, copying, modification,
* merger, publication, distribution, sublicensing, creation of derivative works,
* or sale is expressly withheld.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Jobs;
using Unity.Collections;
using Unity.Burst;
using Unity.Jobs;
using Unity.Mathematics;
public class WaveGeneratorBurst : MonoBehaviour
{
[Header("Wave Parameters")]
public float waveScale;
public float waveOffsetSpeed;
public float waveHeight;
[Header("References and Prefabs")]
public MeshFilter waterMeshFilter;
private Mesh waterMesh;
private NativeArray<Vector3> waterVertices;
private NativeArray<Vector3> waterNormals;
private JobHandle meshModificationJobHandle;
private UpdateMeshJobWithBurst meshModificationJob;
private void Start()
{
waterMesh = waterMeshFilter.mesh;
waterMesh.MarkDynamic();
waterVertices = new NativeArray<Vector3>(waterMesh.vertices, Allocator.Persistent);
waterNormals = new NativeArray<Vector3>(waterMesh.normals, Allocator.Persistent);
}
private void Update()
{
meshModificationJob = new UpdateMeshJobWithBurst()
{
vertices = waterVertices,
normals = waterNormals,
offsetSpeed = waveOffsetSpeed,
time = Time.time,
scale = waveScale,
height = waveHeight
};
meshModificationJobHandle = meshModificationJob.Schedule(waterVertices.Length, 64);
}
private void LateUpdate()
{
meshModificationJobHandle.Complete();
waterMesh.SetVertices(meshModificationJob.vertices);
waterMesh.RecalculateNormals();
}
private void OnDestroy()
{
waterVertices.Dispose();
waterNormals.Dispose();
}
[BurstCompile] // <-- 요 어트리뷰트만 추가해 주면 끝.
private struct UpdateMeshJobWithBurst : IJobParallelFor
{
public NativeArray<Vector3> vertices;
[ReadOnly]
public NativeArray<Vector3> normals;
public float offsetSpeed;
public float scale;
public float height;
public float time;
private float Noise(float x, float y)
{
float2 pos = math.float2(x, y);
return noise.snoise(pos);
}
public void Execute(int i)
{
if (normals[i].z > 0f)
{
var vertex = vertices[i];
float noiseValue = Noise(vertex.x * scale + offsetSpeed * time, vertex.y * scale + offsetSpeed * time);
vertices[i] = new Vector3(vertex.x, vertex.y, noiseValue * height + 0.3f);
}
}
}
}
사용법이 정말 쉬운데..
그냥 기존에 만들어진 잡 구조체 위에
[BurstComplie] 어트리뷰트만 추가해 주면 끝입니다.
이제 실행해 보면
대략 110 ~ 120 프레임을 왔다 갔다 하는데
확실히 성능 향상이 있네요.
유니티의 잡 시스템 기능을 테스트해보면서
뭔가 대량의 연산이 필요한 곳에 적절히 사용하면
확실히 성능 면에서 효과를 볼 수 있을 것 같습니다.
'Programming > Unity3D' 카테고리의 다른 글
[Unity] URP 프로젝트에서 Camera Stacking 하는 방법 (0) | 2022.04.22 |
---|---|
[Unity] URP 프로젝트에서 SkyBox 변경하는 방법 (0) | 2022.04.22 |
[Unity3D] Unity Ads (광고) 붙이는 방법 (0) | 2016.05.16 |
[Unity3D] 유니티 UI (UGUI) 에서 이미지 아틀라스 묶는 방법 (1) | 2016.05.04 |
[Unity3D] 유니티에 Google Spread Sheet 연동하는 방법 (7) | 2016.04.22 |