TECH.ART.FLOW.IO

[번역] UE5 시뮬레이션 상호작용 (3) 단일 패스 헤어 머티리얼 + 바람 필드 상호작용 체계를 달성하기 위한 레이 마칭 디스턴스 필드

jplee 2024. 5. 3. 12:29

저자

개요

 약 1년 전에 인터랙티브 윈드팜에 대한 글을 썼는데, 글 말미에 후속편에서 윈드팜 관련 애플리케이션 구현을 소개하겠다고 한 적이 있습니다. 이번에는 그 빈곳들을 부분적으로 먼저 메워 헤어 렌더링과 헤어와 윈드팜의 상호작용에 대해 소개해 보겠습니다.

 헤어 렌더링은 UE 내부에서 항상 골칫거리였습니다. 우선, 일반적으로 사용되는 헤어 렌더링 방식인 멀티 패스 오버레이 방식은 UE의 싱글 패스 렌더링 메커니즘으로 인해 엔진을 변경하지 않고는 멀티 패스 렌더링을 구현할 수 없습니다. 헤어 이펙트만 렌더링하기 위해 파이프라인을 변경하는 것은 매우 번거로운 작업이며, 아티스트에게도 그다지 우호적이지 않습니다. 따라서 엔진을 변경하지 않는다는 전제하에 멀티 레이어 메시를 복제하는 Shell-Fur와 같은 솔루션이 있지만 이 솔루션은 생성되는 메시 레이어 수를 제어하기 위해 추가 블루프린트 스크립트가 필요하며, 헤어 머티리얼을 참조하는 오브젝트 수가 너무 많으면 관리도 편리하지 않습니다.

 엔진을 변경하지 않고 멀티 레이어 메시를 추가로 복제할 필요가 없는 단일 패스 헤어 렌더링 솔루션이 있을까요? 얼마 전 셰이더토이를 둘러보다가 레이마칭을 사용하면 이 문제를 해결할 수 있을 것 같다는 생각이 들었습니다.

효과적인 디스플레이

바람장 상호작용:


1.아이디어 분석

 헤어 렌더링의 가장 큰 어려움은 헤어 필라멘트를 체적으로 렌더링하는 방법, 즉 표면을 통해 볼륨을 렌더링하는 방법인데, 이는 레이마칭을 사용하여 볼류메트릭 클라우드를 렌더링하는 것과 쉽게 연관될 수 있습니다. 실제로 레이마칭은 패럴랙스 매핑(시차 매핑), 화면 공간 반사(SSR) 등은 모두 RayMarching에서 구현된 개선 사항입니다. .

 

RayMarching实时体积云渲染入门(上)

接上上上篇 今天给大家分享一些 体绘制相关的,有遗漏和错误的地方欢迎指出,文章底部附有工程代码 目录1.RayMarching 是什么? 2.使用RayMarching 画一个BoxCloud 3.求交与遮挡处理 4.3D贴图 与 3D噪音 5

zhuanlan.zhihu.com

