본문으로 바로가기

Cluster simulation by Compute Shader

category Technical Report/Unity Shader 2024. 12. 31. 20:54
반응형

원문 링크

https://zhuanlan.zhihu.com/p/12615431706?utm_psn=1857450462946668545&fbclid=IwZXh0bgNhZW0CMTAAAR0QBoPWgo-Oyqy1Plmg77ueJfda-5oLX6CktLZFTXh_SJvB7FjuN7RKPEY_aem_cl8ztuz1Ak-7tR5Xx4Unmg

Flocking은 자연에서 새나 물고기 떼와 같은 동물의 집단적 움직임 행동을 시뮬레이션하는 알고리즘입니다. 핵심은 Sig 87의 Craig Reynolds가 제안한 세 가지 기본 동작 규칙을 기반으로 하며 종종 "Boids[각주:1]" 알고리즘이라고도 합니다.

  • 분리 입자는 서로 너무 가깝지 않아야 하며 경계감이 있어야 합니다. 구체적으로는 주위의 특정 반경을 갖는 입자를 계산한 다음 충돌을 피하기 위한 방향을 계산합니다.
  • 정렬: 개인의 속도는 그룹의 평균 속도를 따르는 경향이 있으며 소속감이 있어야 합니다. 구체적으로는 가시 범위 내 입자의 평균 속도(속도 방향)를 계산합니다. 이 가시 범위는 새의 실제 생물학적 특성을 기반으로 결정되며 이에 대해서는 다음 섹션에서 설명합니다.
  • 응집력: 개인의 위치는 평균적인 위치(그룹의 중심) 경향이 있으며, 안정감이 있어야 합니다. 구체적으로, 각 입자는 주변 이웃의 기하학적 중심을 찾고 이동 벡터를 계산합니다(최종 결과는 평균 위치입니다).

Picture taken from remo boss

  • 分离 (Separation): "분리"를 의미합니다. 개체들 간의 충돌을 피하기 위해 거리를 유지하는 행동을 나타냅니다.
  • 对齐 (Alignment): "정렬"을 의미합니다. 개체들이 서로의 방향을 맞추어 움직이는 행동을 나타냅니다.
  • 聚合 (Cohesion): "집합" 또는 "응집"을 의미합니다. 개체들이 서로에게 가까워지려고 하는 행동을 나타냅니다.

이 세 가지는 군집 행동(Flocking behavior)에서 중요한 개념으로, Separation(분리), Alignment(정렬), Cohesion(응집)의 세 가지 규칙을 기반으로 군집을 형성하는 메커니즘을 설명합니다.

이전 축적 이후 우리는 이제 컴퓨팅 셰이더에 익숙해져야 합니다. 우리의 목적은 매우 반복적인 계산을 GPU에 넘겨주는 것입니다. 이제 위의 이론적 분석이 완료되었습니다. 컴퓨팅 셰이더에서 각 보이드의 계산 프로세스를 작성한 다음 계산에 필요한 매개변수를 스크립트에서 cs에 전달하기만 하면 됩니다.

 

 

Compute Shader

 

따라서 먼저 cs에 대한 코드를 완성해야 합니다. cs에서는 perlin noise[각주:2]를 사용합니다.

자연의 소음 현상을 시뮬레이션하기 위해 두 가지 기능이 코드에 작성됩니다.


#pragma kernel CSMain
#define GROUP_SIZE 256

float hash( float n )
{
        return frac(sin(n)*43758.5453);
}

// The noise function returns a value in the range -1.0f -> 1.0f 
float noise1( float3 x )
{
        float3 p = floor(x);
        float3 f = frac(x);

        f = f * f * (3.0-2.0 * f);
        float n = p.x + p.y * 57.0 + 113.0 * p.z;

        return lerp(lerp(lerp( hash(n+0.0), hash(n+1.0),f.x),
                   lerp( hash(n+57.0), hash(n+58.0),f.x),f.y),
                   lerp(lerp( hash(n+113.0), hash(n+114.0),f.x),
                   lerp( hash(n+170.0), hash(n+171.0),f.x),f.y),f.z);
}

