https://talkartist.cn/article/1936751166476042240

하느님께서는 "빛이 생겨라!"라고 말씀하셨습니다.
루쉰은 "씨발!"이라고 말한 적이 있다.
"빛"과 "잔디"가 만나면 어떻게 될지 궁금했어요. 그래서 구글 검색을 해봤는데, 거의 똑같은 정보였어요. 모든 페이지에 "잔디 최적화"라는 문구가 적혀 있었죠. 잠도 오지 않아서 밤새도록 꼼꼼히 살펴보다가 마침내 행간의 의미를 알아냈어요. 페이지 전체에 "바퀴를 재발명하라"라는 문구가 가득 적혀 있었죠! 그래서 다음 날 아침, 일찍 작업실에 도착해서 책상 위에 "잔디"를 하나 깎아냈어요.
조명 및 렌더링의 세 가지 법칙
몇 년간의 작업을 거쳐, 저는 문제 해결에 매우 유용한 렌더링의 세 가지 법칙을 요약했습니다. (그냥 스스로 만든 규칙이라는 말인거 같습니다)
1. 옳아 보인다면 옳은 것입니다.
2. 성능상의 이유로 이 효과를 얻을 수 없습니다.
3. 나중에 최적화하겠습니다.
(주: 원하는 시각적 효과는 구현했지만 이 기능은 성능상의 이유로 사용하긴 힘들다는 말인것 같습니다 )
잔디 렌더링 최적화
1. 패치+텍스처 사용은 성능은 좋으나 효과는 미비 (패치란 면Plane을 의미하는 듯 . 즉 평면에 그린 풀을 의미하는 것인것 같습니다)
2. 모델 잔디를 사용하고, 렌더링 최적화에는 GPUInstance를 사용하고, 컬링에는 ComputeShader를 사용한 다음, 그레이딩을 위해 LOD와 결합합니다.
Unity 지형의 기본 잔디는 GPUInstance 최적화를 사용하지 않으므로 사용자 지정 잔디 렌더링 관리자를 만들어야 합니다. 그렇지 않으면 잔디가 많을 때 몇 프레임만 실행됩니다.
Unity 지형에 잔디를 생성하려면 TerrainData를 사용하여 잔디의 위치 정보를 가져온 다음, DrawMeshInstancedIndirect를 사용하여 잔디 모델을 직접 그립니다. 잔디가 생성되면 렌더링을 시작할 수 있습니다.
C4D로 만든 단일 잔디 모델의 경우, 잔디는 모든 방향을 향해야 하며 생생함을 높이기 위해 곡률, 높이, 크기가 달라야 합니다.

잔디 색칠
실제 관찰에 따르면 잔디 조명은 주로 다음 조명 모델의 영향을 받습니다.
1. 난반사
2. 하이라이트
3. 전송
4. 지하 산란 (표면하산란)
사실, 조명 모델을 렌더링하는 방법을 모른다면 이 네 가지 조명 렌더링 기법을 사용해 볼 수 있습니다. 예를 들어 머리카락, 물, 나무, 구름, 눈, 얼음 등이 있습니다. 아래에서 이 네 가지 조명 렌더링 기법을 자세히 구현하는 방법을 소개합니다.
풀잎 하나하나는 고유한 조명 효과를 가지고 있으며, 여러 개의 풀잎으로 이루어진 풀더미 역시 고유한 조명 효과를 가지고 있습니다. 풀에 조명을 비출 때는 국소적인 "단일 풀잎"과 전체적인 "무더기"를 모두 고려하는 것이 중요합니다. 개별 풀잎의 경우 조명 계산에 개별 풀잎의 법선을 사용하고, 풀 무더기의 경우 지형의 법선을 사용하여 무더기의 법선을 근사합니다. 전체적인 컨셉은 하이라이트를 사용하여 풀잎 간의 개별적인 차이를 표현하고, 확산 반사와 투과를 사용하여 무더기의 전체적인 질감을 표현하는 것입니다. 영역별로 다른 조명 방향을 사용하면 입체감을 만들어 예술적 감각과 일치하는 국소적 효과와 전체적인 효과를 모두 구현할 수 있습니다.
다음은 잔디에 대한 조명 모델 조합 표입니다.