하지만 레이마칭 방식으로 볼류메트릭 클라우드를 렌더링하려면 클라우드의 볼류메트릭 텍스처를 미리 베이킹한 다음 스테핑 프로세스 중에 해당 볼류메트릭 텍스처를 샘플링하여 이를 달성해야 합니다. 같은 방법으로 후디니에서 머리카락의 볼류메트릭 텍스처를 생성한다면 이는 분명히 실용적이지 않을 것입니다. 우선, 각 모델마다 적절한 볼륨 텍스처를 생성해야 하므로 저장 공간을 많이 차지하고 아티스트의 작업량이 늘어날 것입니다. 둘째, 헤어 볼륨 텍스처는 미리 구워져 있기 때문에 상호작용을 위해 바람 필드에 액세스할 방법이 없습니다.

 볼류메트릭 텍스처 접근 방식은 분명히 현실적이지 않은데, 레이마칭에서 샘플링할 수 있고 각 메시에는 추가로 생성할 필요가 없는 프로퍼티가 있는 다른 방법은 무엇일까요? 그때 저는 레이마칭에서 스텝 수를 줄이기 위해 스텝 크기로 자주 사용되는 거리 필드를 생각했습니다. 레이마칭에서는 스텝 수를 줄이기 위해 샘플링된 거리 필드를 스텝 크기로 사용하는 경우가 많습니다. 하지만 여기서는 거리 필드 값의 변화를 설명하는 거리 필드 그라데이션을 사용하여 현재 위치에서 거리가 줄어드는 방향을 가리킵니다.

 디스턴스 필드 그라데이션의 방향이 모발의 성장 방향과 유사하다는 것을 알 수 있으므로, 이를 레이마칭의 방향으로 사용하여 모발 성장 패턴을 매핑할 수 있습니다.

 하지만 레이마칭으로 거리 필드를 직접 샘플링하여 얻은 것은 볼류메트릭 포그와 같은 효과와 유사한데, 이는 거리 필드 값이 선형 보간으로 얻어지며 연속적인 분포로 볼 수 있기 때문입니다. 하지만 머리카락이 뿌리를 내리고 있기 때문에 메시 본체 표면에 무작위 노이즈를 도입해야 하며, 두 번째 단계에서는 현재 표면의 노이즈 값을 샘플링합니다. 노이즈 값이 임계값보다 크면 현재 경로가 유효한 것으로 간주되어 그려지고, 그렇지 않으면 유효하지 않으므로 그려질 필요가 없습니다.


2.구현 프로세스

 엔진 버전: 5.0.3

여기에서는 구현 프로세스를 점차적으로 분해하고 반복되는 코드가 있으며 최종 효과를 2.7로 직접 이동하여 전체 코드를 보길 원합니다.

 

2.1 샘플링 거리 필드 단계

 머티리얼 M_Fur와 ShadingModel을 DefaultLit으로 생성하고 그림과 같이 연결합니다.

단계 시작과 방향에 대한 두 개의 입력 ro, rd를 각각 사용하여 Main이라는 이름의 사용자 정의 노드를 만듭니다. 결과는 자체 발광 디버그에 임시로 출력됩니다.

메인 노드는 다음과 같이 코드를 추가합니다:

//Main Code

int stepNum = 16;//步进次数
float stepLength = 1;//步长
float density = 0;
float3 pos = ro;

for(int i = 0; i < stepNum; i++){
    float3 dfg = GetDistanceFieldGradientGlobal(pos);
    float3 furDir = normalize(normalize(dfg));//当前位置点的距离场梯度方向作为毛发方向

    float3 pos1 = pos;
    for(int j = 0; j < 4; j++){
        float dtns1 = GetDistanceToNearestSurfaceGlobal(pos1);

        if(abs(dtns1) < 0.1){
            break;//第二次步进达到Mesh表面时结束
        }
        pos1 += furDir * abs(dtns1);
    }

    density += length(pos1 - pos) / 100.0;

    pos += rd * stepLength;
}

return density;

 


2.2 표면 노이즈 생성

머리카락은 단면이 원형이기 때문에 완전히 무작위적인 노이즈는 사용할 수 없고, 물체 표면에 무작위로 원형 패치를 생성하는 특수 노이즈가 필요했습니다. 그래서 공간 분할을 불연속적인 영역으로 무작위로 나누는 노이즈인 보로노이를 생각해 냈습니다. 그리고 무작위로 분할된 경계의 거리에 임계값을 설정하면 무작위로 뿌려지는 효과를 얻을 수 있습니다.

 

Shadertoy_泰勒多边形(Voronoi)

Voronoi Diagram,泰勒多边形,是由俄国数学家格奥尔吉·沃罗诺伊建立的空间分割算法。在自然中是很常见,像蜻蜓的翅膀,长颈鹿的脖子,肥皂泡,植物的细胞等等都是泰勒多边形。 泰森多边形

zhuanlan.zhihu.com

 

 

