본문 바로가기

프로그래밍/개발일지

군집 시뮬레이션 - 기본셋팅

반응형

 군집시뮬레이션 프로젝트를 진행하기 위해 필요한 프로그램들을 정리해보고 가장 기본적인 시뮬레이션을 테스트해보도록 하겠습니다. 먼저 시뮬레이션은 유니티 엔진에서 진행됨으로, 유니티를 설치해보도록 하겠습니다. 유니티는 대부분의 기능을 무료로 사용할 수 있는 2D/3D 엔진 소프트웨어로 게임, 자동차, 영화 및 애니메이션, 건축 및 설계, VR/MR등 다양한 분야에서 사용되고 있습니다. 이번 프로젝트에서는 유니티 최신버전인 2020.2.1f1을 사용해보도록 하겠습니다.

 

 

 

 

 유니티에서는 크게 3가지의 템플릿(렌더 파이프라인)이 존재합니다. 이중에서 3D(Legacy)는 유니티에서 긴 세월동안 기본적으로 제공해주던 셋팅으로 손쉽게 개발을 진행할 수 있습니다. 특히 유니티에서 오랜 세월동안 제공해왔기 때문에 인터넷에 올라온 대부분의 자료들이 이 Legacy를 대상으로 작성되었고, 개발자들이 쉽게 사용할 수 있는 다양한 기능들도 기본적으로 제공하고 있습니다. URPLegacy보다 개발자가 더욱 많은 것을 제어할 수 있으나, 그만큼 설정해줘야 하고 신경써야 하는 것이 많은 렌더 파이프라인입니다. 확장성이 뛰어나고 Legacy보다 더욱 최적화에 신경을 써줄 수 있기도 한 템플릿입니다. HDRP는 실사같은 그래픽 효과를 표현하기 위한 렌더 파이프라인입니다. 우수한 그래픽 기능들이 내장되어 있고, 현실의 물리값(광량 등)을 참고하여 씬을 구성할 수도 있습니다.

 

 저는 이중에서 URP를 선택하기로 했습니다. 단순히 사실적인 군집 시뮬레이션을 구현하고자 한다면 망설임 없이 HDRP를 선택했겠지만, 이건 소프트웨어경진대회라 조금 더 코드에 신경써야 할거같기도 하고, 이번을 기회를 삼아 그래픽 기능들을 코드로 제어하는 연습을 해보고 싶다는 마음이 들었기 때문입니다. URP가 확장성 면에서는 가장 우수하기에, 아이디어가 떠오르는대로 이런 저런 기능을 붙여서 프로젝트를 진행해나가기 좋은듯 합니다.

 

 이제 유니티에서 군집 시뮬레이션을 실행해보도록 하겠습니다. 시뮬레이션 알고리즘은 IndieVisualLab의 UnityGraphicsProgramming을 참고하였습니다. 기존 예제에서의 큰 변경사항은 사용언어 변경(Cg -> HLSL)과 변환행렬을 직접계산, 상수버퍼 사용 입니다. 기본적인 변경사항만을 적용하고 실행한 결과는 아래와 같습니다.

 

 

 

 

수 많은 오브젝트들이 물고기떼처럼 헤엄치는듯한 모습이 마음에 들지만, 수정해야할 포인트들이 많이 보입니다.

 

1. 동물이 아닌 흰색 큐브가 날라다님

2. 물체에 그림자가 없어서 물체들이 구분되지 않음

3. 배경이나 사운드가 심심함

4. 오브젝트들이 사각형 영역 안에서 돌아다녀서 모서리부분에서의 움직임이 이상함

5. 시뮬레이션에 랜덤성이 부족함

 