획득한 노이즈를 사용하여 보이드의 속도를 교란할 것입니다. 최종 효과에서 교란 후 일부 보이드는 매우 느리고(게으른 것으로 반영됨) 일부 보이드는 더 빨라집니다(생생하게 반영됨). 효과를 보다 쉽게 ​​제어할 수 있도록 효과의 안정성을 제어하기 위해 노이즈를 곱하는 데 사용되는 boidSpeedVariation이라는 변수도 추가합니다.

다음으로 boid 구조를 정의하고 사용할 캐시, 변수 등을 준비해야 합니다.


struct Boid
{
float3 position;
float3 direction;
float noise_offset;
};

RWStructuredBuffer<Boid> boidsBuffer;

float time;
float deltaTime;
float rotationSpeed;
float boidSpeed;
float boidSpeedVariation;
float3 flockPosition;
float neighbourDistance;
int boidsCount;
float cohesionWeight;
float3 cursor;
float cursorWeight;

사용할 변수는 모두 여기에 나와 있습니다. 사실 필요에 따라 변수를 추가하거나 줄일 수 있습니다. 커널 함수

[각주:3]에서 각 변수의 역할을 이해하지 못해도 상관없습니다. 각 boid를 계산할 때 사용하면 이해할 수 있습니다. 다음으로 커널 함수의 코드가 제공됩니다. 커널 함수에서는 방금 배치한 클러스터 알고리즘[각주:4]을 구현할 것으로 예상됩니다.


[numthreads(GROUP_SIZE, 1, 1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
        uint instanceId = id.x;
        Boid boid = boidsBuffer[instanceId];

        float noise = clamp(noise1(time / 100.0 + boid.noise_offset), -1, 1) * 2.0 - 1.0;
        float velocity = boidSpeed * (1.0 + noise * boidSpeedVariation);

        float3 boid_pos = boid.position;
        float3 boid_dir = boid.direction;

        float3 separation = 0;
        float3 alignment = 0;
        float3 cohesion = flockPosition;

        uint nearbyCount = 1; // Add self that is ignored in loop

        for (uint i = 0; i < (uint)boidsCount; i++) {
                if (i == instanceId)
                        continue;

                if (distance(boid_pos, boidsBuffer[i].position) < neighbourDistance)
                {
                        float3 tempBoid_position = boidsBuffer[i].position;

                        float3 offset = boid.position - tempBoid_position;
                        float dist = length(offset);
                        dist = max(dist, 0.000001); //Avoid division by zero
                        separation += offset * (1.0/dist - 1.0/neighbourDistance);

                        alignment += boidsBuffer[i].direction;
                        cohesion += tempBoid_position;

                        nearbyCount += 1;
                }
        }

        float avg = 1.0 / nearbyCount;
        alignment *= avg;
        cohesion *= avg;
        cohesion = normalize(cohesion - boid_pos);

        float3 direction = alignment + separation + cohesion * cohesionWeight + cursor * cursorWeight;

        float ip = exp(-rotationSpeed * deltaTime);
        boid.direction = lerp((direction), normalize(boid_dir), ip);

        boid.position += (boid.direction) * (velocity * deltaTime);

        boidsBuffer[id.x] = boid;
}

위에 제공된 코드에는 주목할 만한 몇 가지 사항이 있습니다. 여기서는 하나씩 설명하겠습니다.

    • 첫 번째는 noise 입니다. 이전에 자세한 설명을 드렸으니 다시 보시면 이해하실 수 있습니다.
    • 그런 다음 클러스터링 알고리즘을 구현하면 separation(분리), alighnment(정렬) 및 cohension(응집)이라는 세 가지 벡터를 사용하는 것을 볼 수 있습니다.
      • 첫 번째 분리는 출발하는 보이드가 가까울수록 현재 보이드가 더 멀리 떨어져 있기를 원하는 것으로 이해될 수 있습니다. 따라서 분리의 최종 형태는 반대 방향을 가리키는 크기의 벡터여야 합니다. 크기를 통해 멀리 떨어져 있으려는 의지), 그렇죠? 결국 어디를 가리켜야 하는지와 크기는 무엇인지에 관해서는 현재 보이드 주변의 모든 보이드에 따라 달라져야 하며 최종적으로는 이를 평균화해야 합니다. 오프셋을 계산할 때 다음과 같은 경우에 주의하세요. 우리는 방향 문제에 집중합니다. 분리는 반대 방향을 가리켜야 합니다.
      • 두 번째 정렬은 현재 보이드가 주변 보이드의 방향과 최대한 정렬되어야 한다는 사실을 나타냅니다. 그런 다음 최종 형태는 분명히 모든 주변 보이드의 평균 방향을 가리켜야 합니다. 크기는 필요하지 않고 방향만 필요합니다.
      • 세 번째 응집력은 집계 위치를 나타냅니다. 처음 두 벡터를 이해하면 이제 for 루프의 코드를 다시 살펴보면 응집력을 대략적으로 이해할 수 있습니다. cohesion - boid_pos); 이 코드는 분명히 for 루프가 끝난 후 우리가 얻는 응집력은 현재 boid 주변의 평균 위치를 나타냅니다. 따라서 응집력은 방향이 아닌 현재 위치를 나타냅니다. 우리는 이해하기 쉬운 응집력(cohesion) - boid_pos를 사용합니다. 이것은 현재 boid에서 평균 위치를 가리키는 벡터입니다. 그런데, 문제는 왜 정규화하느냐 하는 것입니다. Cohesion도 필요하므로 Normalize를 사용합니다. 또한 Cohesion의 초기값은 처음에 물고기 떼가 나타나기를 원하는 위치인 FlockPosition을 0으로 쓰지 않습니다.
    • boid.direction = lerp((direction), Normalize(boid_dir), ip); 이는 현재 boid 방향을 가져오는 최종 명령문입니다. lerp를 사용하고 변수 ip를 가중치로 제공합니다. 그리고 문자 그대로의 의미는 회전속도(rotationSpeed)는 보이드의 조향 속도를 의미하는데, 일반적으로 조향을 최대한 부드럽게 하고 싶어서 조향 속도가 더 작은 경우에는 lerp를 사용합니다. 그러면 원래의 보이드 방향을 유지하려는 경향이 커질수록, 즉 IP가 1에 가까워집니다.
    • cursor? 여기서, 저는 개인적으로 마우스와 함께 boid가 움직이는 효과를 얻고 싶어서 cursor를 추가했습니다. 이 변수는 마우스의 현재 위치를 나타냅니다. 응집력과 매우 유사하다는 것을 알게 될 것입니다. 그러나 저는 응집력을 계산하여 커서를 계산하는 대신 마우스 위치를 직접 주어 방향으로 추가합니다. 이것은 필요에 따라 조정할 수 있는 범주입니다. 저는 단지 boid가 화면의 2차원 공간에서 마우스를 따라가 2차원 방향(위, 아래, 왼쪽, 오른쪽)으로 움직일 수 있기를 바랄 뿐입니다. 저는 마우스가 실제로 세계 공간에서 위치가 되기를 원하지 않습니다.
    • 왜 합산 후 평균을 내는 걸까요? 가장 중요한 세 벡터가 모두 더하고 평균을 내는 방법을 채택한다는 것을 알게 될 겁니다. 응집력은 이해하기 더 쉽습니다. 결국 평균 위치를 나타내기 때문입니다. 그리고 다른 두 벡터는 사실, 벡터 덧셈에 대한 평행사변형 법칙

 