(UnityShader) Voronoi diagram

안녕하세요. 이번 포스트에서는 보로노이 다이어 그램을 만들어 볼 것입니다.보로노이 다이어그램은 자연물에서 흔히 관찰 할 수 있는 패턴입니다. 2차원 평면에 분포한 점들 사이의 거리를 기

walll4542.wixsite.com

월드 스페이스를 밟고 있으므로 3D 보로노이를 생성해야 합니다. 여기서는 샘플링을 위한 버텍스 위치와 반경 임계값 제약 조건을 직접 사용하여 다음과 같은 테스트 결과를 얻습니다.

 


2.3 采样噪声步进

왜냐하면 커스텀 함수 내부의 머티리얼에서 커스텀 함수 내부에서 호출 할 수 있기 때문에, 상대적으로 번거로운 일이므로, UE 머티리얼 컴파일 규칙의 사용의 탄생, 오른쪽 브래킷 씰과 사용자 정의 함수 접근 방식, 구체적으로 확장하지 않는, 당신은이 적용 된 내 얕은 물 방정식을 볼 수 있습니다.

 

[번역] UE5 시뮬레이션 인터랙션 챕터] (2) 얕은 물 방정식을 기반으로 인터랙티브 수면 구현하기

저자一.아이디어 분석게임에서 물과 상호작용하는 일반적인 관행부터 시작하겠습니다:1.텍스처 애니메이션일반적으로 여러 개의 리플 텍스처를 실시간으로 RT로 그린 다음 시간에 따라 리플

techartnomad.tistory.com

함수 정의로 사용할 함수라는 이름의 커스텀 노드를 생성합니다.

∙ 메인 노드에 반경 입력을 추가하여 헤어 반경을 제어합니다.

함수 노드는 다음과 같이 코드를 추가합니다:

//Function Code

    return 1;
}