여기서 일단 1~3번만 수정해보도록 하겠습니다. 수정된 버텍스/프래그먼스 셰이더 코드는 아래와 같습니다

 


            Varyings vert(Attributes IN, uint instanceID : SV_InstanceID)
            {
                Varyings OUT;

                // 인스턴스 ID로 BOid의 데이터를 가져오기
                BoidData boidData = _BoidDataBuffer[instanceID];

                float3 pos = boidData.position.xyz; // Boid의 위치를 취득
                float3 scl = _ObjectScale;          // Boid의 스케일을 취득

                // 객체의 좌표에서 월드 좌표를 변환하는 행렬을 정의
                float4x4 object2world = (float4x4)0;
                // 스케일 값 대입
                object2world._11_22_33_44 = float4(scl.xyz, 1.0);
                // 속도에서 Y축에 대한 회전을 산출
                float rotY =
                    atan2(boidData.velocity.x, boidData.velocity.z);
                // 속도에서 X축에 대한 회전을 산출
                float rotX =
                    -asin(boidData.velocity.y / (length(boidData.velocity.xyz) + 1e-8));
                // 오일러각(라디안)에서 회전 행렬을 구한다
                float4x4 rotMatrix = eulerAnglesToRotationMatrix(float3(rotX, rotY, 0));
                // 행렬에 회전을 적용
                object2world = mul(rotMatrix, object2world);
                // 행렬에 평행이동을 적용
                object2world._14_24_34 += pos.xyz;

                // 정점을 좌표 변환
                OUT.vertex = mul(object2world, IN.vertex);
                OUT.vertex = mul(UNITY_MATRIX_MVP, OUT.vertex);

                // 법선을 좌표 변환
                OUT.normal = normalize(mul(object2world, IN.normal));
                OUT.color = IN.color;

                //
                OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex);
                OUT.fogCoord = ComputeFogFactor(OUT.vertex.z);

                VertexPositionInputs vertexInput = GetVertexPositionInputs(IN.vertex.xyz);
                OUT.shadowCoord = GetShadowCoord(vertexInput);

                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(IN);

                //Lighting Calculate(Lambert)
                Light mainLight = GetMainLight(IN.shadowCoord);
                float NdotL = saturate(dot(normalize(_MainLightPosition.xyz), IN.normal));
                float3 ambient = SampleSH(IN.normal);

                float4 col = tex2D(_MainTex, IN.uv) * _Color;
                //float4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_Maintex, IN.uv);
                
                col.rgb *= NdotL * _MainLightColor.rgb * mainLight.shadowAttenuation * mainLight.distanceAttenuation + ambient;
                col.rgb = MixFog(col.rgb, IN.fogCoord);
                return col;
            }

 

 먼저 버텍스 셰이더에서는 HLSL 언어로 예제를 변환하게 되면서 instanceID를 시맨틱으로 가져오는 부분과 월드/뷰/프로젝션 변환을 직접 계산해주습니다. 이후 프래그먼트 셰이더에서는 그림자를 계산하고, 오브젝트에 텍스처를 입히고 안개효과를 추가하는 코드를 작성하였습니다. 이렇게 코드를 변경하고 바다속 느낌을 내기 위한 리소스를 적용한 결과는 아래 영상과 같습니다.

 

 

 

 

 

 물고기들이 바다속을 헤엄치는듯한 움직임이 완성되었습니다! 하지만 아직도 수정해야할 사항들이 많이 보입니다. 앞에서 말한대로 물고기들이 사각형 영역 안에서만 움직이려고 해서 사각형의 모서리 부분에서 부자연스러운 움직임을 보여주고, 시뮬레이션에 랜덤성이 부족하여 늘 같은 방식으로만 움직이는듯해서 재미가 떨어집니다. 이 외에도 바닷속인것 같은 효과를 사용하는 다양한 시각적인 기능들이 있지만, 일단 비주얼적인 개발은 여기까지 진행하고 학술적인 내용에 더 집중해보도록 하겠습니다.

 

 

 

 

 

 

-

참고

 

IndieVisualLab/UnityGraphicsProgramming

書籍「UnityGraphicsProgramming vol.1」のサンプルコードリポジトリ. Contribute to IndieVisualLab/UnityGraphicsProgramming development by creating an account on GitHub.

github.com

 

 

반응형