TECHARTNOMAD | TECHARTFLOWIO.COM

TECH.ART.FLOW.IO

[번역] UDF 및 얼굴 SDF 섀도 맵의 GPU 생성

jplee 2024. 6. 25. 23:20

저자 : 일출(破晓) - 인터넷 이름

오랫동안 무언가를 쓰지 않으면서 재활의 생활을 했다. 사실 무엇을 쓸 생각은 없었지만 최근에는 더스트 화이트 플레이에서 필요한 텍스처의 이진화 된 이미지에서 UDF 및 얼굴 SDF 그림자를 생성하는 데 사용되는 SDF 생성 도구에서 Unity를 자연스럽게 잭으로 만들기로 결정했습니다. 얼굴 SDF 섀도우 매핑은 [나는 매일 보지만 개인적인 일과 관심에 의해 제한되어 마침내 만지지 않은 것들을 만지고, 그 느낌을 적는 것은 괜찮습니다.]

역자 주 : UDF : Unsigned Distance Field / SDF : Signed Distance Field 로 보면 됩니다. 궁금하시다면 예전 역자의 번역글 중 아래 내용을 참조.
 

[요약번역]GDC 2023 - Real-time Sparse Distance Fields for Games 파트-1 개요

역자의 말. 번역 주제를 딱히 정하지는 않지만 meso structure 에 대해서 좀 살펴보다가 Youtube 타고 뭐 좀 찾다보니 오랫만의 GDC2023 에서 AMD 가 공개했던 발표내용이 있어서 차분하게 번역 해 봅니다.

techartnomad.tistory.com


이론

사이토 알고리즘

유클리드 거리를 수평 및 수직 방향의 값으로 분해한 데서 유래했습니다. 참조:유클리드 거리 변환(EDT) 알고리즘

즉, 첫 번째 단계에서는 각 픽셀 포인트에 대해 수평 방향에서 가장자리와 가장 가까운 거리의 제곱을 계산합니다.

두 번째 단계에서는 각 픽셀에 대해 각 픽셀에서 이 픽셀까지의 수직 방향 거리의 제곱을 계산하여 첫 번째 단계의 값에 더합니다. 이 열의 결과의 최소값은 거리의 제곱입니다.

얼굴 SDF 음영 계산

얼굴 SDF 셰이딩은 미리 그려진 일련의 그림자 맵에 의존하며, 작은 이미지는 정면에서 들어오는 빛에 해당하고 큰 이미지는 측면에서 들어오는 빛에 해당한다고 가정하고, 음영 영역은 하나씩 증가하며 반복되는 영역은 없을 수 있습니다.

이러한 이미지를 더하면 사다리 스타일 이미지가 얻어지고 단면은 대략 다음과 같이 보입니다:

다음 "단계"에서 이 픽셀의 거리와 이전 "단계"에서의 거리는 해당 이미지의 SDF를 사용하여 찾을 수 있으며(SDF를 만들려면 그 중 하나를 반전해야 함), 그라데이션 값도 찾을 수 있습니다.

구현

전처리 이진화

물론 이미지가 깨끗한 가장자리를 갖도록 작업하기 전에 이미지를 이진화해야 합니다.

UDF생성

ComputeShader 코드:

[numthreads(8,8,1)]
void SaitoHorizon(uint3 id : SV_DispatchThreadID)
{
    // Result[id.xy] = float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
    float4 color = Source[id.xy];
    if (color.r == 0.0)
    {
        Result[id.xy] = float4(0.0, 0.0, 0.0, 1.0);
        return;
    }
    
    int dist = 0;
    while (dist < width)
    {
        dist++;
        if ((int)id.x + dist < width && Source[uint2(id.x + dist, id.y)].r == 0.0)
            break;
        if ((int)id.x - dist >= 0 && Source[uint2(id.x - dist, id.y)].r == 0.0)
            break;
        if ((int) id.x + dist >= width &&
            (int) id.x - dist < 0)
            break;
    }
    dist = dist * dist;
    Result[id.xy] = float4(dist, dist, dist, 1.0);
}
​
[numthreads(8,8,1)]
void SaitoVertical(uint3 id : SV_DispatchThreadID)
{
    float4 color = Source[id.xy];
    if (color.r == 0.0)
    {
        Result[id.xy] = float4(0.0, 0.0, 0.0, 1.0);
        return;
    }
    float minDist = Source[uint2(id.x, 0)].r + (float)(id.y * id.y);
    for (int i = 1; i < height; i++)
    {
        float a = Source[uint2(id.x, i)].r;
        float b = i - (int)id.y;
        minDist = min(minDist, a + b * b);
    }
    float sdf = sqrt(minDist) * pixelSize;
    Result[id.xy] = float4(sdf, sdf, sdf, 1.0);
}