float Voronoi(float3 pos){
    float3 f_st = frac(pos);
    float3 i_st = floor(pos);
    float m_dist = 1.0;  //最小距离

    for (int z= -1; z <= 1; z++) {
        for (int y= -1; y <= 1; y++) {
            for (int x= -1; x <= 1; x++) {
                    float3 neighbor = float3(float(x),float(y),float(z));//网格中的相邻位置
                    float3 diff = neighbor - f_st;
                    //添加随机噪声打乱网格均匀分布
                    diff += frac(sin(float3(dot(neighbor + i_st,float3(14.1,23.2,55.100)),dot(neighbor + i_st,float3(13.4,789.9,54.3)),dot(neighbor + i_st,float3(15.8,197.5,263.5))))*43758.5453);

                    float dist = length(diff);
                    m_dist = min(m_dist, dist);
            }
        }
    }
    return m_dist;

메인 노드 수정 코드는 다음과 같습니다:

//Main Code

int stepNum = 16;//步进次数
float stepLength = 1;//步长
float density = 0;
float3 pos = ro;

for(int i = 0; i < stepNum; i++){
    float3 dfg = GetDistanceFieldGradientGlobal(pos);
    float3 furDir = normalize(normalize(dfg));//当前位置点的距离场梯度方向作为毛发方向

    float3 pos1 = pos;
    for(int j = 0; j < 4; j++){
        float dtns1 = GetDistanceToNearestSurfaceGlobal(pos1);

        if(abs(dtns1) < 0.1){
            break;//第二次步进达到Mesh表面时结束
        }
        pos1 += furDir * abs(dtns1);
    }

    //density += length(pos1 - pos) / 100.0;

    float noise = Voronoi(pos1);
    noise = noise < radius ? 1.0 : 0.0;//当噪声值小于半径视为有效路径

    density += noise * (1.0 /stepNum);

    pos += rd * stepLength;
}

return density;

 

2.4 각 이방성의 정상(수학)

 다음 단계는 노멀을 처리하는 것입니다. 머리카락의 노멀은 이방성이므로 고전적인 Kajiya-Kay 조명 모델을 상기해야하며 실제로 머리카락의 방향으로 접선 방향을 찾은 다음 두 개를 곱하여 노멀의 방향을 구하면됩니다.

 메인 노드에 일반 입력을 추가하고 반환값을 float4 유형으로 변경합니다: 채널 저장소 밀도인 RGB 채널 저장소 일반.

  머티리얼 디테일 패널에서 탄젠트 스페이스 노멀 틱을 제거하고 월드 노멀 입력으로 변경합니다.

메인 노드 수정 코드는 다음과 같습니다:

//Main Code

int stepNum = 16;//步进次数
float stepLength = 1;//步长
float density = 0;
float3 pos = ro;
float3 baseNormal = normal;

for(int i = 0; i < stepNum; i++){
    float3 dfg = GetDistanceFieldGradientGlobal(pos);
    float3 furDir = normalize(normalize(dfg));//当前位置点的距离场梯度方向作为毛发方向

    float3 pos1 = pos;
    for(int j = 0; j < 4; j++){
        float dtns1 = GetDistanceToNearestSurfaceGlobal(pos1);

        if(abs(dtns1) < 0.1){
            break;//第二次步进达到Mesh表面时结束
        }
        pos1 += furDir * abs(dtns1);
    }

    //density += length(pos1 - pos) / 100.0;

    float noise = Voronoi(pos1);
    noise = noise < radius ? 1.0 : 0.0;//当随机值小于半径视为有效路径

    if(noise > 0){    
        float3 tangent = normalize(cross(furDir, rd));
        normal = lerp(normalize(-cross(furDir, tangent)), normal, 0.5);
    }

    density += noise * (1.0 /stepNum);

    pos += rd * stepLength;
}
normal = lerp(normal, baseNormal, 0.5);//与顶点法线做一次混合,否则金属感太强

return float4(normal, density);

2.5 가장자리 흐림

 머리카락의 가장자리가 흐려지는 것은 처리하기 좋은 부분이 아닙니다. 각 패스 레이어에서 헤어 단면의 두께를 제어하여 점진적인 전환을 달성하는 멀티 패스 방식과는 다릅니다. 레이마칭이 오브젝트 내부의 거리 필드를 샘플링하여 헤어를 구현하기 때문에 오브젝트의 바깥쪽 가장자리를 감지한 다음 불투명도 마스크를 제어하여 가장자리 블러를 구현하는 방법이 필요합니다.

 . 이것은 실제로 가장자리 감지, 즉 카투슈를 만드는 데 일반적으로 사용되는 스트로크입니다. 에지 감지를 위한 성숙한 솔루션이 많이 있지만, 저희는 파이프라인을 이동하지 않고 머티리얼에서 수행한다는 전제를 가지고 있습니다. 따라서 메시패스 스트로크를 추가하고 MaterialID를 사용하여 후처리 스트로크를 수행하는 것과 같이 이러한 방식은 불가능하므로 프레넬 스트로크만 사용할 수 있습니다.

머티리얼 블렌드 모드를 마스킹으로 수정합니다.

 하지만 프레넬 스트로크의 가장 큰 문제점은 바깥쪽 가장자리만 필요한 반면 안쪽 가장자리도 제거한다는 것입니다. 아래 그림에서와 같이 토끼 귀의 뿌리가 흐려져 있으므로 프레넬 스트로크와 함께 안쪽 가장자리와 바깥쪽 가장자리를 구분할 수 있는 방법이 필요합니다.

 실제로 내부 가장자리와 외부 가장자리의 본질적인 차이점은 가시선을 따라 내부 가장자리는 그 뒤에 자체 오클루전이 있는 반면 외부 가장자리에는 자체 오클루전이 없다는 것입니다. 따라서 메시 정점의 가시선을 따라 광선 검출을 시작점으로 하여 광선의 교차점과 시작점 사이의 거리를 판단하여 거리가 임계값보다 작으면 내부 가장자리로, 그보다 크면 외부 가장자리로 간주하는 레이마칭을 수행할 수 있습니다. 시작점은 정상 방향을 따라 더 오프셋할 수 있으며, 오프셋 거리에 따라 내부 및 외부 에지 감지 범위를 제어할 수 있습니다.

 내부 및 외부 가장자리 감지에 사용할 아웃라인이라는 사용자 지정 노드를 만듭니다.

아웃라인 노드 추가 코드는 다음과 같습니다:

//Outline Code

int stepNum = 16;
float stepLength = 1;
float3 pos = ro + normal * offset;

float3 rd = normalize(pos - cp);
for(int i = 0; i < stepNum; i++){
    float dtns = GetDistanceToNearestSurfaceGlobal(pos);

    stepLength = max(abs(dtns), 1);
    if(dtns < 0){
        break;
    }
    pos += rd * stepLength;
}
return length(pos - ro) > maxDis ? 1 : 0;

내부 및 외부 가장자리 감지 기능으로 외부 가장자리 추출

외부 가장자리는 프레넬 스트로크와 블렌딩되어 오파시티 마스크로 출력됩니다.


2.6 인터랙티브 풍력 발전소 이용

 실제로 헤어의 렌더링 부분은 거의 구현이 완료되었고, 바람장 상호작용은 그다음에 구현됩니다. 바람장 유체에 대한 이전 글을 읽지 않으셨다면 그 글을 읽어보시면 바람장 구성은 그 글에서 주로 구현되었으므로 여기서는 더 이상 설명하지 않겠습니다. 윈드 필드 기사의 마지막에는 두 개의 RT가 나오는데, 하나는 윈드 필드 유체 솔루션의 결과를 저장하고(RT_WindField), 다른 하나는 윈드 필드 데이터 정보(RT_Data)를 저장합니다. 여기서 우리는 RT_Data를 파싱하고, RT_WindField를 샘플링하여 바람 값을 얻은 다음, 머리 방향과 혼합을 수행해야 합니다.

 

[번역] UE5 시뮬레이션과 상호작용 - (1) 인터랙티브 유체 바람막이의 구현

저자시바마마 부추꽃(인터넷명)一.원칙 소개1.아이디어 분석매체의 시뮬레이션을 바라보는 두 가지 관점, 즉 라그랑주 관점과 오일러 관점부터 살펴보겠습니다. 라그랑주 관점은 일반적으로

techartnomad.tistory.com

메인 노드에 두 개의 입력이 추가됩니다: WFRT, DtRT, 그리고 바람장 솔루션의 출력으로 얻은 두 개의 RT가 각각 파라미터 입력으로 사용됩니다.

메인 노드 수정 코드는 다음과 같습니다:

//Main Code

int stepNum = 16;//步进次数
float stepLength = 1;//步长
float density = 0;
float3 pos = ro;
float3 baseNormal = normal;

for(int i = 0; i < stepNum; i++){
    float3 dfg = GetDistanceFieldGradientGlobal(pos);

    float3 windForce = SampleWindField(WFRT, WFRTSampler, DtRT, DtRTSampler, pos);
    //梯度方向与风力方向混合作为毛发方向
    float mixedness = clamp(pow(length(windForce), 0.5) / 2.0, 0, 1);
    float3 furDir = normalize(lerp(normalize(dfg), windForce, mixedness));

    float3 pos1 = pos;
    for(int j = 0; j < 4; j++){
        float dtns1 = GetDistanceToNearestSurfaceGlobal(pos1);

        if(abs(dtns1) < 0.1){
            break;//第二次步进达到Mesh表面时结束
        }
        pos1 += furDir * abs(dtns1);
    }

    //density += length(pos1 - pos) / 100.0;

    float noise = Voronoi(pos1);
    noise = noise < radius ? 1.0 : 0.0;//当随机值小于半径视为有效路径

    if(noise > 0){    
        float3 tangent = normalize(cross(furDir, rd));
        normal = lerp(normalize(-cross(furDir, tangent)), normal, 0.5);
    }

    density += noise * (1.0 /stepNum);

    pos += rd * stepLength;
}
normal = lerp(normal, baseNormal, 0.5);//与顶点法线做一次混合,否则金属感太强

return float4(normal, density);

∙ 함수 노드는 SampleWindField 함수를 추가합니다:

//Function Code

    return 1;
}