[각주:5]을 생각해 보면 두 벡터를 더한 후, 두 벡터가 중간을 가리킨다는 것을 알게 될 겁니다. 마지막 효과로 방향과 크기의 변화가 달성됩니다.

컴퓨팅 셰이더 코드가 가장 중요한 구현이고 나머지는 스크립트와 셰이더의 CS에서 일치시키는 것입니다.

 

 

Script

 

여기서는 GPU 인스턴싱 작업, 즉 DrawMeshInstancedIndirect를 사용하여 보이드를 그릴 것입니다. 이 부분은 이 글의 초점이 아닐 것으로 생각됩니다. 공식 문서나 다른 글을 참조하여 학습하시면 됩니다.

먼저 몇 가지 공통 정의 변수 작업입니다. 여기서는 boidSpeedVariation의 범위가 제한되어야 한다는 점에 유의하세요. 왜냐하면 실제 측정에 따르면 일부 boid 이상값은 1보다 클 때 발생하기 때문입니다(너무 이상합니다). 너무 높게 설정하면 일부 보이드가 되돌아갈 수 없게 되고 마침내 neighborRadius 범위를 벗어나 0.0에서 점점 더 멀어지게 됩니다.


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InstancedFlocking : MonoBehaviour
{
    private Vector2 cursorPos;

    public struct Boid
    {
        public Vector3 position;
        public Vector3 direction;
        public float noise_offset;

        public Boid(Vector3 pos, Vector3 dir, float offset)
        {
            position.x = pos.x;
            position.y = pos.y;
            position.z = pos.z;
            direction.x = dir.x;
            direction.y = dir.y;
            direction.z = dir.z;
            noise_offset = offset;
        }
    }

    public ComputeShader shader;

    public float rotationSpeed = 1f;
    public float boidSpeed = 1f;
    public float neighbourDistance = 1f;
    [Range(0, 1)]public float boidSpeedVariation = 1f;
    public Mesh boidMesh;
    public Material boidMaterial;
    public int boidsCount;
    public float spawnRadius;
    public Transform target;
    public float cohesionWeight;
    public float cursorWeight;

    int kernelHandle;
    ComputeBuffer boidsBuffer;
    ComputeBuffer argsBuffer;
    uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
    Boid[] boidsArray;
    int groupSizeX;
    int numOfBoids;
    Bounds bounds;

잠시 args에 대해 이야기해 보겠습니다. 두 번째 요소는 그려질 인스턴스 수를 나타냅니다. 아래 경계와 위 argsBuffer는 DrawMeshInstancedIndirect에 사용됩니다.

numOfBoids도 여기에 사용되는데, 이는 boidsCount와 다소 겹칩니다. 그 기능은 재생 모드에서 boids 수가 수정되는 것을 방지하는 것입니다. 다음에는 star 함수에서 numOfBoids가 고정 값으로 할당되고 이후에는 변경되지 않습니다. 저것. .


다음으로 초기화합니다.


void Start()
    {
        kernelHandle = shader.FindKernel("CSMain");

        uint x;
        shader.GetKernelThreadGroupSizes(kernelHandle, out x, out _, out _);
        groupSizeX = Mathf.CeilToInt((float)boidsCount / (float)x);
        numOfBoids = groupSizeX * (int)x;

        bounds = new Bounds(Vector3.zero, Vector3.one * 1000);

        InitBoids();
        InitShader();
    }

    private void InitBoids()
    {
        boidsArray = new Boid[numOfBoids];

        for (int i = 0; i < numOfBoids; i++)
        {
            Vector3 pos = transform.position + Random.insideUnitSphere * spawnRadius;
            Quaternion rot = Quaternion.Slerp(transform.rotation, Random.rotation, 0.3f);
            float offset = Random.value * 1000.0f;
            boidsArray[i] = new Boid(pos, rot.eulerAngles, offset);
        }
    }

    void InitShader()
    {
        boidsBuffer = new ComputeBuffer(numOfBoids, 7 * sizeof(float));
        boidsBuffer.SetData(boidsArray);

        argsBuffer = new ComputeBuffer(1, 5 * sizeof(uint), ComputeBufferType.IndirectArguments);
        if (boidMesh != null)
        {
            args[0] = (uint)boidMesh.GetIndexCount(0);
            args[1] = (uint)numOfBoids;
        }
        argsBuffer.SetData(args);

        shader.SetBuffer(this.kernelHandle, "boidsBuffer", boidsBuffer);
        shader.SetFloat("rotationSpeed", rotationSpeed);
        shader.SetFloat("boidSpeed", boidSpeed);
        shader.SetFloat("boidSpeedVariation", boidSpeedVariation);
        shader.SetVector("flockPosition", target.transform.position);
        shader.SetFloat("neighbourDistance", neighbourDistance);
        shader.SetInt("boidsCount", numOfBoids);
        shader.SetFloat("cohesionWeight", cohesionWeight);
        shader.SetFloat("cursorWeight", cursorWeight);

        boidMaterial.SetBuffer("boidsBuffer", boidsBuffer);
    }

일부 변수에 값을 할당할 것입니다. 또한 모든 보이드에 초기 위치와 상태를 지정해야 합니다. 여기서 모든 보이드는 조정 가능한 반경을 가진 공으로 생성되며 해당 방향은 Quaternion.Slerp 를 사용합니다 . lerp는 거의 동일합니다. 가장 중요한 것은 boid를 일반적으로 같은 방향으로 유지하는 동시에 cs에 필요한 변수를 전달하는 것입니다. InitShader에서 args도 초기화한 것을 볼 수 있습니다. args의 두 번째 요소는 numOfBoids와 같습니다.

마지막으로, 업데이트 중에 언제든지 보이드의 상태를 변경하세요.


void Update()
    {
        Vector2 mousePos = new Vector2(cursorPos.x, cursorPos.y);
        shader.SetFloat("time", Time.time);
        shader.SetFloat("deltaTime", Time.deltaTime);
        shader.SetVector("cursor", new Vector3(mousePos.x, mousePos.y, 0));

        shader.Dispatch(this.kernelHandle, groupSizeX, 1, 1);

        Graphics.DrawMeshInstancedIndirect(boidMesh, 0, boidMaterial, bounds, argsBuffer);
    }

    void OnDestroy()
    {
        if (boidsBuffer != null)
        {
            boidsBuffer.Dispose();
        }

        if (argsBuffer != null)
        {
            argsBuffer.Dispose();
        }
    }

    void OnGUI(){
        Vector3 p = new Vector3();
        Camera mainCamera = Camera.main;
        Event currentEvent = Event.current;
        Vector2 mousePos = new Vector2();

        mousePos.x = currentEvent.mousePosition.x;
        mousePos.y = mainCamera.pixelHeight - currentEvent.mousePosition.y;

        p = mainCamera.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, mainCamera.nearClipPlane));
  
        cursorPos.x = p.x;
        cursorPos.y = p.y;
    }
}

