TECHARTNOMAD | TECHARTFLOWIO.COM

TECH.ART.FLOW.IO

[번역] GPU 인스턴스 기반 잔디 렌더링 연구 노트

jplee 2024. 7. 4. 00:17

역자의 말.
사실 2019년도에 오픈월드 프로젝트 게임제작을 할 때 엔진부서에 간접그리기 등에 대한 도입을 계속 요청 했지만 메시 그래스 템플릿을 활용한 GPU 인스턴스로만 최적화 처리를 했던 것이 아직도 아쉽기도 하고... ( 다만 최적화는 잘 되었음.... 다행하게도 ? ) 2019년 중후반 까지만 해도 보편적으로 업계에서 간접그리기와 GPU 폐색에 대한 이해도가 그리 높은 편이 아니었던 터 이기도 하고... 특히 중국은... 한국도 마찬가지지만... 2019년도에 콘솔 게임 진영에서는 이미 이러한 처리가 점점 보편화 되어가고 있던 터라는 점이 있었는데... 아무튼.. 또 읽어볼만한 잘 정돈 된 기사가 있어서 이렇게 간략히 번역처리 하여 공유 해 봅니다.

 

저자

 

wqaetly - Overview

wqaetly has 53 repositories available. Follow their code on GitHub.

github.com

 

 

머리말

사실,이 기사의 내용은 항상 내 학습 계획에 있었지만 다양한 일등으로 인해 지연되었지만.... 최근 무료한 시간도 생기고 하여...조용히 배울 수 있었다고 할 수 있음,이 기사는 내 2021 렌더링 학습 기간, 나머지 분기는 스킬 편집기에 에너지를 투입하여 개선하고 상태 프레임 동기화를 구현하는 데 에너지를 쏟았습니다.

연구 대상은 여전히 콜린의 깃리포이며, 이번에는 GPU 간접그리기를 사용한 잔디 렌더링을 기반으로 가장 충격적이고 멋진 효과를 구현했습니다!

https://github.com/ColinLeung-NiloCat/UnityURP-MobileDrawMeshInstancedIndirectExample

 

GitHub - ColinLeung-NiloCat/UnityURP-MobileDrawMeshInstancedIndirectExample: Example project to draw 1million grass instances on

Example project to draw 1million grass instances on mobile - ColinLeung-NiloCat/UnityURP-MobileDrawMeshInstancedIndirectExample

github.com

이 문서에서 다루는 주요 기술 내용은 다음과 같습니다:

  • GPU 인스턴스의 기본 원리 
  • GPU 인스턴스 API 
  • GPU 인스턴스 기반 잔디 렌더링 구현 
  • 잔디 렌더링 콘텐츠 확장

 

GPU 인스턴스의 기본 원칙

간단히 말해, 오브젝트 메시를 전달하고 그릴 횟수와 머티리얼을 지정하면 Unity가 GPU의 유니티/컨스턴트 버퍼에서 필요한 버퍼를 열고 지정한 머티리얼로 지정한 횟수만큼 메시를 렌더링하여 단일 드로우콜에서 엄청난 수의 오브젝트를 그리는 목표를 달성할 수 있습니다.

좋은 점은

  • 기존 렌더링(배치 케이스 없음): 그리는 오브젝트 수, 데이터를 분류하고 전달해야 하는 횟수, 데이터를 분류하고 전달하는 과정이 많이 소모되는 경우, 대부분 성능 병목 현상 GPU 인스턴스: 데이터를 CPU에서 GPU로 한 번만 전달하여 렌더링 효율을 크게 향상시킵니다.