float Voronoi(float3 pos){
    float3 f_st = frac(pos);
    float3 i_st = floor(pos);
    float m_dist = 1.0;  //最小距离

    for (int z= -1; z <= 1; z++) {
        for (int y= -1; y <= 1; y++) {
            for (int x= -1; x <= 1; x++) {
                    float3 neighbor = float3(float(x),float(y),float(z));//网格中的相邻位置
                    float3 diff = neighbor - f_st;
                    //添加随机噪声打乱网格均匀分布
                    diff += frac(sin(float3(dot(neighbor + i_st,float3(14.1,23.2,55.100)),dot(neighbor + i_st,float3(13.4,789.9,54.3)),dot(neighbor + i_st,float3(15.8,197.5,263.5))))*43758.5453);

                    float dist = length(diff);
                    m_dist = min(m_dist, dist);
            }
        }
    }
    return m_dist;
}

#define IsInside(x) (x < 1.0 && x > 0.0)

float3 SampleWindField(
    Texture2D WFTex, 
    SamplerState WFTexSampler, 
    Texture2D DtTex, 
    SamplerState DtTexSampler,
    float3 pos){
    
    //解析DataRT
    float3 wfPos = Texture2DSample(DtTex, DtTexSampler, float2(1.0 / 6.0, 0.5)).xyz;
    float3 wfSize = Texture2DSample(DtTex, DtTexSampler, float2(3.0 / 6.0, 0.5)).xyz;
    float wfRes = Texture2DSample(DtTex, DtTexSampler, float2(5.0 / 6.0, 0.5)).x;

    float3 coord = (pos - wfPos) / wfSize;
    float3 windForce = float3(0, 0, 0);

    //判断是否在模拟框内
    if( IsInside(coord.x) && 
        IsInside(coord.y) && 
        IsInside(coord.z))
    {
        float2 texCoord = float2(coord.x, (floor(coord.z * wfRes) + coord.y ) / wfRes);
        windForce = Texture2DSample(WFTex, WFTexSampler, texCoord).xyz;
    }

    return windForce;
 

 

또한 바람 필드의 출력에 노이즈 맵을 겹쳐서 정지 상태에서도 머리카락에 약간의 교란을 줄 수 있습니다.

 

 


2.7 노드가 포함된 전체 코드

메인 노드:

//Main Code

int stepNum = 16;//스텝 수
float stepLength = 1;//스텝 길이
float density = 0;
float3 pos = ro;
float3 baseNormal = normal;

for(int i = 0; i < stepNum; i++){
    float3 dfg = GetDistanceFieldGradientGlobal(pos);

    float3 windForce = SampleWindField(WFRT, WFRTSampler, DtRT, DtRTSampler, pos);
    //바람 방향과 헤어 방향이 혼합된 그라데이션 방향
    float mixedness = clamp(pow(length(windForce), 0.5) / 2.0, 0, 1);
    float3 furDir = normalize(lerp(normalize(dfg), windForce, mixedness));

    float3 pos1 = pos;
    for(int j = 0; j < 4; j++){
        float dtns1 = GetDistanceToNearestSurfaceGlobal(pos1);

        if(abs(dtns1) < 0.1){
            break;//두 번째 단계가 메시 표면에 도달하면 종료됩니다.
        }
        pos1 += furDir * abs(dtns1);
    }

    //density += length(pos1 - pos) / 100.0;

    float noise = Voronoi(pos1);
    noise = noise < radius ? 1.0 : 0.0;//임의의 값이 반경보다 작으면 경로가 유효한 것으로 간주됩니다.

    if(noise > 0){    
        float3 tangent = normalize(cross(furDir, rd));
        normal = lerp(normalize(-cross(furDir, tangent)), normal, 0.5);
    }

    density += noise * (1.0 /stepNum);

    pos += rd * stepLength;
}
normal = lerp(normal, baseNormal, 0.5);//버텍스 노멀과 블렌드를 수행하지 않으면 메탈릭 룩이 너무 강해집니다.

return float4(normal, density);

함수 노드:

 

//Function Code

    return 1;
}

