<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Juicy</title>
        <link>https://velog.io/</link>
        <description>개발 무지 달다...🍉</description>
        <lastBuildDate>Sun, 07 Nov 2021 12:10:25 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Juicy</title>
            <url>https://images.velog.io/images/dev_juicy/profile/e5bfc583-66a6-47e3-8121-3216e6e3ba6d/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Juicy. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_juicy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Unity 구(Sphere)를 직접 만들어보자 🏐]]></title>
            <link>https://velog.io/@dev_juicy/Unity-%EC%A7%81%EC%A0%91-%EA%B5%ACSphere%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev_juicy/Unity-%EC%A7%81%EC%A0%91-%EA%B5%ACSphere%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 07 Nov 2021 12:10:25 GMT</pubDate>
            <description><![CDATA[<p>Unity에서 기본적으로 제공하는 3D 오브젝트중 Sphere를 만들었을때 WireFrame을 보면 이런 삼각형들로 이루어져있다.
<img src="https://images.velog.io/images/dev_juicy/post/6b957634-f1bf-417b-9ef6-4c64a2f7b30c/image.png" alt="">
사실 컴퓨터로 표현되는 3D모델들은 모두 폴리곤이라는 삼각형들이 모여 하나의 물체를 이루기 때문에 구 또한 삼각형들의 집함으로 이루어져있다는걸 알 수 있다. 이 폴리곤들이 촘촘하면 촘촘 할 수록 더 매끈한 구가 되겠지만 그만큼 연산량은 많아질것이다. 
<img src="https://images.velog.io/images/dev_juicy/post/04700b53-de24-402c-bec3-7ba0fbc1065b/image.png" alt=""></p>
<blockquote>
<p>넓은의미로 모두 구 이다.</p>
</blockquote>
<p>구를 만들기 위해 차근차근 <strong>[ 평면 -&gt; 정육면체 -&gt; 구체 ]</strong> 순으로 만들어 볼 생각이다.</p>
<hr>
<h3 id="평면-만들기">평면 만들기</h3>
<p>평면을 만들기 위해 생성자로 3개의 인자를 전달받을 껀데, 정점배열들을 이용해서 면을 채워줄 mesh, 정점들의 갯수를 결정하는 resolution, 면이 중심으로부터 생성될 방향을 결정할 localUp을 정의해준다.</p>
<pre><code class="language-c#">public class MeshFace
{
    private Mesh mesh;
    private int resolution;
    private Vector3 localUp;
    private Vector3 axisA;
    private Vector3 axisB;

    public MeshFace(Mesh mesh, int resolution, Vector3 localUp)
    {
        this.mesh = mesh;
        this.resolution = resolution;
        this.localUp = localUp;

        axisA = new Vector3(localUp.y, localUp.z, localUp.x);
        axisB = Vector3.Cross(localUp, axisA);
    }
}</code></pre>
<p>axisA와 axisB는 localUp벡터를 y축방향 벡터라고 생각한다면 각각 x, z 벡터라 할 수 있겠다.</p>
<p>화면에 직접 보이도록하기 위해서는 Mesh라는 컴포넌트를 이용해야하는데, Mesh에게 정점의 위치정보 mesh.vertices와  정점을 그리는 순서를 담은 mesh.triangles를 정해주면 그에따라 면을 그려주게 된다. <a href="https://docs.unity3d.com/kr/530/ScriptReference/Mesh.html">https://docs.unity3d.com/kr/530/ScriptReference/Mesh.html</a></p>
<pre><code class="language-c#">mesh.vertices 
mesh.triangles</code></pre>
<p>그렇다면 이제 만들어야할것은 vertices와 triangles에 들어가야할 배열들을 계산해줘야할듯 싶다.</p>
<p>먼저 <strong>vertices</strong>, 정점의 갯수는 resolution의 값에 따라 일괄적으로 줄여주거나 늘려줄것이다. resolution = 5 일경우 가로 5개 x 세로 5개 총 25개의 정점을 만들도록 할 것이다.</p>
<p><img src="https://images.velog.io/images/dev_juicy/post/7fd23e6a-1fdb-45cb-bc87-96dfc241f84f/image.png" alt=""></p>
<pre><code class="language-c#">Vector3[] vertices = new Vector3[resolution * resolution];</code></pre>
<p>다음 <strong>triangles</strong>,</p>
<p>resolution = 5 일경우 (5 - 1)개의 가로사각형 (5-1)개의 세로사각형 = 16개의 사각형을 만들수 있을것이고, 사각형1개는 2개의 삼각형으로 이루어져있고, 이 삼각형은 3개의 정점으로 이루어져 있다.</p>
<p><img src="https://images.velog.io/images/dev_juicy/post/5ee51371-23e1-45ce-bfec-13e787330a65/image.png" alt=""></p>
<pre><code class="language-c#">int[] triangles = new int[(resolution - 1) * (resolution - 1) * 2 * 3];</code></pre>
<p>이제 vertices와 triangles의 값들을 채워보자</p>
<pre><code class="language-c#">public void CreateMesh()
{
    Vector3[] vertices = new Vector3[resolution * resolution];
    int[] triangles = new int[(resolution - 1) * (resolution - 1) * 2 * 3];

    int triIndex = 0;
    for (int y = 0; y &lt; resolution; y++)
    {
        for (int x = 0; x &lt; resolution; x++)
        {
            int vertexIndex = x + y * resolution;
            Vector2 percent = new Vector2(x, y) / (resolution - 1); // 0~1로 노멀라이즈
            Vector3 pointOnUnitCube = localUp + (percent.x - .5f) * 2 * axisA + (percent.y - .5f) * 2 * axisB;
            Vector3 pointOnUnitSphere = pointOnUnitCube.normalized;
            vertices[vertexIndex] = pointOnUnitCube;

            if (x != resolution - 1 &amp;&amp; y != resolution - 1)
            {
                triangles[triIndex] = vertexIndex;
                triangles[triIndex + 1] = vertexIndex + resolution + 1;
                triangles[triIndex + 2] = vertexIndex + resolution;

                triangles[triIndex + 3] = vertexIndex;
                triangles[triIndex + 4] = vertexIndex + 1;
                triangles[triIndex + 5] = vertexIndex + resolution + 1;
                triIndex += 6;
            }
        }
    }
    mesh.Clear();
    mesh.vertices = vertices;
    mesh.triangles = triangles;
    mesh.RecalculateNormals();
}</code></pre>
<p>코드를 하나하나 봐보자면 </p>
<pre><code class="language-c#">int vertexIndex = x + y * resolution;
Vector2 percent = new Vector2(x, y) / (resolution - 1); // 0~1로 노멀라이즈
Vector3 pointOnUnitCube = (percent.x - 0.5f) * 2 * axisA + (percent.y - 0.5f) * 2 * axisB + localUp; </code></pre>
<p>resolution = 5라고 가정하면,</p>
<p><strong>percent</strong> : x, y가 (0,0) 일때부터 (4,4) 까지의 값을 가질텐데 그 값을 4로 나눠주기 때문에 (0,0) ~ (1,1) 의 값으로 노멀라이즈 해주게된다.</p>
<p><strong>pointOnUnitCube</strong> :  x랑 y의 값을 - 0.5 만큼 뺀 값을 2만큼 곱해주어, (0,0) ~ (1,1)의 범위의 값들이 (-1,-1) ~ (1,1)의 값으로 바뀔것이고 이에 localUp 벡터만큼 더해주면 다음과같은 평면의 벡터들을 구할 수 있을것이다.</p>
<p><img src="https://images.velog.io/images/dev_juicy/post/23465eb6-167a-4cfc-af35-c5e1c614edb3/image.png" alt=""></p>
<p><strong>triangles[ ]</strong> : 아래 그림의 각 숫자가 vertexIndex인데 삼각형의 정점을 시계방향순으로 써보면 vertexIndex, vertexIndex +  1, vertexIndex + resolution 이라는 하단 삼각형과 vertexIndex, vertexIndex + 1, vertexIndex + resolution + 1 이라는 상단 삼각형의 순서를 구할 수 있다.</p>
<p><img src="https://images.velog.io/images/dev_juicy/post/9c9242d8-0b0c-4eec-9e48-4508d3b0e259/image.png" alt=""></p>
<p>이렇게 vertices와 triangles를 구했으면 mesh에 넣어주면된다.</p>
<pre><code class="language-c#">mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles
mesh.RecalculateNormals();</code></pre>
<hr>
<p>테스트를 해보자</p>
<pre><code class="language-c#">using UnityEngine;

public class Sphere : MonoBehaviour
{
    [Range(2, 256)]
    public int resolution = 5;

    private void OnValidate()
    {
        TEST();
    }

    void TEST()
    {
        GameObject meshObj = new GameObject(&quot;mesh&quot;);
        meshObj.transform.parent = transform;

        meshObj.AddComponent&lt;MeshRenderer&gt;().sharedMaterial = new Material(Shader.Find(&quot;Standard&quot;));
        MeshFilter meshFilter = meshObj.AddComponent&lt;MeshFilter&gt;();
        meshFilter.sharedMesh = new Mesh();

        MeshFace face = new MeshFace(meshFilter.sharedMesh, resolution, Vector3.up);
        face.CreateMesh();
    }
}</code></pre>
<p><img src="https://images.velog.io/images/dev_juicy/post/d505ea57-3eb9-4be2-95a8-f37cd1b33e13/image.png" alt=""></p>
<p><strong>GOOD!</strong></p>
<hr>
<h3 id="정육면체-만들기">정육면체 만들기</h3>
<p>평면을 만들었다면 정육면체를 만드는건 너무나 쉽다. 평면을 6개만 만들면 되니까.</p>
<pre><code class="language-c#">Vector3[] directions = { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back };
for (int i = 0; i &lt; 6; i++)
{
    if (meshFilters[i] == null)
    {
        GameObject meshObj = new GameObject(&quot;mesh&quot;);
        meshObj.transform.parent = transform;

        meshObj.AddComponent&lt;MeshRenderer&gt;().sharedMaterial = new Material(Shader.Find(&quot;Standard&quot;));
        meshFilters[i] = meshObj.AddComponent&lt;MeshFilter&gt;();
        meshFilters[i].sharedMesh = new Mesh();
    }

    terrainFaces[i] = new MeshFace(meshFilters[i].sharedMesh, resolution, directions[i]);
}</code></pre>
<p><img src="https://images.velog.io/images/dev_juicy/post/7a97364d-2d34-4691-8368-a6039921071f/image.png" alt=""></p>
<p><strong>EASY~</strong></p>
<hr>
<h3 id="마지막으로-구체-만들기">마지막으로 구체 만들기</h3>
<p>고등학교 수학시간에 구를 배울때 구의 정의를 생각해보면 3차원에서 한 중점으로부터 거리가 같은 점들의 집합으로 배웠었다. 그렇다면 다시 돌아가서 아까 평면을 만들었을때를 봐보자</p>
<p><img src="https://images.velog.io/images/dev_juicy/post/00c011df-ccc7-488c-b64c-b8859eb07bcd/image.png" alt=""></p>
<p>저 평면을 이루는 벡터는 중점으로부터 거리가 정점마다 모두 다를 것이다. 이 벡터들을 방향은 같고 크기만 동일하게 노멀라이즈 해준다면</p>
<p><img src="https://images.velog.io/images/dev_juicy/post/73d56d94-aac8-4d77-a3d6-0491a33e6bb5/image.png" alt=""></p>
<p>마치 이런모양으로 바뀔것이다.</p>
<pre><code class="language-c#">Vector3 pointOnUnitCube = localUp + (percent.x - .5f) * 2 * axisA + (percent.y - .5f) * 2 * axisB;
Vector3 pointOnUnitSphere = pointOnUnitCube.normalized; // 노멀라이즈
vertices[vertexIndex] = pointOnUnitSphere; // 노멀라이즈한 값을 넣어줌</code></pre>
<p><img src="https://images.velog.io/images/dev_juicy/post/79d25d6c-33af-44cc-b35f-ce8d07c65c4c/image.png" alt=""></p>
<h4 id="great">GREAT!</h4>
<hr>
<p>Sphere.cs의 코드를 좀더 깔끔하게 다듬어 보면</p>
<pre><code class="language-c#">using UnityEngine;

public class Sphere : MonoBehaviour
{
    [Range(2, 256)]
    public int resolution = 10;

    [SerializeField, HideInInspector]
    MeshFilter[] meshFilters;
    MeshFace[] terrainFaces;

    private void OnValidate()
    {
        Initialize();
        GenerateMesh();
    }

    void Initialize()
    {
        if (meshFilters == null || meshFilters.Length == 0)
        {
            meshFilters = new MeshFilter[6];
        }
        terrainFaces = new MeshFace[6];

        Vector3[] directions = { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back };
        for (int i = 0; i &lt; 6; i++)
        {
            if (meshFilters[i] == null)
            {
                GameObject meshObj = new GameObject(&quot;mesh&quot;);
                meshObj.transform.parent = transform;

                meshObj.AddComponent&lt;MeshRenderer&gt;().sharedMaterial = new Material(Shader.Find(&quot;Standard&quot;));
                meshFilters[i] = meshObj.AddComponent&lt;MeshFilter&gt;();
                meshFilters[i].sharedMesh = new Mesh();
            }

            terrainFaces[i] = new MeshFace(meshFilters[i].sharedMesh, resolution, directions[i]);
        }
    }

    void GenerateMesh()
    {
        foreach (MeshFace face in terrainFaces)
        {
            face.CreateMesh();
        }
    }
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>