Unity의 GPU 인스턴스의 경우 데이터 처리 관점에서 보면 실제로 두 가지 범주로 나눌 수 있습니다:

  • 첫 번째는 GPU 인스턴스 지원 셰이더를 지원하는 머티리얼로 오브젝트를 렌더링할 때(예: Gameobject.Instantiate를 통해 100w 사각형을 인스턴스화하는 경우) Unity가 특수 프로세스를 수행하여 렌더링되는 모든 오브젝트에 대해 GPU의 Constant Buffer에 다양한 버퍼(버텍스 데이터 버퍼, 변환 행렬 데이터 버퍼 등)를 준비한다는 점입니다. 버텍스 데이터 버퍼, 머티리얼 데이터 버퍼, 트랜스폼 매트릭스 데이터 버퍼 등)의 모든 렌더 타깃에 대해 GPU의 상수 버퍼에 버퍼를 준비합니다.
  • 두 번째는 GPU 인스턴스 API를 호출하여 인스턴스를 직접 그리면 Unity는 전달한 파라미터에 따라 버텍스 버퍼와 머티리얼 데이터 버퍼만 준비하지만 매트릭스 데이터 버퍼나 기타 커스텀 데이터는 준비하지 않으므로 직접 ComputeBuffer를 통해 전달한 다음 인스턴스Id에 따라 처리해야 한다는 것입니다. 이러한 데이터는 ComputeBuffer를 통해 전달하고 인스턴스Id에 따라 셰이더에서 처리해야 합니다. 예를 들어 GPU 인스턴스 API를 사용하여 100w 트라이앵글을 그리면 Unity가 GPU 백엔드를 제어하여 300w 버텍스를 담을 수 있는 버퍼와 머티리얼 데이터 버퍼를 준비합니다.
역자 주: 다만 모바일 디바이스의 Compute Buffer 는 512K 남짓이었다... 최신폰은 아직 조사하지 않았는데... 최에신 폰은 4메가 까지 지원 한다고는 한다. 2019년도에 실제로 대다수의 데이터를 GPU 사이드로 밀어넣고 처리 했는데... 버퍼 오버플로 나서 무식하게 GPU 로 때려박아도 안된다는 점이 함정이다... 

컴퓨트 버퍼 크기

GPU Instance API

GPU 인스턴스 API에는 두 가지 종류가 있는데, 첫 번째는 Graphic에서 제공하는 것이고 두 번째는 CommandBuffer에서 제공하는 것으로, 둘 다 다른 인터페이스를 제공할 뿐 효율성에는 차이가 없으며, 결국 CommandBuffer API를 직접 사용하더라도 실제 GPU가 아닌 Unity에서 제공하는 상위 계층 래퍼일 뿐입니다. 

콜린의 프로젝트는 그래픽의 API를 사용하며, CommandBuffer의 GPU 인스턴스 API는 URP 렌더 파이프라인 - GPU 인스턴스 드로잉그래스 문서도 있으며, API에 대한 자세한 내용은 Graphics.DrawMeshInstancedIndirect 및 CommandBuffer.DrawMeshInstancedProcedural 공식 문서를 참조하세요.

자세한 내용은 Graphics.DrawMeshInstancedIndirect 및 CommandBuffer.DrawMeshInstancedProcedural 공식 문서를 참조하세요.

제가 이해한 DrawMeshInstanced와 DrawMeshInstancedIndirect API의 차이점은 이렇습니다:

  • 단일 DrawMeshInstanced 호출에 최대 1023개의 인스턴스 그리기, 단일 드로우콜에 최대 500개의 인스턴스 그리기(즉, 단일 드로우메시 인스턴스에 여러 드로우콜이 있을 수 있음) 등 Unity의 내부 사전 정의에 의해 제한되지만, 다음과 같이 제한이 있습니다. Unity의 내부 인프라를 어느 정도 활용할 수 있기 때문에(하나의 DrawMeshInstanced가 그리는 모든 인스턴스는 컬 그룹으로 처리되지만 투명도 및 뎁스 테스트 성능 향상을 위해 개별 인스턴스 컬링 및 정렬은 지원되지 않음) 이 '어느 정도 Unity 인프라'는 이미 쓸모가 없어졌습니다. 따라서 이 "어느 정도의 Unity 인프라"는 거의 쓸모가 없으니 신경 쓰지 마세요!
  • DrawMeshInstancedIndirect는 자유도가 높고 성능 상한이 DrawMeshInstanced만큼 제한적이지 않지만, Unity의 일부 내부 인프라(닭과 달걀도 마찬가지)에 대한 액세스를 포기해야 하고 LOD와 크롭을 직접 처리해야 한다는 단점도 있습니다.

따라서 일반적으로 개발에는 DrawMeshInstancedIndirect를 선택합니다.

 

GPU 인스턴스 기반 잔디 렌더링 구현

렌더링 프로세스

셰이더 기반 자르기 계산

