ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Signed Distance Fields
    Graphics/Graphics Study 자료 2020. 4. 13. 00:27

     

     

    Signed Distance Fields

     

    최초작성 : 2020년 4월 10일

    최종수정 : 2020년 4월 10일

    최재호

     

     

    목표

    SDF의 의미 파악과 실제 활용과 구현에 대해서 알아보자

     

    소개

    Signed Distance Fields(이후 SDF로 부름) 는 아래와 같이 Geometry의 내부는 +, 외부는 -, 그리고 Edge에 근접할 수록 0이 되는 형태의 데이터 입니다.

    예를들어 아래와 같은 Geometry가 있다고 해봅시다.

     

     

    레퍼런스3 에서 가져온 그림

     

     

    SDF 로 표현하면 아래와 같습니다.

     

     

    레퍼런스 3에서 가져온 그림

     

     

     

    3차원으로 표시하게 되면 아래와 같습니다.

     

     

    레퍼런스 3에서 가져온 그림

     

     

    SDF는 주로 폰트 렌더링시 외곽선이 깔끔하게 나오지 않는 문제를 해결하기 위해서 사용합니다.

    아래 이미지 처럼 AlphaBlend나 AlphaTest 두가지 방법다 깔끔한 에지를 만들어주지 못하지만 맨 우측의 SDF를 사용한 방식은 외곽선이 부드럽게 나옵니다.

     

     

    레퍼런스1에서 가져온 그림

     

     

    디스턴스 필드는 아래와 같은 형태입니다.

     

     

    레퍼런스1에서 가져온 그림

     

     

     

    이렇게 하기 위해서 다음과 같은 과정을 거칩니다.

    1. 고해상도 (예를들면 4096x4096) 이미지 에서 SDF 를 생성합니다. 이때 생성한 SDF는 저해상도 입니다.

    2. 폰트를 렌더링 할때 1에서 만든 SDF를 사용하여 AlphaTest를 처리합니다.

     

    SDF를 깔끔한 외곽선 처리, 그리고 Outline 그리고 폰트의 Shadow 효과에도 활용할 수 있습니다.

     

    실제구현

    추후 SDF 생성기를 이 글에 추가할 예정입니다. 바로 SDF와 기존 방식의 비교로 들어갑니다.

     

    아래 예제는 총 3가지 방식의 폰트 렌더링 예제입니다.

    1. AlphaBlend

    2. AlphaTest

    3. SDF (+ Outline 출력)

     

    테스트에 사용한 기본 텍스쳐들은 레퍼런스4번 링크에서 가져왔습니다. 가져온 텍스쳐는 폰트가 담겨있는 4096x4096 텍스쳐, 그리고 SDF Generator로 제작한 1024x1024 사이즈의 SDF 텍스쳐 입니다.

    저는 원본 폰트가 담겨있는 4096x4096 텍스쳐를 1024x1024 사이즈로 줄여서 AlphaBlend와 AlphaTest 비교에 사용하였습니다. 즉, AlphaBlend/AlphaTest/SDF 에 사용한 모든 텍스쳐는 동일하게 모두 1024 x 1024 사이즈 텍스쳐입니다.

     

    확대했을 경우에 폰트 깨짐 문제가 더 명확하게 들어나기 때문에 확대한 상태로 각각의 3가지 형태로 폰트를 렌더링 해보았습니다.

     

    사용된 SDF 텍스쳐

     

    사용된 SDF 텍스쳐, SDF 레퍼런스4에서 가져옴

     

     

     

    1. AlphaBlend (1024 X 1024)

     

    AlphaBlend (1024 X 1024)

     

     

    2. AlphaTest (1024 X 1024)

     

    AlphaTest (1024 X 1024)

     

     

    3. SDF (1024 x 1024)

     

    SDF (1024 x 1024)

     

     

    3. SDF with outline (1024 x 1024)

     

    SDF with outline (1024 x 1024)

     

     

    같은 해상도의 텍스쳐를 사용했더라도 확대한 경우 발생하는 Artifacts는 SDF 를 사용한 경우가 더 적다는 것을 확인할 수 있습니다.

     

    사용한 C++ 코드

    MainCamera->IsEnableCullMode = true;
    
    //auto ClearColor = Vector4(135.0f / 255.0f, 206.0f / 255.0f, 250.0f / 255.0f, 1.0f);	// light sky blue
    auto ClearColor = Vector4(0.0f);
    auto ClearType = ERenderBufferType::COLOR | ERenderBufferType::DEPTH;
    auto EnableDepthTest = true;
    auto DepthStencilFunc = EComparisonFunc::LESS;
    auto EnableBlend = true;
    auto BlendSrc = EBlendSrc::ONE;
    auto BlendDest = EBlendDest::ZERO;
    auto Shader = jShader::GetShader("Simple");
    
    g_rhi->SetClearColor(ClearColor);
    g_rhi->SetClear(ClearType);
    
    g_rhi->EnableDepthTest(EnableDepthTest);
    g_rhi->SetDepthFunc(DepthStencilFunc);
    g_rhi->SetDepthMask(true);						// Depth write on
    
    g_rhi->EnableBlend(EnableBlend);				// Blend on
    g_rhi->SetBlendFunc(BlendSrc, BlendDest);		// Src One, Dst Zero
    
    auto& PropertiesInstance = jShadowAppSettingProperties::GetInstance();
    
    static bool Initialized = false;
    if (!Initialized)
    {
    	Initialized = true;
    	{
    		jImageData data;
    		jImageFileLoader::GetInstance().LoadTextureFromFile(data, "Image/font.png", false);
    		SDFFontTexture = g_rhi->CreateTextureFromData(&data.ImageData[0], data.Width, data.Height, data.sRGB);
    	}
    	{
    		jImageData data;
    		jImageFileLoader::GetInstance().LoadTextureFromFile(data, "Image/test2.png", false);
    		FontTexture = g_rhi->CreateTextureFromData(&data.ImageData[0], data.Width, data.Height, data.sRGB);
    	}
    	FontPrimitive = jPrimitiveUtil::CreateUIQuad(Vector2(10, 10), Vector2(2840, 2840), true, nullptr, nullptr);
    }
    
    switch (PropertiesInstance.RenderType)
    {
    case ESDFTest::AlphaBlend:
    	Shader = jShader::GetShader("UIShader");
    	FontPrimitive->RenderObject->tex_object = FontTexture;
    	FontPrimitive->RenderObject->samplerState = jSamplerStatePool::GetSamplerState("LinearClamp").get();
    	g_rhi->EnableBlend(true);
    	break;
    case ESDFTest::AlphaTest:
    	Shader = jShader::GetShader("UIAlphaTestShader");
    	FontPrimitive->RenderObject->tex_object = FontTexture;
    	FontPrimitive->RenderObject->samplerState = jSamplerStatePool::GetSamplerState("LinearClamp").get();
    	g_rhi->EnableBlend(false);
    	break;
    case ESDFTest::SDF:
    	Shader = jShader::GetShader("UISDFShader");
    	g_rhi->SetShader(Shader);
    
    	static auto* OutlineUniformBuffer = new jUniformBuffer<bool>("UseOutline", false);
    	OutlineUniformBuffer->Data = PropertiesInstance.EnableOutline;
    	OutlineUniformBuffer->Bind(Shader);
    	FontPrimitive->RenderObject->tex_object = SDFFontTexture;
    	FontPrimitive->RenderObject->samplerState = jSamplerStatePool::GetSamplerState("LinearClamp").get();
    	g_rhi->EnableBlend(false);
    	break;
    default:
    	break;
    }
    		
    FontPrimitive->Draw(MainCamera, Shader, { });

     

    AlphaBlend 에 사용한 PixelShader

    #version 330 core
    
    precision mediump float;
    
    uniform sampler2D tex_object;
    uniform int TextureSRGB[1];
    in vec2 TexCoord_;
    
    out vec4 color;
    
    void main()
    {
        color = texture2D(tex_object, TexCoord_);
    
    	if (TextureSRGB[0] > 0)
    		color.xyz = pow(color.xyz, vec3(2.2));
    }

     

    AlphaTest 에 사용한 PixelShader

    #version 330 core
    
    precision mediump float;
    
    uniform sampler2D tex_object;
    uniform int TextureSRGB[1];
    in vec2 TexCoord_;
    
    out vec4 color;
    
    void main()
    {
        color = texture2D(tex_object, TexCoord_);
    
    	if (color.a < 0.5)		// Alpha Test Threshold
    		discard;
    
    	if (TextureSRGB[0] > 0)
    		color.xyz = pow(color.xyz, vec3(2.2));
    }

     

    SDF 에 사용한 PixelShader

    #version 330 core
    
    precision mediump float;
    
    uniform sampler2D tex_object;
    uniform int TextureSRGB[1];
    uniform int UseOutline;
    in vec2 TexCoord_;
    
    out vec4 color;
    
    void main()
    {
        color = texture2D(tex_object, TexCoord_);
    	
    	if (UseOutline > 0)
    	{
    		if (color.r < 0.4)
    			discard;
    		
    		if (color.r < 0.47) 
    			color = vec4(1.0, 0.0, 0.0, 1.0);
    		else 
    			color = vec4(1.0, 1.0, 1.0, 1.0);
    	}
    	else
    	{
    		if (color.r < 0.5)
    			discard;
    
    		color = vec4(1, 1, 1, 1);
    	}
    	
    	if (TextureSRGB[0] > 0)
    		color.xyz = pow(color.xyz, vec3(2.2));
    }

     

     

    최종결과 비교용 GIF

     

     

    예제코드

    https://github.com/scahp/Shadows/tree/SDF

     

    레퍼런스

    1. https://lifeisforu.tistory.com/357

    2. https://lifeisforu.tistory.com/358

    3. https://github.com/Chlumsky/msdfgen/files/3050967/thesis.pdf  

    4. https://github.com/jkevin1/SDF

     

     

     

    'Graphics > Graphics Study 자료' 카테고리의 다른 글

    Exponential Shadow Map(ESM)  (0) 2020.04.23
    Variance Shadow Map(VSM)  (0) 2020.04.16
    Light Indexed Deferred Rendering  (0) 2020.04.03
    DeepShadowMap(DSM)  (0) 2020.02.25
    Dual Paraboloid ShadowMap  (0) 2020.02.18

    댓글

Designed by Tistory & scahp.