TECHARTNOMAD | TECHARTFLOWIO.COM

UNREAL ENGINE

Custom Expression 구조체 형식 함수화 버그(?) 회피.

jplee 2023. 10. 20. 01:45

캐릭터 팀 서포트 하는 과정에서 전체 머트리얼 함수를 유지보수에 용이 한 형태로 리펙토링 하도록 컨설팅 측면에서 order 를 최근에 했습니다. 내부 테크니컬 아티스트 파트에 연결 된 분이 주니어 이기 때문에 여러가지 측면에서 이슈를 탐색 해 드리고 있는데요... 머트리얼 에디터의 노드 구성 의존형에 대해서 저 개인적으로 매우 비효율적이고 직관성이 떨어진다고 생각하기 때문에 언리얼 머트리얼 노드 에디터에서 함수와 머트리얼 어트리뷰트 단에서의 깔끔한 구조화에 좀 집착하는 편이기도 한데....  그런 과정에서의 하나의 꼭지 입니다. 


버그 라고 하기에는 애초에 문제가 있다.

아래 기사는 언리얼엔진에서 정한 형식에 따라 작성 해야 하는 셰이더 형식에 대한 복기에 가깝다.
피부 렌더링 처리 중 Custom 노드를 사용한 셰이더코드 작성 하는 과정에서 구조체 형식을 비러서 작성 된 단일 함수 리턴 형식의 익스프레션 노드 사용의 문제점이 있다.

문제점 이라기 보다는 구조체 형식으로 작성 된 셰이더 코드는 하나의 구조체 안에 다중 함수가 존재 하도록 허용하는데 이 방안을 규범이라고 생각 하고 사용 했어야 했다.

문제점에 대한 예로 들어 보자면…

 

Conflict case example.

UnpackNormalRG.usf  라는 셰이더 함수 블럭을 담은 usf 파일이 있을 때 이 usf 의 구성은 아래…

//2023년 10월20일 최조 작성
#pragma once

struct UNPACKNORMALRG 
{
    
    float3 Output(float4 packedNormal, float scale = 1.0)
    {
        float3 normal;
        normal.xy = packedNormal.rg * 2.0 - 1.0;
        normal.z = max(1.0e-16, sqrt(1.0 - saturate(dot(normal.xy, normal.xy))));
        normal.xy *= scale;
        return normalize(normal);
        
    }           
    
};

UNPACKNORMALRG material;
return material.Output(InputSample , InputScale);

이렇게 구조체 안에 하나의 함수가 있고 바로 아웃풋으로 리턴 하는 형식이다.

 

이 구조의 문제점은… 

만약 같은 머트리얼 내부 함수를 작성 할 떄 노말맵과 디테일 노말맵을 구성 할 때가 있는데 동일한 Custom  노드를 동일한 랩핑 된 함수 안에서 중복 사용 하면 안된다. 코드 생성 단계에서 문제가 발생한다. 미묘한…

 

이러한 구조체 형식은 애초에 다중함수를 허용하는 사용자 셰이더 코드에 적합한 언리얼 엔진 내부 규약으로서… 

 

아래와 같이 구성을 해야 한다.

베이스 노말과 추가될 디테일 노말을 처리하는 일괄 함수화.

//2023년 10월15일 최조 작성
//베이스 노멀맵과 디테일 노말맵 혼합 함수 재형성
#pragma once

struct UnpackNormalDualBlend
{
    
    float3 BaseNormal(float4 packedNormal, float scale = 1.0)
    {
        //베이스노멀 함수의 연산에서 Scale 은 제거 했음. 
        //다만 판단에 따라 추가 될 수 있기 때문에 매개변수에 정의는 해 놈. 
        //실제 인자는 전달 되지 않고 있다.
        float3 normal;
        normal.xy= packedNormal.rg * 2.0 - 1.0;
        normal.z = max(1.0e-16, sqrt(1.0 - saturate(dot(normal.xy, normal.xy))));
        return normalize(normal);
    }           
    
    float3 AdditionalNormal(float4 packedNormal, float scale = 1.0)
    {
        float3 normal;
        normal.xy= packedNormal.rg * 2.0 - 1.0;
        normal.z = max(1.0e-16, sqrt(1.0 - saturate(dot(normal.xy, normal.xy))));
        normal.xy *= scale;
        return normalize(normal);
    }

    float3 BlendNRM(float3 n1, float3 n2)
    {
        float3 t = n1.xyz + float3(0.0, 0.0, 1.0);
        float3 u = n2.xyz * float3(-1.0, -1.0, 1.0);
        float3 r = (t / t.z) * dot(t, u) - u;
        return r;
    }    

    float3 DualNRM(float4 normalBase , float4 normalAdditional, float scaleBase = 1.0, float scaleAdditional = 1.0)
    {
        float3 baseValue = BaseNormal(normalBase, scaleBase);
        float3 additionalValue = AdditionalNormal(normalAdditional, scaleAdditional);
        float3 blendedNormalValue = BlendNRM(baseValue ,additionalValue );
        return blendedNormalValue;
    }

};

UnpackNormalDualBlend material;
return material.DualNRM(InputSampleBase , InputSampleAdditional, InputScaleBase, InputScaleAdditional);

하나의 구조체 안에 다중 함수 형식에 맞게 구현 된 Custom Node