컴퓨트 셰이더 관련 콘텐츠는 스크린스페이스플래너리플렉션(스크린스페이스플래너리플렉션) 학습 노트의 URP로 이동할 수 있으며, 여기에는 회로도만 넣습니다:

 

 

GPU 인스턴스 기반 원리 - 하나의 오브젝트의 버텍스 데이터만 GPU 측에 전달한 다음 인스턴스 ID와 다른 트랜스폼 행렬을 사용하여 vs와 ps에서 여러 오브젝트를 그리지만, 드로우콜 호출은 하나만 있지만 렌더링 백엔드의 내부 처리인 는 이 하나의 오브젝트의 버텍스 데이터를 여러 번 (그리고자 하는 인스턴스 수만큼) vs를 사용하여 처리하고, 뷰포인트에 전혀 보이지 않는 인스턴스의 경우 이에 대한 vs 처리가 불필요하지만, GPU 인스턴스 컨트롤을 사용하여 직접 그린 인스턴스는 적용 단계에서 크롭 보너스(일반적으로 엔진에서 제공하는 거친 단위의 오브젝트별 자르기인 자르기)의 이점을 누릴 수 없으며, 이는 GPU 인스턴스가 수행해야 하는 자르기 작업으로 이어집니다.

플러시 컷 공간의 최종 컷이 불필요한 슬라이스를 잘라내긴 하지만, 뷰콘 컷을 미리 수동으로 수행하면 버텍스 연산을 많이 줄일 수 있으며, 인스턴스 수가 매우 많은 경우 성능 개선이 즉각적으로 나타납니다.

  1. 첫 번째는 순전히 CPU 측 자르기인데, 각 잔디를 청크 처리한 다음 카메라의 뷰 콘을 사용하여 청크에 대해 AABB 테스트를 수행하면 뷰 콘 내의 청크만 렌더링할 잔디 청크 목록에 추가됩니다.
  2. 두 번째는 컴퓨트 셰이더를 기반으로 하는 순수 GPU 크롭으로, 전달된 하나 이상의 잔디 영역에 있는 각 잔디를 VP 매트릭스 변환을 통해 제곱 자르기 공간으로 수동으로 자르고 테스트를 통과한 것만 최종 렌더링할 목록에 추가하는 방식입니다.

따라서 청크 크기 설정은 CPU Heavy인지 GPU Heavy인지에 직접적인 영향을 미칩니다. 각 청크의 크기가 클수록 CPU 자르기의 세분성이 커지고, CPU 압박이 작아지고, GPU 압박이 커지며, 그 반대의 경우도 마찬가지입니다!

타깃 자르기 렌더링과 관련된 구현 기법은 [게임 씬 자르기] 자르기 알고리즘 개요를 참조하세요.

 

잔디 빌보드

카메라를 월드 스페이스에서 모델 스페이스로 변환하여 오브젝트의 정점을 항상 카메라를 향하고 있는 것처럼 보이도록 회전 행렬을 구성하는 고전적인 빌보드 알고리즘을 알고 있습니다.

셰이더 에센셜 이미지

 

하지만 여기서는 알고리즘이 조금 다른 것 같나요?

사실 여전히 고전적인 빌보드 알고리즘인데, 이렇게 단순화 할 수있는 이유는이 잔디 렌더링에서 각 잔디의 앵커 포인트의 세계 좌표를 알고 있기 때문에 오프셋의 형태를 통해 각 정점의 세계 좌표로 직접 변환 할 수 있기 때문입니다. 즉, 각 잔디의 모델 공간 정점을 세계 공간 정점으로 취급하므로 관측 행렬의 회전 정보에 직접 적용하여 빌보드 효과를 얻고 마지막으로 앵커 포인트 세계 좌표를 추가 (또는 변위 만 포함하는 세계 변환 행렬을 곱)하여이 버텍스의 실제 세계 좌표를 얻을 수 있기 때문입니다. 그런 다음 각 버텍스의 월드 좌표를 앵커 포인트의 월드 좌표에 더하거나 변위 전용 월드 변환 행렬을 곱하여 버텍스의 실제 월드 좌표를 얻습니다.