호출 메서드:

 

    public void GenerateUDF(ref RenderTexture sdf, int width, int height, int pixelCountToArriveOne)
    {
        RenderTexture result = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.ARGBFloat);
        result.enableRandomWrite = true;
        result.useMipMap = false;
        result.Create();
        uint groupX, groupY, groupZ;
        SDFUtils.SDFCompute.GetKernelThreadGroupSizes(0, out groupX, out groupY, out groupZ);
        SDFUtils.SDFCompute.SetTexture(0, "Source", sdf);
        SDFUtils.SDFCompute.SetTexture(0, "Result", result);
        SDFUtils.SDFCompute.SetInt("width", width);
        SDFUtils.SDFCompute.SetInt("height", height);
        SDFUtils.SDFCompute.SetFloat("pixelSize", 1.0f / (float)pixelCountToArriveOne);
        SDFUtils.SDFCompute.Dispatch(0, width / (int)groupX, height / (int)groupY, 1);
        SDFUtils.SDFCompute.SetTexture(1, "Source", result);
        SDFUtils.SDFCompute.SetTexture(1, "Result", sdf);
        SDFUtils.SDFCompute.Dispatch(1, width / (int)groupX, height / (int)groupY, 1);
        RenderTexture.ReleaseTemporary(result);
    }

여기서 픽셀 카운트 투 어라이브 원은 이미지에서 거리 1이 몇 픽셀에 해당하는지를 나타내며, exr 형식이 아닌 이미지를 사용하는 경우 그라데이션 거리를 나타내는 데 사용할 수 있습니다.

얼굴 그림자 그리기

저는 서브스턴스 페인터를 사용하여 페인팅합니다. 모델을 로드한 후 [텍스처 세트 설정]으로 이동하여 채널은 하나만 남기고 깨끗하게 삭제합니다. 여기서는 BaseColor를 사용하거나 사용자 정의 채널 User0 또는 이와 유사한 것을 사용할 수 있습니다.

그런 다음 자료 보기에서 보기 모드를 해당 채널로 전환합니다.

 

3D 창에서 보이는 브러시 영역과 UV의 브러시 영역이 반드시 동일하지는 않으므로(예: 아래 이미지에서 그려진 획의 끝은 3D 창에서 볼 때는 폭이 같지만 UV 관점에서 볼 때는 두껍습니다) 그리기 전에 브러시의 보정, 간격 및 디더링을 신중하게 고려해야 합니다.

마지막으로 나중에 그리는 그림자가 항상 앞의 그림자 위로 바뀌도록 하려면 한 레이어씩 진행하면서 서서히 그리는 것이 가장 좋습니다:

얼굴 SDF 맵 생성

컴퓨트쉐이더 코드:

 

[numthreads(8,8,1)]
void Interpolation(uint3 id : SV_DispatchThreadID)
{
    float dist0 = Shadow0[id.xy].x;
    float dist1 = Shadow1[id.xy].x;
    float r = dist0 / (dist0 + dist1);
    Result[id.xy] = float4(r, r, r, 1);
}
​
[numthreads(8, 8, 1)]
void Sum(uint3 id : SV_DispatchThreadID)
{
    float dist = Source[id.xy].x;
    float r = dist / sumTime;
    Result[id.xy] += float4(r, r, r, 1);
}

调用方法:

// 逐个读入文件
int sumTime = shadowPathList.Count - 1;
List<RenderTexture> sdfList = new List<RenderTexture>();
for (int index = 0; index < shadowPathList.Count - 1; index++)
{
    Texture2D shadow0 = new Texture2D(2, 2);
    shadow0.LoadImage(System.IO.File.ReadAllBytes(shadowPathList[index]));
    shadow0.Apply();
    Texture2D shadow1 = new Texture2D(2, 2);
    shadow1.LoadImage(System.IO.File.ReadAllBytes(shadowPathList[index + 1]));
    shadow1.Apply();
    // 注意,其中一个阴影贴图在预处理时必须反向
    RenderTexture rt0 = sdfGenerator.PreprocessImage(shadow0, channel, 0.5f, false, sdfWidth, sdfHeight, RenderTextureFormat.ARGBFloat);
    RenderTexture rt1 = sdfGenerator.PreprocessImage(shadow1, channel, 0.5f, true, sdfWidth, sdfHeight, RenderTextureFormat.ARGBFloat);
    var sdfBetweenTwo = sdfGenerator.GenerateFaceSDFBetweenTwo(ref rt0, ref rt1, sdfWidth, sdfHeight, 0, 0);
    sdfList.Add(sdfBetweenTwo);
}
originalSdfTexture = sdfGenerator.SumFaceSDF(ref sdfList);
​
// 先在相邻两个阴影贴图里渐变
public RenderTexture GenerateFaceSDFBetweenTwo(ref RenderTexture shadow0, ref RenderTexture shadow1, int width, int height, int lower, int upper)
{
    GenerateUDF(ref shadow0, width, height, 1);
    GenerateUDF(ref shadow1, width, height, 1);
    RenderTexture result = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.ARGBFloat);
    result.enableRandomWrite = true;
    result.useMipMap = false;
    result.Create();
    uint groupX, groupY, groupZ;
    SDFUtils.SDFCompute.GetKernelThreadGroupSizes(2, out groupX, out groupY, out groupZ);
    SDFUtils.SDFCompute.SetTexture(2, "Shadow0", shadow0);
    SDFUtils.SDFCompute.SetTexture(2, "Shadow1", shadow1);
    SDFUtils.SDFCompute.SetTexture(2, "Result", result);
    SDFUtils.SDFCompute.SetInt("width", width);
    SDFUtils.SDFCompute.SetInt("height", height);
    SDFUtils.SDFCompute.SetInt("lower", lower);
    SDFUtils.SDFCompute.SetInt("upper", upper);
    SDFUtils.SDFCompute.Dispatch(2, width / (int)groupX, height / (int)groupY, 1);
    return result;
}
​
// 把渐变结果累加起来
public RenderTexture SumFaceSDF(ref List<RenderTexture> sdfList)
{
    RenderTexture result = RenderTexture.GetTemporary(sdfList[0].width, sdfList[0].height, 0, RenderTextureFormat.ARGBFloat);
    result.enableRandomWrite = true;
    result.useMipMap = false;
    result.Create();
    uint groupX, groupY, groupZ;
    SDFUtils.SDFCompute.GetKernelThreadGroupSizes(3, out groupX, out groupY, out groupZ);
    for (int i = 0; i < sdfList.Count; i++)
    {
        string path = "D:/" + i + ".exr";
        Texture2D tex = SDFUtils.GetTexture2DFromRenderTexture(sdfList[i]);
        byte[] bytes = tex.EncodeToEXR();
        System.IO.File.WriteAllBytes(path, bytes);
​
        SDFUtils.SDFCompute.SetTexture(3, "Source", sdfList[i]);
        SDFUtils.SDFCompute.SetTexture(3, "Result", result);
        SDFUtils.SDFCompute.SetInt("sumTime", sdfList.Count);
        SDFUtils.SDFCompute.Dispatch(3, sdfList[i].width / (int)groupX, sdfList[i].height / (int)groupY, 1);
    }
    return result;
}

효과 및 주소

UDF 생성

얼굴 SDF 효과

최종 결과물:

섀도우 매핑의 두 프레임마다 계산되는 그라데이션 맵의 조합으로 구성됩니다:

여기에서는 얼굴의 반쪽(즉, 광원이 90° 회전) 총 6개의 섀도우 맵을 테스트적으로 만들었을 뿐이며 실제 생산은 180°로 완료되어야 하며 수량은 이렇게 적을 수 없습니다.

프로젝트 주소

제가 글을 쓰는 대신 깃허브 코파일럿이 작성하는 주석을 쓰고, C#을 작성하는 데 정말 오랜 시간이 걸리고, 무의식적으로 디프를 작성하여 혼자서 자소 할 뻔 했습니다.

 

 

ShaderRepo/SDFGenerator at master · noobdawn/ShaderRepo

a repository of my shader. Contribute to noobdawn/ShaderRepo development by creating an account on GitHub.

github.com

 


원문

https://zhuanlan.zhihu.com/p/705226178?utm_psn=1788900461492973570&utm_id=0

 

GPU伴瘩UDF虑芙砖SDF棉珊贮日

永鸯妇知拒梳匣,橄啦敞赞,辱芬陡抓虑栅,进翠现连啄硅它,枣邓喻囤舟足阔Unity会风SDF纤敏始夸,颗企鹰趣辑志绍宠焰恩理UDF瓷控篇SDF喻特确榛撕全认广。驯杀SDF伊圆甘仇遗丘凯官旨【址tm婚

zhuanlan.zhihu.com