| 풀이 받는 빛 | 하이라이트 | 확산 반사 | 전염(아마도 투과) | 표면하 산란 |
| 낱개의 잔디 | 있음 | 없음 | 있음 | 없음 |
| 잔디 덤불 | 있음 | 있음 | 있음 | 있음 |
잔디에 대한 이해
조명 렌더링의 첫 번째 법칙 에 따르면 조명을 올바르게 보이게 해야 하므로 인간의 경험에서 시작하여 잔디에 대한 지각적 인식을 구체적인 목표로 전환해야 합니다.
다양한 종류의 풀은 각기 다른 생활 주기에 따라 다른 "물"을 가지고 있으므로, 조명 모델은 이 "물"을 기준으로 구별될 수 있습니다.
풀을 관찰하기 위해 다음과 같은 가정을 할 수 있습니다.
풀이 어릴수록 수분이 많고, 색깔이 밝고, 무거우며, 휘어짐이 적고, 흔들리는 것이 적으며, 조명이 풍부합니다(난반사 + 반사 + 투과 + SSS).
풀이 오래될수록 수분이 적고, 색깔은 어둡고, 밝으며, 더 많이 구부러지고, 더 많이 흔들리고, 조명이 약합니다(더 많이 확산 반사).
풀의 녹색이 블록처럼 보이기 때문에 Visual Studio와 Photoshop에서 계산된 조명 모델은 본질적으로 동일합니다. 따라서 최적화를 수행할 수 있습니다. Visual Studio에서 조명 모델을 직접 계산하는 것입니다. 풀은 정점 수가 비교적 적기 때문에 상당한 계산 리소스를 절약할 수 있습니다. 이는 예술적 관찰에 기반한 최적화 기법입니다.
구체적인 구현
확산 반사
PBR 조명 모델을 사용한 잔디는 엄청나게 칙칙해 보입니다! PBR은 범용 조명 모델로, 금속성 근사치를 렌더링하는 데는 적합하지만 "잔디"에는 무력합니다. 비아그라조차도 마찬가지입니다. 문제는 확산 반사가 램버트(Lambert)를 사용하여 어두운 영역에서 칠흑 같은 효과를 낸다는 것입니다. 디즈니 디퓨즈(Disney Diffuse)로 전환해도 효과가 개선되지 않습니다. 스타일리시한 효과를 얻으려면 일본 만화 셰이딩 기법을 빌려 확산 반사를 구현할 수 있습니다.
float NL = dot(N,L);
float v1 = NL+1;
float v2 = NL;
float3 D1 = lerp(DiffuseColorLow,DiffuseColorMid,v1);
float3 D2 = lerp(DiffuseColorMid,DiffuseColorHigh,v2);
float3 DiffuseGrass = lerp(D1,D2,NL>0);
코드 복사
가장 밝은 부분
경찰에 성매매 혐의로 체포되면 눈에 손전등을 비추는데, 본능적으로 손으로 빛을 가리게 됩니다. 인간의 눈은 강한 빛 자극을 견디지 못하기 때문입니다.

이러한 사실을 바탕으로, 이미지 렌더링 과정에서 플레이어의 얼굴에 지나치게 밝은 이미지가 여러 개 겹쳐지는 현상을 원치 않습니다. 이를 위해 기존 하이라이트 공식을 수정하여 잔디 렌더링을 더욱 "합법적"으로 만들어야 합니다.
세 개의 PBR 하이라이트 GGX DFG에서 G 항을 제거했습니다. G 항은 어두운 영역의 하이라이트를 클리핑하기 때문입니다. 그러나 잔디는 매우 얇고 빛이 잔디를 직접 통과할 수 있기 때문에 G 항은 잔디를 공처럼 취급하여 하이라이트의 어두운 영역을 직접 클리핑할 수 없습니다! 하지만 아무것도 하지 않으면 전체 화면에 하이라이트가 나타납니다. 실용적이고 미적인 측면을 고려할 때, 사람의 눈에 가까울수록 하이라이트는 약해지고, 멀어질수록 하이라이트는 강해집니다. 이를 바탕으로 카메라와의 거리에 따라 하이라이트를 약화시키는 CameraFade라는 G 항을 재구성할 수 있습니다.
float GGX_DistanceFade(float3 N,float3 V,float3 L,float Roughness,float DistanceFade)
{
float3 H = normalize(L+V);
float D = D_DistributionGGX(N,H,Roughness);
float F = F_FrenelSchlick(saturate( dot(N,V)),0.04);
float G = G_GeometrySmith(N,V,L,Roughness);
return D*F*DistanceFade;//Kill G for more natural looking
}
코드 복사단일 잔디 모델의 정점 법선을 기본 하이라이트로 사용하여 국소 하이라이트를 표현합니다. 잔디의 하이라이트는 잔디의 아랫부분보다 끝부분에서 더 뚜렷하게 나타납니다.
전반적인 효과를 표현하기 위해 잔디(지형 일반)를 보조 하이라이트로 사용합니다.
단일 잔디 하이라이트(SceneView)

