WinCNT

URP에서 HDR 텍스처 사용해보기 본문

Unity/URP or Shader 관련

URP에서 HDR 텍스처 사용해보기

WinCNT_SSS 2023. 7. 3. 17:57

서론

현재 수정 작업을 맡은 셰이더가 있는데, Emissive를 추가하려고 보니 Emissive Color 프로퍼티가 없었다

사수에게 확인해보니 이 셰이더는 Emissive는 HDR 텍스처로 컨트롤할 방침이라는 얘기를 들었다

그럼 뭐다? 구현은 내가 찾아서 해야 한다는 거지…


HDR Texture

테스트용 HDR Texture는 아트 팀장님이 포토샵으로 만들어주셨다

포토샵의 컬러 피커로 Cyan색에 강도를 추가한 단색 텍스처이다

HDRI_Test_hdr.hdr
0.07MB

사실 아트 팀장님께 부탁하기 전에 혼자서 Photopea에서 만들어 보려고 했지만, HDR 관련 항목을 찾지 못해 실패했다

이게 될지 안 될지는 해봐야 안다…


DecodeHDR 함수와 _HDR

HDR 파일을 임포트 해서 텍스처로 사용하면!

놀랍게도 아무 일도 일어나지 않는다…

이것은 유니티의 디폴트 셰이더인 Lit 셰이더 또한 마찬가지였다

즉 나나 커스텀 셰이더의 문제는 아니라는 뜻

역시 별도의 처리가 필요한 것 같았다

 

그래서 인터넷을 찾아보니 다음과 같은 공식 문서의 문구를 발견할 수 있었다

Cg/HLSL でシェーダープロパティを参照する - Unity マニュアル

 

Cg/HLSL でシェーダープロパティを参照する - Unity マニュアル

シェーダーは Material プロパティを Properties ブロックで宣言します。これらのプロパティのいくつかに シェーダプログラム でアクセスしたい場合は、Cg/HLSL 変数を同じ名前と一致する型で宣

docs.unity3d.com

💡 텍스처 HDR 파라미터
{TextureName}_HDR
사용되는 색 공간에 따라 HDR(예: RGBM으로 인코딩된) 텍스처를 잠재적으로 어떻게 디코딩할지에 대한 정보를 포함한 float4 프로퍼티입니다.
UnityCG.cginc 셰이더 포함 파일에서 DecodeHDR 함수를 참조하십시오.

조금 살펴보니 hdr를 사용할 텍스처의 이름에 _HDR를 붙인 변수를 따로 선언해야 하고, DecodeHDR 함수 등을 이용해서 HDR의 값을 얻어야 하는 것 같았다


UnityCG.cginc의 코드로 테스트

매우 귀찮게도 URP에서는 여러 이유로 UnityCG.cginc를 사용이 권장되지 않는다…

그래서 DecodeHDR 함수를 복붙해서 테스트를 해보기로 했다

 

다음은 여러가지를 생략한 테스트 코드이다

// ...전략...

HLSLPROGRAM
CBUFFER_START(UnityPerMaterial)
			// ...중략...
      float4    _EmissionMap_HDR;
			// ...중략...
CBUFFER_END

// ...중략...

// Decodes HDR textures
// handles dLDR, RGBM formats
inline half3 DecodeHDR(half4 data, half4 decodeInstructions, int colorspaceIsGamma)
{
    // Take into account texture alpha if decodeInstructions.w is true(the alpha value affects the RGB channels)
    half alpha = decodeInstructions.w * (data.a - 1.0) + 1.0;

    // If Linear mode is not supported we can skip exponent part
    if(colorspaceIsGamma)
        return (decodeInstructions.x * alpha) * data.rgb;

    #if defined(UNITY_USE_NATIVE_HDR)
        return decodeInstructions.x * data.rgb; // Multiplier for future HDRI relative to absolute conversion.
    #else
        return (decodeInstructions.x * pow(alpha, decodeInstructions.y)) * data.rgb;
    #endif
}
// Decodes HDR textures
// handles dLDR, RGBM formats
inline half3 DecodeHDR (half4 data, half4 decodeInstructions)
{
    #if defined(UNITY_COLORSPACE_GAMMA)
        return DecodeHDR(data, decodeInstructions, 1);
    #else
        return DecodeHDR(data, decodeInstructions, 0);
    #endif
}

// ...중략...

float3 frag(v2f i) : SV_Target
{    
    float4 emissiveColor = tex2D(_EmissionMap, i.uv);
    emissiveColor.rgb = DecodeHDR(emissiveColor, _EmissionMap_HDR);
    return emissiveColor;
}
ENDHLSL

