MaterialPropertyBlock

반응형

50x50 같은 물체가 있다고 가정하자.

이들 물체의 색은 상황에 따라 몇가지 색상으로 변경될 수 있다.

즉, 하나의 물체의 색상이 빨간색, 파란색, 노란색 등 바뀔 수 있다는 것이다.

2500개의 물체를 그리더라도 색상이 다 같다면 배치는 한번만 이루어 진다.

하지만 2500개의 물체가 다 다른 색상을 가진다면 배치는 물체의 수만큼 이루어 진다.


!테스트에 사용된 리소스와 소스파일은 가장 아래에 있습니다.!


테스트 1. 단순하게 2500개의 물체 뿌리기

Test Code

GameObject[] objects;

void Start()

{

if (sphere)

{

float colorOffset = square * square;

objects = new GameObject[square * square];

for (int xi = 0; xi < square; xi++)

{

for (int zi = 0; zi < square; zi++)

{

GameObject go = Game.CreateDynamicObject(sphere, this.gameObject.transform);

go.transform.position = new Vector3(xi, 0, zi % square);

go.name = "sphere" + xi.ToString() + "_" + zi.ToString();

objects[xi * square + zi] = go;

}

}

}

}

결과값

material : 40

setpass call : 2

draw calls : 2501

(static batching)batches : 0

참고로 테스트 화면이 기본 디폴트 화면이라서 스카이 박스나 플랜등 기본적으로 쓰는 값이 있다.


테스트 2. static batching

Test Code

..

StaticBatchingUtility.Combine(gos, this.gameObject);

..

}

결과값

첫번째 테스트에서 staticbatching만 추가했다.

머티리얼이 바뀌지 않는이상 머티리얼은 하나로 사용하기 때문에 배칭이 될 수 있다.

material : 40

setpass call : 2

draw calls : 102

(static batching)batches : 101

배칭에 관한 내용은 이곳에 정리를 한 적이 있어서 다시 자세히 언급하지는 않고 패스한다.


테스트 3. 색상 바꾸기.

Test Code

첫번째 테스트에서 material.color = ...만 추가되었다.

objects[xi * square + zi].material.color = new Color(rCol + (xi * square + zi) / colorOffset, gCol, bCol);


결과값

material : 2540

setpass call : 2501

draw calls : 2501

(static batching)batches : 0

위 결과값에서 보면 알 수 있듯이 머티리얼 갯수가 기존 40개에서 2540개로 늘어났다.


테스트 4. MaterialPropertyBlock 사용

Test Code

첫번째 테스트에서 Offset 관련 코드만 추가되었다.

go.GetComponent<SphereWithMaterialPropertyBlock>().Offset = (xi * square + zi) / colorOffset;

그리고 물체 프리팹에 아래와 같은 코드를 달아 주었다.

void Start()

{

    _propBlock = new MaterialPropertyBlock();

    _renderer = GetComponent<Renderer>();

    _renderer.GetPropertyBlock(_propBlock);

    _propBlock.SetColor("_Color", Color.Lerp(Color1, Color2, (Mathf.Sin(Time.time * Speed + Offset) + 1) / 2f));

    _renderer.SetPropertyBlock(_propBlock);

}


결과값

material : 40

setpass call : 2501

draw calls : 2501

(static batching)batches : 0

render opaque geometry : 3.26

위 결과값에서 보면 알 수 있듯이 머티리얼 갯수가 테스트 3에서 컬러바꿀 때와 다르게

2540개가 아니라 40개로 2500개가 줄었다.


테스트 5. material.color update(animation)

Test Code

업데이트 함수에서 컬러를 계속 변경해준다.

void Update()

{

float colorOffset = square;

if (objects[0] == null)

return;

for (int xi = 0; xi < square; xi++)

for (int zi = 0; zi < square; zi++)

objects[xi * square + zi].material.SetColor("_Color", Color.Lerp(Color.red, Color.yellow, (Mathf.Sin(Time.time * 1.0f + (xi * square + zi) / colorOffset) + 1) / 2f));

}


결과값

material : 2540

Render.OpaqueGeometry : 9.03

Material.SetPassUncached : 6.3

Color Change 관련 Update : 5.49


테스트 6. MaterialPropertyBlock update(animation)

Test Code

첫번째 테스트에서 Offset 관련 코드만 추가되었다.

go.GetComponent<SphereWithMaterialPropertyBlock>().Offset = (xi * square + zi) / colorOffset;

그리고 물체 프리팹에 아래와 같은 코드를 달아 주었다.

void Start()

{

    ..

   go.GetComponent<SphereWithMaterialPropertyBlock>().Offset = (xi * square + zi) / colorOffset;

    ..

}

각 프리팹의 스크립트에서 아래와 같이 업데이트 해 준다.

void Update()

 {

      _renderer.GetPropertyBlock(_propBlock);

      _propBlock.SetColor("_Color", Color.Lerp(Color.red, Color.yellow, (Mathf.Sin(Time.time * Speed + Offset) + 1) / 2f));

      _renderer.SetPropertyBlock(_propBlock);

}

그리고 셰이더 코드의 컬러값에 [PerRendererData]을 추가해 준다.