//rotation(make grass LookAt() camera just like a billboard)
//=========================================
float3 cameraTransformRightWS = UNITY_MATRIX_V[0].xyz;
//UNITY_MATRIX_V[0].xyz == world space camera Right unit vector
float3 cameraTransformUpWS = UNITY_MATRIX_V[1].xyz;
//UNITY_MATRIX_V[1].xyz == world space camera Up unit vector
float3 cameraTransformForwardWS = -UNITY_MATRIX_V[2].xyz;
//UNITY_MATRIX_V[2].xyz == -1 * world space camera Forward unit vector

//Expand Billboard (billboard Left+right)
float3 positionOS = IN.positionOS.x * cameraTransformRightWS;
//random width from posXZ, min 0.1
//Expand Billboard (billboard Up)
positionOS += IN.positionOS.y * cameraTransformUpWS;

float3 positionWS = positionOS + perGrassPivotPosWS;
OUT.positionCS = TransformWorldToHClip(positionWS);

 

초원 상호작용

핵심은 GrassBendingRTPrePass로, 트레일 렌더 컴포넌트를 사용하여 잔디 상호작용을 위한 정보 소스로서 자동 페이드 런 궤적을 단일 RT에 렌더링하는 것입니다.

이 렌더링 타이밍은...

m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingPrePasses;

렌더링 결과는 FrameDebug를 통해 확인할 수 있습니다.

 

 

 

그런 다음 셰이더에서 이 RT에 따라 잔디와 상호작용할 수 있습니다. 여기서 해결책은 영향을 받는 잔디 끝 위치를 직접 아래로 이동하여 잔디가 눌리는 효과를 시뮬레이션하는 것인데, 위의 RT 샘플링에 사용된 UV 좌표, 즉 각 잔디의 앵커 포인트의 월드 공간 위치에서 전체 잔디의 중심을 뺀 다음 전체 잔디의 크기로 나누어 구성해야 한다는 점에 유의해야 합니다.

//get "is grass stepped" data(bending) from RT
float2 grassBendingUV = ((perGrassPivotPosWS.xz - _PivotPosWS.xz) / _BoundSize) * 0.5 + 0.5;
//claculate where is this grass inside bound (can optimize to 2 MAD)
float stepped = tex2Dlod(_GrassBendingRT, float4(grassBendingUV, 0, 0)).x;

//bending by RT (hard code)
float3 bendDir = cameraTransformForwardWS;
bendDir.xz *= 0.5; //make grass shorter when bending, looks better
bendDir.y = min(-0.5, bendDir.y);
//prevent grass become too long if camera forward is / near parallel to ground
positionOS = lerp(positionOS.xyz + bendDir * positionOS.y / -bendDir.y, positionOS.xyz,
                  stepped * 0.95 + 0.05); //don't fully bend, will produce ZFighting

정점 위치가 아래로 이동하는 것이 불가피하게 약간 뻣뻣한 경우, 잔디가 넘어지는 바람의 시뮬레이션과 결합하면 매우 자연스러워 보입니다.

 

 

풍파 시뮬레이션

세 개의 풍파가 주기적으로 작용하여 잔디의 끝 부분에만 영향을 주고, 마지막으로 버텍스 위치에 풍파 효과의 오프셋을 겹쳐줍니다.

//wind animation (biilboard Left Right direction only sin wave)            
float wind = 0;
wind += (sin(
    _Time.y * _WindAFrequency + perGrassPivotPosWS.x * _WindATiling.x + perGrassPivotPosWS.z *
    _WindATiling.y) * _WindAWrap.x + _WindAWrap.y) * _WindAIntensity; //windA
wind += (sin(
    _Time.y * _WindBFrequency + perGrassPivotPosWS.x * _WindBTiling.x + perGrassPivotPosWS.z *
    _WindBTiling.y) * _WindBWrap.x + _WindBWrap.y) * _WindBIntensity; //windB
wind += (sin(
    _Time.y * _WindCFrequency + perGrassPivotPosWS.x * _WindCTiling.x + perGrassPivotPosWS.z *
    _WindCTiling.y) * _WindCWrap.x + _WindCWrap.y) * _WindCIntensity; //windC
wind *= IN.positionOS.y; //wind only affect top region, don't affect root region
float3 windOffset = cameraTransformRightWS * wind; //swing using billboard left right direction
positionWS.xyz += windOffset;

 

조명 및 그림자 지원

