유니티 2018.1 버전에서 공개된
잡 시스템 (Job System)에 대해서
공부도 할 겸 테스트 진행해 보았습니다.
간단하게 말하면 멀티 스레드를 쉽게 이용할 수 있게
만들어 놓은 시스템 정도로 생각하면 될 것 같습니다.
스레드를 사용하려고 하면 Race Condition이나
Context Switching 등등 신경 써야 될 것들이 많은데
그런 것 신경 안 쓰고 멀티 스레드 기능을 활용할 수 있으면
훨씬 편하고 좋을 것 같긴 하네요.
일단 아래 사이트를 참조해서 테스트했습니다.
(샘플용 메시도 제공해 주고 설명도 아주 잘 되어 있습니다)
https://www.raywenderlich.com/7880445-unity-job-system-and-burst-compiler-getting-started
Unity Job System and Burst Compiler: Getting Started
In this tutorial, you’ll learn how to use Unity’s Job System and Burst compiler to create efficient code to simulate water filled with swimming fish.
www.raywenderlich.com
유니티 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 |