OnGUI는 마우스 위치를 획득하는 것입니다. DrawMeshInstancedIndirect의 사용법은 공식 문서에서 찾을 수 있습니다. 여기서는 참고용으로 내 메모(반드시 정확하지는 않음)를 발췌했습니다.

프로그래밍 방식으로 그리려면 Graphics.DrawMeshInstancedProcedural 메소드를 호출해야 합니다. 첫 번째 매개변수는 우리가 참조하는 메쉬이고, 두 번째 매개변수는 메쉬에 여러 부분이 포함된 경우에만 유용한 하위 메쉬 인덱스입니다. 세 번째는 우리가 참조한 머티리얼입니다. 프로그램은 머티리얼의 셰이더를 사용하여 이런 식으로 그리지 않기 때문에 Unity는 어디에서 그릴지 알 수 없습니다. 장면. . 경계를 사용하여 그리기 공간을 지정할 수 있습니다. 마지막으로 버퍼의 요소 수(즉, numOfBoids로도 간주될 수 있는 boidsBuffer의 요소 수)도 지정해야 합니다. argsBuffer를 사용하여 가져올 수 있습니다. 이는 다섯 번째 매개변수입니다.

좋아요, 사실, 앞으로는 비교적 간단하죠, 그렇죠? 적어도 저는 우리의 cs와 스크립트가 비교적 간단하다고 생각합니다. 다음 단계는 surface shader defense(?) 입니다. 저는 개인적으로 표면 셰이더에 익숙하지 않아서 기본적으로 Big guy의 기사에서 그대로 복사했습니다(자세한 내용은 "참고문헌"을 참고하세요). hhh[각주:6]를 모르기 때문에 자세한 설명은 하지 않겠습니다. 나중에 관련 내용을 알게 되면 돌아와서 더 추가할 수도 있습니다.

 

 