// ...후략...

상수 버퍼에 _EmissionMap_HDR를 추가했고, DecodeHDR 함수는 UnityCG.cginc에서 가져왔으며, 사용은 다른 셰이더랑 인터넷의 레퍼런스를 참고했다

 

그 결과는…

따란~!


URP에서는 뭘 써야 할까?

문제는 코드를 복붙하는 방식을 피하고 싶다는 것!

이거 Meta Passd에서도 써야한다고~!!

 

그리고 잘 생각해면 이러한 처리가 URP에서 사라질리 없다…어딘가에 이름만 다른 함수가 존재할 것이다…라고 생각해서 무작정 쳐보니 라이더가 딱 봐도 그럴듯한 함수를 알려주었다

다행히 현재 수정 중인 셰이더는 Lighting.hlsl를 인클루드하고 있어서 EntityLighting.hlsl에 있는 DecodeHDREnvironment() 함수를 사용할 수 있었다

그래도 혹시 몰라 어떤 처리를 하는지 살펴보니…

real3 DecodeHDREnvironment(real4 encodedIrradiance, real4 decodeInstructions)
{
    // Take into account texture alpha if decodeInstructions.w is true(the alpha value affects the RGB channels)
    real alpha = max(decodeInstructions.w * (encodedIrradiance.a - 1.0) + 1.0, 0.0);

    // If Linear mode is not supported we can skip exponent part
    return (decodeInstructions.x * PositivePow(alpha, decodeInstructions.y)) * encodedIrradiance.rgb;
}

DecodeHDR와 거의 비슷했길래 DecodeHDREnvironment() 를 사용해보기로 했다

inline half3 DecodeHDR(half4 data, half4 decodeInstructions, int colorspaceIsGamma)
{
    // Take into account texture alpha if decodeInstructions.w is true(the alpha value affects the RGB channels)
    half alpha = decodeInstructions.w * (data.a - 1.0) + 1.0;

    // If Linear mode is not supported we can skip exponent part
    if(colorspaceIsGamma)
        return (decodeInstructions.x * alpha) * data.rgb;

    #if defined(UNITY_USE_NATIVE_HDR)
        return decodeInstructions.x * data.rgb; // Multiplier for future HDRI relative to absolute conversion.
    #else
        return (decodeInstructions.x * pow(alpha, decodeInstructions.y)) * data.rgb;
    #endif
}

 

이쪽은 마찬가지로 여러가지 생략한 테스트 코드이다

(Meta Pass도 같은 처리로 변경했다)

// ...전략...
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

HLSLPROGRAM
CBUFFER_START(UnityPerMaterial)
			// ...중략...
      float4    _EmissionMap_HDR;
			// ...중략...
CBUFFER_END

// ...중략...
float3 frag(v2f i) : SV_Target
{    
    float4 emissiveColor = tex2D(_EmissionMap, i.uv);
    emissiveColor.rgb = DecodeHDREnvironment(emissiveColor, _EmissionMap_HDR);
    return emissiveColor;
}
ENDHLSL

// ...후략...

 

그 결과는…

Profit!!


마무리

단색 HRDI 텍스처 만들기부터 잘 안 되서 삽질을 좀 했다

처음부터 아트 팀장님께 부탁하면 삽질을 안 했겠지만, 아무리 그래도 바쁜 사람에게 단색 HDR Texture 만들어 주세요!라고 하는 건 쪽팔리지미안하지 않는가!

 

그리고 HDRI를 금방 만들었다고 해도 그 뒤에 별도의 처리가 필요한지 몰랐으니 어차피 삽질은 예정되어 있었다

반대로 HDRI가 이상하지 않다는 확신이 없었으면 별도의 처리가 필요한지 깨닫기까지 수많은 삽질이 있었을 것이다

개발은 참으로 어렵다…


참고 사이트

Cg/HLSL でシェーダープロパティを参照する - Unity マニュアル

 

Cg/HLSL でシェーダープロパティを参照する - Unity マニュアル

シェーダーは Material プロパティを Properties ブロックで宣言します。これらのプロパティのいくつかに シェーダプログラム でアクセスしたい場合は、Cg/HLSL 変数を同じ名前と一致する型で宣

docs.unity3d.com

モバイルでのHDRテクスチャの扱い - BeXide Tech Blog

 

モバイルでのHDRテクスチャの扱い - BeXide Tech Blog

#Tech #Unity #Shader Unity で HDRテクスチャが使用できることは存外知られていません。知っている,という人もせいぜい IBR の空に使うくらいではないでしょうか。 HDRテクスチャによるレンダリン

scrapbox.io