URP의 빛 처리 방식은 빌트인 파이프라인과 매우 다릅니다.

  • 기본 제공: 각 광원은 모든 패스를 한 번씩 통과하며, 렌더링할 씬에 m개의 패스와 n개의 광원이 있는 경우 m*n개의 드로우콜이 발생하며, 이러한 유형의 드로우콜 프로세스에서는 렌더링 데이터와 렌더링 상태 변경이 성능에 치명적인 타격을 줄 수 있습니다!
  • URP:각 패스에서 모든 광원을 반복하여 모든 광원을 한 번에 렌더링합니다.

당연히 많은 드로우콜을 절약하고 성능이 향상됩니다.

먼저 메모에 명시된 이유에 대한 몇 가지 키워드를 추가하세요.

  • 셰이더의 키워드는 URP C# 측 소스 코드에서 판단되며, 정의가 합법적이지 않은 경우 잘립니다. 자세한 내용은 ShaderPreprocessor.cs의 StripUnused 함수를 참조하십시오.
  • Core.hlsl 및 Lighting.hlsl에 정의된 필드와 함수는 이러한 키워드를 추가하지 않으면 제대로 호출할 수 없습니다(내부적으로 키워드 정의 여부를 판단하기 때문).
// -------------------------------------
// Universal Render Pipeline keywords
// When doing custom shaders you most often want to copy and paste these #pragmas
// These multi_compile variants are stripped from the build depending on:
// 1) Settings in the URP Asset assigned in the GraphicsSettings at build time
// e.g If you disabled AdditionalLights in the asset then all _ADDITIONA_LIGHTS variants
// will be stripped from build
// 2) Invalid combinations are stripped. e.g variants with _MAIN_LIGHT_SHADOWS_CASCADE
// but not _MAIN_LIGHT_SHADOWS are invalid and therefore stripped.
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile _ _SHADOWS_SOFT
// -------------------------------------

조명 함수의 경우 Lighting.hlsl, 그림자 함수의 경우 Shadows.hlsl을 참조하면 모든 키워드 정의를 얻을 수 있습니다.

그런 다음 평행 조명과 추가 광원의 조명과 그림자를 계산하고 조명 모델의 확산 반사 방식으로 고전적인 세미 램버트를 선택하고 조명 모델의 하이라이트 방식으로 버텍스별 하이라이트 반사를 선택할 수 있습니다(조명에 대한 자세한 내용은 아래 메모에 나와 있습니다).

 

GLSL

half3 ApplySingleDirectLight(Light light, half3 N, half3 V, half3 albedo, half positionOSY)
{
    half3 H = normalize(light.direction + V);
    //direct diffuse 
    half directDiffuse = dot(N, light.direction) * 0.5 + 0.5; //half lambert, to fake grass SSS
    //direct specular
    float directSpecular = saturate(dot(N,H));
    //pow(directSpecular,8)
    directSpecular *= directSpecular;
    directSpecular *= directSpecular;
    directSpecular *= directSpecular;
    //directSpecular *= directSpecular; //enable this line = change to pow(directSpecular,16)
    //add direct directSpecular to result
    directSpecular *= 0.1 * positionOSY;//only apply directSpecular to grass's top area, to simulate grass AO
    half3 lighting = light.color * (light.shadowAttenuation * light.distanceAttenuation);
    half3 result = (albedo * directDiffuse + directSpecular) * lighting;
    return result; 
}

-----------------------------------PS------------------------------------------
    
    Light mainLight;