잔디밭의 하이라이트

싱글그래스의 하이라이트

어두운 곳에서도 완전히 검은색이 아닌, 풀밭 사이로 밝은 빛이 비치는 것을 볼 수 있습니다.
잔디 + 단일 잔디 하이라이트(폭 거리 페이드)

반사엽
단일 레이어의 하이라이트만으로 디테일이 부족하다면 레이어를 더 추가하세요. Siggraph 전문가들은 이 방법을 스페큘러 로빙(specular lobing)이라고 부릅니다. 수분 함량이 다른 여러 레이어의 하이라이트를 겹쳐 사용할 수 있지만, 에너지 보존을 보장해야 합니다. 다음과 같은 스페큘러 로브 중첩 방법을 구성할 수 있습니다.
float3 g1 = GrassSpecular(GrassWater)
float3 g2 = GrassSpecular(GrassWater*0.5)
float3 g3 = GrassSpecular(GrassWater*0.5*0.5)
float3 grassSpecular = g1*0.5+g2*0.3+g3*0.2;//高光能量守恒
코드 복사에너지 보존
조명 계산을 많이 추가했다는 점에 유의하세요. 에너지 보존을 고려하지 않으면 계산된 조명 결과가 과장될 것입니다. 과장된 조명 결과 후에 매개변수를 사용하여 어둡게 하는 효과를 억제하면 매개변수가 너무 많아지고, 그러면 아티스트는 작성한 내용이 "너무 복잡하고" 사용하기 어렵다고 불평할 것입니다! PBR에서 확산 반사와 정반사를 혼합하는 방법은 Finnel을 사용하는 것입니다. 즉, 다음과 같습니다.
FianlColor = lerp(Diffuse,Specular,Fresnel);
코드 복사하지만 잔디를 생성할 때 GGX에서 G 항을 삭제했기 때문에 프레넬과 프레넬을 강제로 블렌딩해도 좋은 결과를 얻을 수 없습니다. 하지만 GrassWater를 사용하여 효과를 블렌딩할 수 있습니다. 이는 앞서 논의한 내용과 일맥상통합니다. 잔디에 수분이 많을수록 조명 성능이 더 풍부해지고, 그렇지 않으면 확산되는 경향이 있습니다. 아티스트는 다양한 수분 함량으로 잔디를 미리 설정하여 초원 내에서 더욱 풍부한 조명 성능을 구현할 수 있습니다.
FinalColor = lerp(Diffuse,Specular+Transmission+SSS,GrassWater);
코드 복사잔디 AO
잔디 AO 정보는 후처리 깊이와 일반을 통해 AO를 계산하는 것이 아니라 주변 잔디의 양에 따라 결정됩니다!
이를 위해 2차원 가우시안 샘플링 연산자(또는 다른 연산자)를 구성하여 AO 정보를 빠르게 계산할 수 있습니다. AO 정보가 오프라인으로 계산된 후, 잔디 데이터에 직접 기록되므로 읽을 때 컴퓨팅 리소스를 차지하지 않습니다.
간단한 예: 이 잔디의 AO는 7/9 = 0.78입니다.

SceneView에 잔디의 AO가 표시됩니다. 잔디가 얇을수록 더 어둡게 표시되는 것을 확인할 수 있습니다(1-AO 표시).

Grass AO 디스플레이 GameView

전염 (아마도 투과를 의미하는 듯)
풀잎 한 개는 가늘지만, 여러 개는 매우 두꺼울 수 있습니다. 따라서 두 경우 모두 투과율을 고려해야 합니다. AO 정보에는 해당 영역 내 풀의 "밀도"도 포함됩니다. 풀잎이 빽빽할수록 빛이 덜 통과합니다. 따라서 투과율을 계산할 때 AO 정보도 고려해야 합니다.
float SimpleTransmission(float3 N,float3 L,float3 V,float TransLerp,float TransExp,float TransIntensity,float ThicknessFade)
{
float3 fakeN = -normalize(lerp(N,L,TransLerp));
float trans = TransIntensity * pow( saturate( dot(fakeN,V)),TransExp);
return trans*ThicknessFade;
}
코드 복사잔디의 전파