float Voronoi(float3 pos){
    float3 f_st = frac(pos);
    float3 i_st = floor(pos);
    float m_dist = 1.0;  //최소 거리

    for (int z= -1; z <= 1; z++) {
        for (int y= -1; y <= 1; y++) {
            for (int x= -1; x <= 1; x++) {
                    float3 neighbor = float3(float(x),float(y),float(z));//그리드에서 인접한 위치
                    float3 diff = neighbor - f_st;
                    //랜덤 노이즈를 추가하면 메시의 균일한 분포가 흐트러집니다.
                    diff += frac(sin(float3(dot(neighbor + i_st,float3(14.1,23.2,55.100)),dot(neighbor + i_st,float3(13.4,789.9,54.3)),dot(neighbor + i_st,float3(15.8,197.5,263.5))))*43758.5453);

                    float dist = length(diff);
                    m_dist = min(m_dist, dist);
            }
        }
    }
    return m_dist;
}

#define IsInside(x) (x < 1.0 && x > 0.0)

float3 SampleWindField(
    Texture2D WFTex, 
    SamplerState WFTexSampler, 
    Texture2D DtTex, 
    SamplerState DtTexSampler,
    float3 pos){
    
    //解析DataRT
    float3 wfPos = Texture2DSample(DtTex, DtTexSampler, float2(1.0 / 6.0, 0.5)).xyz;
    float3 wfSize = Texture2DSample(DtTex, DtTexSampler, float2(3.0 / 6.0, 0.5)).xyz;
    float wfRes = Texture2DSample(DtTex, DtTexSampler, float2(5.0 / 6.0, 0.5)).x;

    float3 coord = (pos - wfPos) / wfSize;
    float3 windForce = float3(0, 0, 0);

    //시뮬레이션 볼륨에 있는지 확인합니다.
    if( IsInside(coord.x) && 
        IsInside(coord.y) && 
        IsInside(coord.z))
    {
        float2 texCoord = float2(coord.x, (floor(coord.z * wfRes) + coord.y ) / wfRes);
        windForce = Texture2DSample(WFTex, WFTexSampler, texCoord).xyz;
    }

    return windForce;

개요 노드:

 

//Outline Code

int stepNum = 16;
float stepLength = 1;
float3 pos = ro + normal * offset;

float3 rd = normalize(pos - cp);
for(int i = 0; i < stepNum; i++){
    float dtns = GetDistanceToNearestSurfaceGlobal(pos);

    stepLength = max(abs(dtns), 1);
    if(dtns < 0){
        break;
    }
    pos += rd * stepLength;
}
return length(pos - ro) > maxDis ? 1 : 0;

요약

이 기술 프로그램은 헤어 렌더링 효과를 수행하기 위해 신에 대한 ShaderToy에서 영감을 얻었으며, ShaderToy는 실제로 극단적 인 장소까지 플레이하고 때로는 단서없이 효과를 수행하고 ShaderToy를 탐색하면 새로운 영감을 얻을 수 있다고 말해야합니다.

 

Shadertoy

 

www.shadertoy.com


원문

 

【UE5 模拟交互篇】(三)RayMarching距离场实现单Pass毛发材质 + 风场交互方案

前言 \circ 大约在一年前我写过一篇关于可交互风场的文章,文章最后挖了个坑说在后续会介绍风场相关应用实现。这次就把这个坑先填上一部分,来介绍一下毛发的渲染以及毛发与风场的交互。

zhuanlan.zhihu.com