#if _MAIN_LIGHT_SHADOWS
    mainLight = GetMainLight(TransformWorldToShadowCoord(pos
#else
    mainLight = GetMainLight();
#endif                               
    half3 randomAddToN = (_RandomNormal * sin(perGrassPivotPosWS.x * 82.32523 + perGrassPivotPosWS.z) + wind
        * -0.25) * cameraTransformRightWS; //random normal per grass 
    //default grass's normal is pointing 100% upward in world space, it is an important but simple grass normal trick
    //-apply random to normal else lighting is too uniform
    //-apply cameraTransformForwardWS to normal because grass is billboard
    half3 N = normalize(half3(0, 1, 0) + randomAddToN - cameraTransformForwardWS * 0.5);
    half3 V = viewWS / ViewWSLength;
    half3 baseColor = tex2Dlod(_BaseColorTexture,
                               float4(TRANSFORM_TEX(positionWS.xz, _BaseColorTexture), 0, 0)) * _BaseColor;
    //sample mip 0 only
    half3 albedo = lerp(_GroundColor, baseColor, IN.positionOS.y);
    //(计算球谐光照,因为场景中的光照探针)
    half3 lightingResult = SampleSH(0) * albedo;
    //main direct light
    lightingResult += ApplySingleDirectLight(mainLight, N, V, albedo, positionOS.y);
    mainLight = GetMainLight(TransformWorldToShadowCoord(positionWS));
#if _ADDITIONAL_LIGHTS
    // Returns the amount of lights affecting the object being renderer.
    // These lights are culled per-object in the forward renderer
    int additionalLightsCount = GetAdditionalLightsCount();
    for (int i = 0; i < additionalLightsCount; ++i)
    {
        // Similar to GetMainLight, but it takes a for-loop index. This figures out the
        // per-object light index and samples the light buffer accordingly to initialized the
        // Light struct. If _ADDITIONAL_LIGHT_SHADOWS is defined it will also compute shadows.
        Light light = GetAdditionalLight(i, positionWS);
        // Same functions used to shade the main light.
        lightingResult += ApplySingleDirectLight(light, N, V, albedo, positionOS.y);
    }
#endif

여기에 작은 트릭이 있는데, 하이라이트 부분은 모든 정점이 아닌 잔디의 끝 부분에만 영향을 미치며 모든 정점이 하이라이트를 계산하면 과다 노출되기 때문에 (흰색 잔디 영역의 오른쪽 그림은 시야 방향과 빛의 방향으로 인해 기본적으로 점으로 이어지는 법선의 방향과 동일한 방향의 정규화 평균에 지나치게 큰, 즉 하이라이트 계수가 너무 높아서 과다 노출 될 것입니다) 잔디의 팁 부분에만 영향을 줄 수 있으며, 또한 사용할 수 있습니다. VS에서 PS 선형 보간을 사용하여 더 나은 조명 효과를 얻을 수 있습니다(자동 그라데이션).

 

왼쪽은 잔디 끝만 강조하는 효과이고, 오른쪽은 모든 정점을 강조하는 효과입니다.

 

확장

이러한 잔디 시스템은 실제로 매우 완벽하며 산업 생산의 응용 프로그램 만 알고리즘을 개선하고 다른 인프라를 제공해야하며 원래 프로젝트의 개선 방향 (쿼드 트리 검색 최적화)의 알고리즘 부분은 여기에 표를 작성하지 않고 메모에서 수행되었습니다!

이 부분은 주로 인프라에 대한 부분입니다.

 

초원 편집기

산업 프로덕션에서는 이 프로젝트의 라인을 따라 Unity의 브러시 잔디, 브러시 트리 기능의 자체 지형 시스템과 유사한 잔디 에디터가 필요하게 될 것이며, 문제의 핵심은 두 가지입니다:

  1. 브러시를 사용하여 세계 좌표를 브러시하는 방법과 기복이있는 지형에 대처해야하는 방법
  2. 식물 종류를 다양화하는 방법(예: 잔디와 꽃을 모두 지원하는 방법)(아래에서 세부 개체라고 통칭)

첫 번째로, 데이터 편집 및 익스포트 툴로 Unity의 자체 지형 시스템(Terrain)을 직접 사용할 수 있으며, 여기서는 Terrain의 작동 방식을 간략하게 설명합니다:

  • 높이 맵은 지형의 구배를 기록하는 데 사용됩니다.
  • 표면 믹싱 정보 기록을 위한 컨트롤 맵
  • 그레이스케일 맵은 풀, 꽃, 바위 등과 같은 세부적인 물체의 수를 단일 픽셀에 기록하는 데 사용됩니다.

터레인 에디터에서 잔디를 페인팅한 후 하이트맵과 TerrainData.GetDetailLayer API를 샘플링하여 잔디의 월드 좌표를 구한 다음 렌더링하기만 하면 됩니다.

또한 자체 지형 편집기를 참조하여 자체 특수 브러시 편집기를 작성하고 더 제어 할 수 있으며 브러시 편집기의 핵심은 좌표를 계산하는 광선 감지 + 브러시 영향의 UV를 계산하는 브러시 매핑 샘플링이며 다음 두 링크를 참조 할 수 있습니다.

  • Unity 터레인 에디터 일부 소스 코드
  • Grass Geometry Shader with Interactivity

두 번째 요점은 패딩의 첫 번째 요점과 함께 그 해결책도 호출됩니다. Unity의 TerrainData는 세부 개체 유형을 가져 오는 API를 제공하지 않으므로 자체 브러시 잔디 편집기를 작성해야하므로 데이터는 두 개의 회색조 맵에 저장 될 수 있으며 첫 번째는 잔디의 높이 (잔디의 UV 좌표의 텍스처는 잔디 X-Z 좌표)를 기록하는 데 사용되며 두 번째는 잔디를 기록하는 데 사용됩니다. 디테일 오브젝트의 유형(꽃인지 풀인지)

 

더욱 풍부한 잔디 상호작용

이제 잔디 상호 작용의 프로젝트는 캡슐 바디가 잔디가 눌려지는 곳을 걷고, 실제로 비슷한 캐릭터가 검을 휘두르고 충격파로 인한 강력한 기술을 방출하여 잔디가 눌려지는 효과를 가져 오는이 RT를 조작하는 방법으로 얻을 수 있지만 일부 게임 유형은 잔디를 깎고 태우는 것과 같이 철저하지 않은 요구 사항과 상호 작용할 수 있습니다!

일반 게임 오브젝트 잔디인 경우 충돌 감지를 사용하여 잔디의 상태 데이터를 설정할 수 있지만 GPU 인스턴스를 사용하여 잔디를 그리면 특정 잔디 오브젝트를 얻을 수 없으므로 다른 해결 방법을 찾아야 합니다.

 

잔디 깎기

뿌리를 남기고 잘라낸 경우 텍스처 정보의 해당 부분의 그레이 스케일 맵을 직접 지울 수 있지만 일반적으로 잔디의 뿌리를 남기고 더 자연스러운 성능을 추구하기 위해 여기서는 실제로 아이디어와 위의 잔디 상호 작용 RT는 거의 동일하지만 차이점은 자동으로 복원되지 않는다는 것, 즉 RT의 영향이 영구적이므로 잔디 정보를 깎은 기록을 위해 새로운 RT를 열어야한다는 것입니다.

 

잔디 태우기

불타는 풀은 조금 더 복잡하지만 핵심은 여전히 불타는 풀의 위치를 표현하는 RT이며, 이 RT는 셰이더에서 샘플링되고 영향을 받는 경우 불타는 효과로 표현해야 합니다.

구체적인 알고리즘은 많은 게임의 "아이템 검색" 월드 스캔라인 이펙트와 유사합니다(Unity3D 셰이더: 데스 스트랜딩 스캐닝 이펙트 참조).

 

참조

  • LearnOpenGL GPU Instance
  • [게임 씬 컬링] 컬링 알고리즘 개요
  • 스태틱 배칭 / 동적 배칭 / GPU 인스턴싱 / SRP 배처의 상세 해부학
  • URP 렌더 파이프라인 - GPU인스턴스 드로잉 그래스
  • Gurbu GPU 인스턴스 위키
  • GPU Gems 2: Inside Geometry Instancing
  • URP 스터디 노트의 ScreenSpacePlanarReflection
  • Graphics.DrawMeshInstancedIndirect 및 CommandBuffer.DrawMeshInstancedProcedural 官方文档
  • ball-harmonic illumination
  • Unity Terrains editor partial source code
  • Grass Geometry Shader with Interactivity
  • Unity3D Shader: Death Stranding Scan Effect

기사 작성자: 烟雨迷离半世殇

저작권: 이 블로그의 모든 게시물은 별도의 언급이 없는 한 CC BY-NC-SA 4.0 라이선스에 따라 라이센스가 부여됩니다. 다른 방법으로 허가를 받아 재인쇄되었습니다!


원문

https://www.lfzxb.top/massive-grass-rendering-based-on-gpu-instance/

 

基于GPU Instance的草地渲染学习笔记

前言 其实这篇文章的内容一直是在我的学习计划中的,但是由于种种事情而耽误了,最近正好有空,可以静下心来好好学习下,这篇文章应该是我2021年渲染学习的一个句号,剩下的一个季度要

www.lfzxb.top