단일 잔디의 전파

잔디 + 단일 잔디 변속기

투과 백라이트 효과

잔디의 지하 산란
Siggraph의 거장은 표면하 산란을 할 때 고려해야 할 세 가지 사항이 있다고 말했습니다.
-대곡률에서의 SSS:
-작은 곡률에서의 SSS
-그림자 속의 SSS
1. 큰 곡률에서의 SSS
잔디의 곡률은 지형의 곡률로 근사할 수 있습니다. NL 맵과 곡률 샘플링 LUT 맵을 사용하여 잔디의 SSS를 근사할 수 있습니다. 하지만 잔디는 피부처럼 미세할 필요는 없습니다. 잔디의 난반사를 구현할 때 이미 이 단계를 근사화했습니다. NL이 0보다 작은 부분은 투과율 색상을 표시하도록 사용자 정의할 수 있기 때문입니다.
2. 곡률이 작은 SSS
피부 렌더링에서 작은 곡률에서의 SSS는 노멀 맵을 블러링하여 직접 해결합니다. 잔디 렌더링에서는 지형 노멀을 블러링하여 잔디의 블러링된 노멀을 근사할 수도 있습니다. 이 단계는 TerrainData에서 오프라인으로 전처리하여 잔디 데이터로 저장할 수 있습니다. 이 보간을 사용하면 잔디의 SSS 정도를 시뮬레이션하는 데 직접 사용할 수 있습니다.
float3 GrassNoraml = lerp(TerrainNoraml,TerrainBlurNormal,sssIntensity);
코드 복사3. 그림자 속의 SSS
풀밭의 그림자 속 SSS는 피부 속 SSS만큼 명확하지 않기 때문에 우리는 이 처리를 무시합니다.
풀의 굽음
우리는 잔디의 곡률에 대해 다음과 같은 가정을 합니다.
1. 지형에 따라 다릅니다
풀의 곡률은 지형의 일반성에 따라 달라집니다.
2. 주변 잔디의 높이와 밀도에 따라 다름
풀이 키가 크고 빽빽한 풀에 둘러싸여 있다면, 풀은 햇빛을 덜 받게 됩니다. 햇빛을 더 많이 받기 위해 풀은 수직이 아닌 수평으로 자랍니다. 수평으로 길게 자랄수록 풀이 흔들리기 쉽습니다.
3. 오신에 따라 다릅니다
고등학교 생물 수업에 따르면, 식물 옥신은 줄기 끝에서 생성됩니다. 적당량의 옥신은 생장을 촉진하고, 과량은 생장을 억제합니다. 억제된 부분은 더 짧습니다. 주줄기에 가까울수록 흔들림이 적고, 멀어질수록 흔들림이 커집니다. 코드 구현에서는 정점의 Y 값과 좌표 중심에서 XZ까지의 거리를 기반으로 대략적인 흔들림을 계산합니다.
현재는 4kb 노이즈 맵을 사용하여 잔디 정점이 흔들리는 효과를 만들고 있습니다. 조명 렌더링의 세 번째 법칙에 따르면, 나중에 이 작업을 더 잘 구현할 수 있는 방법이 있을 것입니다.
마지막으로 몇 가지 렌더링
(공을 가운데에 놓는 것은 조명 비교 미리보기를 용이하게 하기 위함입니다)




잔디 효과는 아직 개선의 여지가 많습니다. 예를 들어, GPU 인스턴스, 절차적 잔디, 그리고 GPU 컬링을 결합하면 완벽한 잔디 솔루션으로 볼 수 있습니다. 현재는 실험적인 효과에 중점을 두고 있습니다.
'자료는 자료지 > 외부에서 퍼온자료' 카테고리의 다른 글
| ASTC_User_Guide 번역 4 (0) | 2025.12.14 |
|---|---|
| ASTC_User_Guide 번역 3 (0) | 2025.12.09 |
| ASTC_User_Guide 번역 2 (0) | 2025.12.05 |
| ASTC_User_Guide 번역 1 (0) | 2025.10.26 |
| 눈 구조 (0) | 2025.02.22 |
댓글