Surface Shader

 

GPU 인스턴싱의 한 가지 특징은 CS에서 계산한 양이 버텍스 셰이더에 직접 전달될 수 있다는 것입니다.

CPU로 다시 전달하지 않고 픽셀 셰이더와 함께 사용하세요. 이제 각 보이드의 방향과 위치가 cs에서 계산되었으므로 이 데이터를 사용하여 정점 셰이더에서 올바른 변환을 완료한 다음 전달하면 됩니다. 그냥 픽셀 셰이더로 그리면 됩니다.

여기서는 동차좌표(homogeneous coordinates)[각주:7]를 사용합니다.

관련 변환을 완료합니다(자세한 내용은 games101 참조). 모든 코드는 여기에 직접 제공됩니다. (코드 레이아웃이 약간 나쁘지만 읽을만 할겁니다). 역자주/ CG코드이지만 HLSL로 변환할때 특별히 어려운 부분은 없어 보인다.


Shader "Flocking/Instanced" { 

    Properties {
            _Color ("Color", Color) = (1,1,1,1)
            _MainTex ("Albedo (RGB)", 2D) = "white" {}
            _BumpMap ("Bumpmap", 2D) = "bump" {}
            _MetallicGlossMap("Metallic", 2D) = "white" {}
            _Metallic ("Metallic", Range(0,1)) = 0.0
            _Glossiness ("Smoothness", Range(0,1)) = 1.0
        }

    SubShader {

            CGPROGRAM

            sampler2D _MainTex;
            sampler2D _BumpMap;
            sampler2D _MetallicGlossMap;
            struct Input {
                float2 uv_MainTex;
                float2 uv_BumpMap;
                float3 worldPos;
            };
            half _Glossiness;
            half _Metallic;
            fixed4 _Color;

            #pragma surface surf Standard vertex:vert addshadow nolightmap
            #pragma instancing_options procedural : setup

            float4x4 _Matrix;
            float3 _BoidPosition;

            #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
                struct Boid
                {
                    float3 position;
                    float3 direction;
                    float noise_offset;
                };

                StructuredBuffer<Boid> boidsBuffer; 
            #endif

            float4x4 create_matrix(float3 pos, float3 dir, float3 up)
           {

            float3 zaxis = normalize(dir);
            float3 xaxis = normalize(cross(up, zaxis));
            float3 yaxis = cross(zaxis, xaxis);
            return float4x4(
                xaxis.x, yaxis.x, zaxis.x, pos.x,
                xaxis.y, yaxis.y, zaxis.y, pos.y,
                xaxis.z, yaxis.z, zaxis.z, pos.z,
                0, 0, 0, 1
                                   );
            }
  
            void vert(inout appdata_full v, out Input data)
            {
            UNITY_INITIALIZE_OUTPUT(Input, data);

            #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
                v.vertex = mul(_Matrix, v.vertex);
            #endif
            }

            void setup()
            {
                #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
                    _BoidPosition = boidsBuffer[unity_InstanceID].position;
                    _Matrix = create_matrix(boidsBuffer[unity_InstanceID].position, boidsBuffer[unity_InstanceID].direction, float3(0.0, 1.0, 0.0));
                #endif
            }

            void surf (Input IN, inout SurfaceOutputStandard o) {
                fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
                fixed4 m = tex2D (_MetallicGlossMap, IN.uv_MainTex); 
                o.Albedo = c.rgb;
                o.Alpha = c.a;
                o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
                o.Metallic = m.r;
                o.Smoothness = _Glossiness * m.a;
            }

            ENDCG
        }
    }