결과값

material : 40

Render.OpaqueGeometry : 2.90

Material.SetPassUncached : 없음

Color Change 관련 Update : 2.64


테스트 5와 테스트 6의 결과를 비교해 보면 Material 갯수가 2540에서 40으로 줄어들었고

렌더링 하기 위한 시간이 9.03에서 2.90으로 6.13정도 줄었다.

이는 아마 setpass 관련 시간인듯 하다.

업데이트 시간을 보면 5.49에서 2.64로 줄었다.

이제 정리를 해 보자.

Material을 접근 할 때는 Renderer.sharedMaterial과 Renderer.material 이렇게 두가지가 있다.

일반적으로 렌더링 시 sharedmaterial을 참조해서 랜더링 하는 이는 하나의 머티리얼을 공용으로 

사용하는 것이고 이를 통해 배칭이 가능하게 된다.

하지만 여기에 수정을 가해야 할 때(예를들어 머티리얼의 컬러값을 바꿔야 할 때) 생각해야 할 부분이 있다.

10개의 물체가 같은 머티리얼을 공유하는 중에 sharedmaterial.color값을 수정하면 모든 물체의 color값이 바뀐다.

이걸 원한다면 다행이지만 그렇지 않다면 material.color을 수정해야 하는데 이렇게 되면 하나의 물체의 컬러만 

바꿀 수 있긴 하지만 material을 참조하는 순간 내부적으로 sharedmaterial의 사본이 생성되어 material에 할당된다.

하지만 MaterialPropertyBlock을 사용하면 사본이 생성되지 않는다.

그리고 모든 물체들이 다시 같은 색상 파라미터값을 가지게 되면 배칭이 된다.

아래 참고 글중 "Setting MaterialPropertyBlocks breaks batching"을 인용하자면,


"배칭은 GPU로 GPU로 보내기 전에 여러 오브젝트의 지오메트리를 결합하여 하나의 객체로

렌더링하는 것을 의미한다. 당연히 모든 셰이더는 정점 데이터의 일부가 아니라 전반적으로 같아야 한다.

그래서 머티리얼 프로퍼티를 바꾸면, 어떤 짓을 하더라도, 프로퍼티가 같지 않는 한 오브젝트는 배칭이 되지 않는다.

MaterialPropertyBLock은 파라미터 값이 많이 바뀌는 많은  인스턴스가 있는 상황을 위한 것이다.

그래서 머티리얼을 복제할 필요는 없지만 동적으로 전역 셰이더를 동일한 MaterialPropertyBlock로 설정한다.

이는 드로우콜이 절약되지는 않지만 오버헤드는 절약된다. (뒤의 이야기는 질문자의 코드에서 사용번 관련 이야기)"


그리고 유니티 도큐먼트에 보면 "같은 머티리얼을 가진 여러개의 오브젝트를 그리고 싶은데 프로퍼티 값이

살짝씩 다른경우에 사용한다. 예를 들면 각 메시를 그릴 때 색사을 살짝 다르게 하는 정도며 렌더 스테이트의

변경은 지원되지 않는다. 유니티 지형 엔진은 나무를 그리기 위해 이 기능을 사용하고 있다.

모든 나무가 같은 머티리얼이지만 다른 색상, 크기, 그리고 바람 속성값을 가진다.

Graphics.DrawMesh나 Renderer.SetPropertyBlock에게 전달된 블럭은 복사되어지며, 

그래서 이 기능을 가장 효율적으로 사용하는 방법은 하나의 블럭을 만들어서 모든 DrawMesh 호출에 

재활용 하는 것이다."라고 적혀있다.


아래 링크중 "The Magic Of Material Property Blocks"을 참고하면

"셰이더 파일에 [PerRendererData] 속성으로 셰이더 프로퍼티를 명시해 줘야 한다.

이걸 추가 해 주면 이 셰이더를 사용하는 모든 렌더러에 공유되는 대신 해당 속성(예를 들면 _Color)

이 렌더러별로 설정되는 방식으로 유니티에 셰이더를 컴파일 하도록 요청한다.

그렇지 않으면 SetPropertyBlock이 작동은 하지만 내부적으로 머티리얼 인스턴스를 만들어서

renderer.material과 같은 결과가 된다." 라고 적혀 있어서 그렇게 하긴 했는데

실제로 저 [PerRendererData]를 넣는 것과 넣지 않는것에 대한 프로파일러의 변화값을 못찾겠다.


testProperty.unitypackage


Reference Link

- The Magic Of Material Property Blocks

- The Magic Of Material Property Blocks 번역

- Dynamic Batching이 효율적일까?

- unity, MaterialProperty

- Setting MaterialPropertyBlocks breaks batching

- unity, ShaderLab: Properties



'Optimizing > Unity' 카테고리의 다른 글

Mesh 최적화  (0) 2017.04.11
Dynamic Batching이 효율적일까?  (0) 2017.03.31
유니티 텍스처  (0) 2017.03.22
animation 최적화  (0) 2017.03.04
유니티에서의 메모리 관리.  (0) 2017.01.13
TAGS.

Comments