이제 남은 것은 효과뿐입니다.

빈 개체를 만들고 스크립트를 드래그한 다음 cs를 스크립트로 드래그하고 표면 셰이더를 재료로 드래그한 다음 스크립트의 메쉬를 큐브로 드래그합니다. 관련 매개변수는 다음과 같습니다.

 

참고: 큐브가 너무 크기 때문에 카메라 관찰을 용이하게 하기 위해 객체의 z 값을 50으로 설정했습니다.

효과가 좋지 않다고 생각되더라도 걱정하지 마세요. 매개변수를 다시 조정하여 더 좋게 보이도록 만들 수도 있고, 보이드에 재질과 스킨 애니메이션을 추가할 수도 있습니다. 그럼 바로 배워보겠습니다 0.0)

 

Reference

 

 

  1. 무리를 짓는 행동을 사실적으로 시뮬레이션 하는 알고리즘으로 Bird-oid-object 의 약자이다. 
    https://vanhunteradams.com/Pico/Animal_Movement/Boids-algorithm.html [본문으로]
  2. https://1217pgy.tistory.com/7 [본문으로]
  3. 커널 함수는 컴퓨팅 셰이더에서 특정 작업을 수행하는 데 사용되는 함수를 의미합니다. 예를 들어, 이 예에서 커널 함수는 클러스터 시뮬레이션 알고리즘을 구현하고 각 개체(boid)에 대한 계산을 수행합니다.

    • 커널 기능의 구체적인 의미:
      • GPU 프로그래밍에서 커널 함수는 GPU의 병렬 처리 장치에서 실행되는 함수입니다.
      • 이는 효율성을 높이기 위해 병렬화할 수 있는 광범위한 데이터 처리 작업을 수행하는 데 종종 사용됩니다.
      • 커널 함수의 호출 방식은 일반 함수와 다르며, CUDA 등의 API를 통해 GPU에서 실행되도록 예정되어 있으며, 여러 스레드를 동시에 실행하여 계산 속도를 높일 수 있습니다.
      • 커널 함수의 코드는 성능을 최적화하기 위해 GPU의 병렬 아키텍처를 사용하여 GPU에서 실행될 수 있는 형태로 컴파일됩니다.
    • 확대:
      • 커널 함수는 운영 환경을 지정하기 위해 다양한 한정자를 지원합니다. 예를 들어 __device__는 함수가 장치 측(즉, GPU)에서만 실행될 수 있음을 나타내고 __global__은 이 함수가 호스트 측에서 호출될 수 있음을 나타냅니다( CPU)이지만 실제로는 장치 측에서 실행됩니다.
      • 커널 함수에는 매개변수 전달, 변수 선언 및 메모리 액세스에 대한 특정 규칙이 있습니다. 예를 들어 정적 변수를 선언할 수 없거나 가변 길이 매개변수 목록을 사용할 수 없습니다.
      • 커널 기능 설계에서는 데이터가 병렬로 처리되는 방식에 직접적인 영향을 미치는 스레드 블록 및 그리드 할당을 포함하여 스레드 레이아웃을 고려해야 합니다.
      • 어떤 맥락에서 커널 함수의 개념은 기계 학습의 커널 함수와 유사합니다. 둘 다 복잡한 문제를 해결하기 위해 원시 데이터를 고차원 공간에 매핑하는 것을 포함하지만 구현 세부 사항과 기술 적용에는 차이가 있습니다.

    [본문으로]

  4. 클러스터 알고리즘은 그룹 행동을 시뮬레이션하고 제어하는 ​​데 사용되는 컴퓨팅 방법을 말합니다. 이는 개인 간의 상호 작용 규칙을 정의하여 복잡한 시스템의 협력적 행동을 달성합니다. 원래 텍스트에서 떼 지어 움직이는 알고리즘은 보이드(새 모델의 단순화된 버전)의 행동을 시뮬레이션하는 데 사용되는 일련의 계산 단계를 말합니다. 이러한 단계에는 충돌을 피하고, 형성의 모양을 유지하고, 리더를 따르는 규칙이 포함됩니다.

    클러스터 알고리즘의 구체적인 의미는 다음과 같습니다.

    • 충돌 회피: 각 개인은 주변 이웃의 위치에 따라 이동 방향을 조정하여 이웃과의 충돌을 방지합니다.
    • 일관된 속도: 개인은 속도가 일관되고 긴밀한 대형을 형성하도록 서로 영향을 미칩니다.
    • 중앙 집합: 모든 개인은 대형의 무결성을 유지하기 위해 대형의 중심점을 향해 이동합니다.

    이 외에도 클러스터링 알고리즘에는 특정 개인이 리더 역할을 하고 다른 개인이 리더의 움직임을 따르는 리더 규칙과 같은 다른 규칙도 포함될 수 있습니다. 이러한 규칙은 함께 작용하여 그룹 행동의 복잡한 패턴을 생성합니다.

    스웜 알고리즘은 비행 물체의 동작을 시뮬레이션하는 데 국한되지 않고 UAV 편대 비행 및 지상 로봇의 협업과 같은 로봇 공학의 다중 로봇 시스템에도 적용될 수 있습니다. 또한 컴퓨터 그래픽 분야에서는 클러스터링 알고리즘을 사용하여 새 떼, 물고기 또는 군중 시뮬레이션과 같은 사실적인 그룹 애니메이션 효과를 생성합니다. 실제 적용에서 이러한 알고리즘이 고려해야 할 요소에는 환경 인식, 동적 장애물 회피 전략 및 통신 지연이 포함됩니다. [본문으로]

  5. 평행사변형 법칙은 벡터 덧셈에서 두 벡터의 시작점을 같은 점에 놓고 이 두 벡터를 인접한 변으로 하여 평행사변형을 만드는 것을 의미합니다.

    구체적으로:

    • 수학적 정의 : 두 벡터의 합은 평행사변형 법칙으로 표현될 수 있습니다. 두 벡터 A와 B가 있고 두 벡터의 시작점을 같은 위치에 놓고 평행사변형의 인접한 두 변의 역할을 한다고 가정하면 평행사변형의 대각선은 이 두 벡터의 합을 나타냅니다.
    • 물리학 응용 : 물리학에서 평행사변형 규칙은 힘과 속도와 같은 벡터의 합성에 사용될 수 있습니다. 예를 들어 두 힘이 동시에 물체에 작용할 때 평행사변형 법칙을 사용하여 두 힘의 합력의 크기와 방향을 계산할 수 있습니다.
    • 실험적 검증 : 평행사변형 법칙의 타당성은 '등가법', '평형법' 등의 실험적 방법을 통해 검증할 수 있습니다. 실험에서는 일반적으로 고무밴드를 힘을 받는 물체로 선택하고, 서로 일정한 각도를 이루는 두 힘을 사용하여 고무밴드를 늘린 다음, 고무밴드가 동일한 변형을 일으키도록 하기 위해 별도의 힘을 사용합니다. 따라서 이 단일 힘은 두 가지 구성 요소의 결과임을 증명합니다.

    또한, 평행사변형 법칙은 유클리드 공간의 기본 특성으로 역학에서 힘의 합성에만 적용할 수 있는 것이 아니라, 전계 강도의 중첩과 같은 다른 벡터의 합성에도 적용할 수 있습니다. [본문으로]

  6. hlsl인가 싶은데 surface shader는 또 Cg 기반이라 뭘 이야기 하는지 했는데 그냥 hahaha... 였음...; [본문으로]
  7. 동차좌표란 투영된 기하학에 사용되는 좌표계를 말하며, 원래의 n차원 벡터를 n+1차원 벡터로 표현하여 구현한다. 구체적으로, 2차원 공간에서 점 (x, y)는 동차 좌표 (hx, hy, h)로 표현될 수 있습니다. 여기서 h는 3차원 공간에서 0이 아닌 스케일 인수입니다. y, z)는 동차좌표(hx, hy, hz, h)로 표현될 수 있다. 이 표현을 사용하면 무한한 점을 더 쉽게 처리하고 아핀 변환을 수행할 수 있습니다.

    동차 좌표를 사용하면 다음과 같은 이점이 있습니다.

    • 단순화된 표현 : 동차좌표는 직선이나 평면 위의 점들 사이의 관계를 매우 편리하게 표현할 수 있습니다. 예를 들어, 2D 평면에서 직선은 ax + by + c = 0 방정식으로 나타낼 수 있으며, 직선 위의 점 p = (x, y)에 대한 조건은 ax + by + c = 0입니다. 동차좌표를 사용하면 점 p의 동차좌표는 p'=(x, y, 1)이고, 점 p가 직선 l 위에 있다는 조건은 두 벡터의 내적은 0이 된다.
    • 단순화된 작업 : 동차 좌표를 사용하면 3차원 공간에서 점, 선 및 표면 표현, 회전 및 이동과 같은 작업을 크게 단순화할 수 있습니다. 예를 들어 두 선이나 평면의 교차점은 외적(외적)으로 간결하게 표현할 수 있습니다.
    • 무한대에서의 점 처리 : 데카르트 좌표계에서는 평행선이 교차할 수 없지만 동차 좌표계에서는 두 개의 평행선이 무한대의 한 점에서 교차할 수 있습니다. 이를 통해 균일한 좌표가 무한히 먼 점의 문제를 자연스럽게 처리할 수 있게 되어 유클리드 기하학으로 설명할 수 없는 특정 시나리오를 해결할 수 있습니다.
    • 행렬 연산의 편리성 : 동차 좌표는 행렬 연산을 사용하여 한 좌표계에서 다른 좌표계로 점 집합을 변환하는 효과적인 방법을 제공하며 이는 그래픽 변환 작업에 매우 중요합니다.

    요약하면, 동차 좌표는 기하학적 계산 과정을 단순화할 뿐만 아니라 특히 투영, 원근 및 무한과 관련된 장면을 다룰 때 기하학적 객체에 대한 이해 범위를 확장하는 강력한 도구입니다. [본문으로]

반응형

'Technical Report > Unity Shader' 카테고리의 다른 글

DXC shader compiler integration  (0) 2023.02.13
stochastic tiling shader  (0) 2022.05.29
PBR-BRDF-Disney-Unity-1  (0) 2022.03.02
URP Realtime Shadow color control  (2) 2022.01.22
URP BlinnPhong Default Character Shader  (0) 2021.07.14