<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발자 공방</title>
    <link>https://jin-lee.tistory.com/</link>
    <description>모르면 배우고 없으면 만듭니다.</description>
    <language>ko</language>
    <pubDate>Mon, 13 Jul 2026 14:14:32 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jinlee0310</managingEditor>
    <item>
      <title>게임 기능에 디자인 패턴 적용하기</title>
      <link>https://jin-lee.tistory.com/9</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;들어가며&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희&amp;nbsp;회사에서는&amp;nbsp;React&amp;nbsp;Native를&amp;nbsp;사용하여&amp;nbsp;교육용&amp;nbsp;앱&amp;nbsp;서비스를&amp;nbsp;개발하고&amp;nbsp;있습니다.&amp;nbsp;이&amp;nbsp;서비스에서&amp;nbsp;저는&amp;nbsp;카드&amp;nbsp;게임이&amp;nbsp;들어가는&amp;nbsp;기능을&amp;nbsp;맡아&amp;nbsp;개발하게&amp;nbsp;되었습니다.&lt;br /&gt;&lt;br /&gt;하지만 개발을 시작한 지 얼마 지나지 않아 다양한 인터랙션과 애니메이션, 게임의 상태 관리, 카드의 위치와 상태 관리 등 복잡하게 뒤엉킨 요구사항에 부딪히게 되었습니다.&lt;br /&gt;&lt;br /&gt;이러한 복잡한 요구사항에 디자인 패턴을 적용하여 풀어나간 이야기를 써보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;요구사항&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심&amp;nbsp;요구사항은&amp;nbsp;카드&amp;nbsp;슬롯&amp;nbsp;교체&amp;nbsp;기능이었습니다.&amp;nbsp;2개의&amp;nbsp;보드&amp;nbsp;슬롯&amp;nbsp;중&amp;nbsp;한&amp;nbsp;곳에&amp;nbsp;이미&amp;nbsp;카드가&amp;nbsp;있을&amp;nbsp;때,&amp;nbsp;다른&amp;nbsp;카드를&amp;nbsp;올리면&amp;nbsp;기존&amp;nbsp;카드가&amp;nbsp;자동으로&amp;nbsp;빈&amp;nbsp;슬롯으로&amp;nbsp;이동해야&amp;nbsp;했습니다.&lt;br /&gt;&lt;br /&gt;이를&amp;nbsp;구현하기&amp;nbsp;위해&amp;nbsp;카드&amp;nbsp;배치&amp;nbsp;상태&amp;nbsp;플래그,&amp;nbsp;드래그&amp;nbsp;시의&amp;nbsp;Snap&amp;nbsp;로직,&amp;nbsp;위치&amp;nbsp;변경&amp;nbsp;애니메이션&amp;nbsp;트리거&amp;nbsp;등&amp;nbsp;useState,&amp;nbsp;useRef,&amp;nbsp;react-native-reanimated&amp;nbsp;로직이&amp;nbsp;메인&amp;nbsp;컴포넌트에&amp;nbsp;집중되었습니다.&amp;nbsp;게임에&amp;nbsp;다른&amp;nbsp;여러가지&amp;nbsp;기능이&amp;nbsp;추가되면서&amp;nbsp;메인&amp;nbsp;컴포넌트는&amp;nbsp;수많은&amp;nbsp;state와&amp;nbsp;side&amp;nbsp;effect들로&amp;nbsp;인해&amp;nbsp;점점&amp;nbsp;비대해져갔습니다.&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;문제를&amp;nbsp;해결하기&amp;nbsp;위해&amp;nbsp;카드간의&amp;nbsp;관계와&amp;nbsp;애니메이션을&amp;nbsp;제어하는&amp;nbsp;로직을&amp;nbsp;Mediator&amp;nbsp;패턴이라는&amp;nbsp;디자인&amp;nbsp;패턴을&amp;nbsp;적용하여&amp;nbsp;분리해보자&amp;nbsp;라는&amp;nbsp;생각을&amp;nbsp;하게&amp;nbsp;되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Mediator(중재자) 패턴이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중재자&amp;nbsp;패턴은&amp;nbsp;서로&amp;nbsp;독립적으로&amp;nbsp;작동해야&amp;nbsp;하는&amp;nbsp;컴포넌트&amp;nbsp;간의&amp;nbsp;모든&amp;nbsp;직접&amp;nbsp;통신을&amp;nbsp;중단한&amp;nbsp;후,&amp;nbsp;컴포넌트들의&amp;nbsp;호출을&amp;nbsp;적절한&amp;nbsp;컴포넌트들로&amp;nbsp;리다이렉션하는&amp;nbsp;특수&amp;nbsp;중재자&amp;nbsp;객체를&amp;nbsp;호출하여&amp;nbsp;간접적으로&amp;nbsp;협력하게&amp;nbsp;하라고&amp;nbsp;제안합니다.&amp;nbsp;그러면&amp;nbsp;컴포넌트들은&amp;nbsp;수십&amp;nbsp;개의&amp;nbsp;동료&amp;nbsp;컴포넌트들과&amp;nbsp;결합되는&amp;nbsp;대신&amp;nbsp;단일&amp;nbsp;중재자&amp;nbsp;클래스에만&amp;nbsp;의존하게&amp;nbsp;되어&amp;nbsp;결합도가&amp;nbsp;크게&amp;nbsp;낮아집니다.&amp;nbsp;&lt;br /&gt;카드&amp;nbsp;컴포넌트들은&amp;nbsp;드래그앤드롭을&amp;nbsp;통해&amp;nbsp;독립적으로&amp;nbsp;움직이지만,&amp;nbsp;동시에&amp;nbsp;다른&amp;nbsp;카드의&amp;nbsp;위치와&amp;nbsp;상태에&amp;nbsp;따라&amp;nbsp;자신의&amp;nbsp;위치가&amp;nbsp;강제로&amp;nbsp;변경되어야&amp;nbsp;했습니다.&quot;독립성을&amp;nbsp;유지하면서&amp;nbsp;상호작용은&amp;nbsp;필요한&quot;&amp;nbsp;상황이었기에,&amp;nbsp;중재자&amp;nbsp;패턴을&amp;nbsp;통해&amp;nbsp;카드&amp;nbsp;간의&amp;nbsp;복잡한&amp;nbsp;위치&amp;nbsp;결정&amp;nbsp;로직을&amp;nbsp;분리하는&amp;nbsp;것이&amp;nbsp;적합하다고&amp;nbsp;판단하였습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mediator&amp;nbsp;클래스를&amp;nbsp;구현하여&amp;nbsp;카드가&amp;nbsp;드롭되었을&amp;nbsp;때&amp;nbsp;카드&amp;nbsp;간&amp;nbsp;액션을&amp;nbsp;어떻게&amp;nbsp;할&amp;nbsp;것인지&amp;nbsp;결정하고&amp;nbsp;다른쪽&amp;nbsp;카드의&amp;nbsp;id를&amp;nbsp;찾아서&amp;nbsp;해당&amp;nbsp;카드의&amp;nbsp;움직임을&amp;nbsp;제어하는&amp;nbsp;로직을&amp;nbsp;구현하였습니다.&amp;nbsp;이러한&amp;nbsp;로직을&amp;nbsp;통해&amp;nbsp;카드간의&amp;nbsp;직접적인&amp;nbsp;상호작용을&amp;nbsp;피하고&amp;nbsp;복잡한&amp;nbsp;의존&amp;nbsp;관계를&amp;nbsp;줄일&amp;nbsp;수&amp;nbsp;있었습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1759055793334&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Mediator {
...
  public handleDrop = (cardId) =&amp;gt; {
    // 1. 카드에 발생해야 하는 이벤트를 계산
    const event = this.calculateEvent();

    // 2. 계산 결과를 바탕으로 UI 애니메이션을 지시
    this.animatePositions(event, cardId);
  };

  /**
   * 계산된 결과에 따라 Reanimated를 통해 카드들의 움직임을 제어
   * UI 스레드에서 실행되도록 runOnUI를 사용
   */
  private animatePositions = (event, cardId) =&amp;gt; {
    // UI 스레드에서 애니메이션 실행 지시
    runOnUI(() =&amp;gt; {
      'worklet';
      if (event==='snap') {
        // 단일 카드 애니메이션 지시
      } else if (event==='swap') {
        // 카드 A (드롭된 카드)의 위치 애니메이션 지시
        this.springAnimation(targetPositionA,cardId);
        
        // 카드 B (밀려난 카드)의 위치 애니메이션 지시
        this.swapAnimation(targetPositionB,otherCardId);
      }
    })();
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;br /&gt;맺음말&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안은 프론트엔드 코드를 작성하면서 컴포넌트 간의 복잡한 의존 관계를 줄이는 경험이 부족했었습니다.&amp;nbsp;&amp;nbsp;코드에 Mediator 패턴을 적용한 결과, 메인 컴포넌트는 깔끔하게 유지되었고 복잡했던 카드 상호작용 로직은 단일 책임 클래스로 캡슐화되었습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;이번&amp;nbsp;기능&amp;nbsp;구현과&amp;nbsp;리팩토링을&amp;nbsp;통해&amp;nbsp;디자인&amp;nbsp;패턴이&amp;nbsp;단순한&amp;nbsp;이론이&amp;nbsp;아니라&amp;nbsp;복잡성을&amp;nbsp;관리하고&amp;nbsp;코드의&amp;nbsp;수명을&amp;nbsp;연장할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;도구&amp;nbsp;중&amp;nbsp;하나라는&amp;nbsp;사실을&amp;nbsp;깨닫게&amp;nbsp;되었고&amp;nbsp;코드의&amp;nbsp;유지보수성을&amp;nbsp;높이는&amp;nbsp;좋은&amp;nbsp;경험을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있었습니다.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;본문의 코드 예시는 이해를 돕기 위한 간단한 예시일 뿐입니다.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;실제 프로덕션용 코드로 사용하기에는 수정이 필요한 부분이 많다는 점 참고바랍니다.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;- 참고 링크&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://refactoring.guru/ko/design-patterns/mediator&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://refactoring.guru/ko/design-patterns/mediator&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend</category>
      <author>jinlee0310</author>
      <guid isPermaLink="true">https://jin-lee.tistory.com/9</guid>
      <comments>https://jin-lee.tistory.com/9#entry9comment</comments>
      <pubDate>Sun, 28 Sep 2025 19:38:36 +0900</pubDate>
    </item>
    <item>
      <title>테블리 프로젝트 폴더 구조 설계</title>
      <link>https://jin-lee.tistory.com/7</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;OG_Image.png&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WCyfl/btsLEGHZEoV/tDFxaeRYR9KYBeSeIf1P9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WCyfl/btsLEGHZEoV/tDFxaeRYR9KYBeSeIf1P9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WCyfl/btsLEGHZEoV/tDFxaeRYR9KYBeSeIf1P9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWCyfl%2FbtsLEGHZEoV%2FtDFxaeRYR9KYBeSeIf1P9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;OG_Image.png&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트를 개발하면서 특히 신경썼던 부분들을 정리해본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기술스택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 사용한 기술 스택은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코어: React, Next.js, TypeScript&lt;/li&gt;
&lt;li&gt;스타일링: shadcn/ui, tailwind&lt;/li&gt;
&lt;li&gt;상태관리: jotai&lt;/li&gt;
&lt;li&gt;API 통신: React-Query, axios&lt;/li&gt;
&lt;li&gt;Package Manager: pnpm&lt;/li&gt;
&lt;li&gt;로깅: Sentry&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;폴더 구조 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서 폴더 구조 설계 방법에 정답은 없다. 하지만 이전의 개발 경험들을 미루어봤을 때 많은 양의 파일들 사이에서 수정해야 하는 코드를 찾는 일은 언제나 괴로웠기에.. 그리고 다른 사람과 의사소통을 할 때에도 필요한 코드를 찾는데 시간이 오래 걸리는 것은 소통의 불편함을 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분을 최대한 방지하고자 폴더구조를 설계할 때 코드의 맥락을 계속 생각하면서 설계하고 프로젝트의 초반에 원칙을 분명하게 세우려고 노력했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;/public&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public 아래에는 프로젝트에서 사용되는 fonts, images 등 asset 파일들을 넣었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;/app&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app폴더는 Next.js에서 라우팅 기능을 지원해주는 폴더이다. /app/blog-input이라는 폴더를 만들고 안에 page.tsx를 만들면 /blog-input이라는 url의 페이지가 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/app에서는 전역으로 사용하는 style과 error 페이지를 만들어 사용했고 url이름을 표시하는 폴더 안에서는 최대 section 단위의 컴포넌트까지만 사용하도록 하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;/src&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/src에서는 다시 constants, components, hooks, lib, store, types 등으로 나눈다. 해당 폴더에는 프로젝트 전체를 관통하는 파일들을 넣는다. 예를 들면 useThrottle, useResize등은 프로젝트 전체에서 사용 가능하기 때문에 /src/hooks아래에 넣는 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-05 오후 9.38.05.png&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HwYA3/btsLEZgcOPy/b3Y09HeRKkBt7gEfTsQ880/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HwYA3/btsLEZgcOPy/b3Y09HeRKkBt7gEfTsQ880/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HwYA3/btsLEZgcOPy/b3Y09HeRKkBt7gEfTsQ880/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHwYA3%2FbtsLEZgcOPy%2Fb3Y09HeRKkBt7gEfTsQ880%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;456&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2025-01-05 오후 9.38.05.png&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;/src/components&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;components들은 page url 기준으로 사용되는 컴포넌트들과 그렇지 않은 컴포넌트로 나눴다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-05 오후 9.38.51 1.png&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oc42R/btsLC5oDD7R/ZLzu7baJFRpuKplgjRo7jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oc42R/btsLC5oDD7R/ZLzu7baJFRpuKplgjRo7jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oc42R/btsLC5oDD7R/ZLzu7baJFRpuKplgjRo7jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foc42R%2FbtsLC5oDD7R%2FZLzu7baJFRpuKplgjRo7jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;404&quot; height=&quot;266&quot; data-filename=&quot;스크린샷 2025-01-05 오후 9.38.51 1.png&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url과 상관 없이 사용되는 컴포넌트들은 /shared와 /ui에 분류했다. shadcn/ui에서 받은 raw한 컴포넌트들과 Text처럼 ui 표시에 중점을 두는 컴포넌트들은 /ui에 추가하였고 KakaoAdfit 컴포넌트처럼 프로젝트 어디에서나 사용할 수 있는 컴포넌트는 shared에 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;page url을 기준으로 사용되는 컴포넌트들은 /blog-input, /blog-recap 하위에서 ui의 컴포넌트들을 조합하고 페이지에 필요한 로직들을 추가해서 만들었다. 이때 추가한 로직이나 타입 등은 /hooks, /lib, /store, /types 등으로 /src에서 나눈 것과 동일한 형식으로 다시 나눴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-05 오후 3.48.53.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwvs6C/btsLEMg3ofN/mSypcE3yQUY3hnNtt7OYK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwvs6C/btsLEMg3ofN/mSypcE3yQUY3hnNtt7OYK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwvs6C/btsLEMg3ofN/mSypcE3yQUY3hnNtt7OYK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwvs6C%2FbtsLEMg3ofN%2FmSypcE3yQUY3hnNtt7OYK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;308&quot; data-filename=&quot;스크린샷 2025-01-05 오후 3.48.53.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트에서 해당 부분은 section tag로 이루어져 있고 /app/blog-recap 폴더에 속해있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-05 오후 4.01.47.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dR5trH/btsLEHNEtEJ/OW7lyWKBKxy7TpULPONvPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dR5trH/btsLEHNEtEJ/OW7lyWKBKxy7TpULPONvPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dR5trH/btsLEHNEtEJ/OW7lyWKBKxy7TpULPONvPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdR5trH%2FbtsLEHNEtEJ%2FOW7lyWKBKxy7TpULPONvPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1144&quot; height=&quot;434&quot; data-filename=&quot;스크린샷 2025-01-05 오후 4.01.47.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;이 블로그 방문하기&amp;rsquo; 부분은 단순 텍스트로 이루어져 있기 때문에 /ui의 Text컴포넌트를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하단 부분은 blog-link-card라는 이름으로 /components/blog-recap 하위에 넣었다. blog-link-card에서 필요한 부분은 다음 두가지이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버에서 온 프로필 사진, 블로그 이름, 블로그 주소 데이터&lt;/li&gt;
&lt;li&gt;블로그 주소에서 프로토콜을 제외한 나머지 부분&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카드를 렌더링하는 데 필요한 데이터들은 /components/blog-recap/store에 분류하고 readonly atom을 이용해 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 주소에서 프로토콜을 제외하는 로직은 /components/blog-recap/blog-link-card의 utils.ts 에 넣었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;openapi-generator-cli&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트를 개발하면서 사용했던 라이브러리 중 하나는 openapi-generator-cli이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;open-api-tools는 OAS에 따라 개발된 API 스펙 문서에 따라 클라이언트에서 사용 가능한 타입들을 자동으로 생성해주는 도구이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 사이드 프로젝트에서는 api schema 문서를 open api로 받아볼 수 있었고 이를 이용해 openapi-generator-cli를 사용하여 shema의 타입을 자동 생성할 수 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm init을 통해 만든 프로젝트 폴더에서 아래 명령어를 실행한다&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;npm install @openapitools/openapi-generator-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 package.json에 다음과 같은 스크립트 명령어를 작성한다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;  &quot;scripts&quot;: {
    ...
    &quot;generate&quot;: &quot;openapi-generator-cli generate -i {api문서 주소}/openapi.json -g typescript-axios -o src/api/generated&quot;
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 터미널에 npm run generate를 입력하면 /src/api/generated 폴더 안에 api.ts, base.ts, common.ts 등 자동 생성된 파일들을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 openapi-generator를 이용해 통신함수 구현까지 자동화를 하지는 않았고 자주 변경될 수 있는 schema의 타입을 자동 생성하는 것에 만족하였다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Outro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 개발하면서 이전에 느꼈던 한계점들을 보완하려고 노력했는데 역시 프로젝트 설계와 적절한 도구 사용은 많은 경험이 필요한 일인 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;references.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://xionwcfm.tistory.com/459&quot;&gt;https://xionwcfm.tistory.com/459&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@529539/Nextjs14-TypeScript-Project-Setting-Structure&quot;&gt;https://velog.io/@529539/Nextjs14-TypeScript-Project-Setting-Structure&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://min9nim.vercel.app/2022-04-07-openapi-generator/&quot;&gt;https://min9nim.vercel.app/2022-04-07-openapi-generator/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.stevy.dev/react-design-guide/&quot;&gt;https://www.stevy.dev/react-design-guide/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Blazity/enterprise-commerce&quot;&gt;https://github.com/Blazity/enterprise-commerce&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/SoYoung210/craft&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/SoYoung210/craft&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ps. 혹시 부족한 부분이나 개선점을 발견하셨다면 댓글 부탁드립니다&lt;/p&gt;</description>
      <category>Frontend</category>
      <category>개발후기</category>
      <author>jinlee0310</author>
      <guid isPermaLink="true">https://jin-lee.tistory.com/7</guid>
      <comments>https://jin-lee.tistory.com/7#entry7comment</comments>
      <pubDate>Sun, 5 Jan 2025 22:11:32 +0900</pubDate>
    </item>
    <item>
      <title>사이드 프로젝트: 테블리 개발 후기</title>
      <link>https://jin-lee.tistory.com/6</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;약 3주간의 사이드 프로젝트를 성공적으로 개발한 후기를 적어본다&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;11월 중순쯤 수민님이 사이드 프로젝트 모임을 모집하셨다. 글또 10기 네임드이신 수민님과의 사이드 프로젝트라 같이 해보고 싶기도 하고 글또 운영진이신 은찬님이 백엔드 개발자 역할을 해주신다고 하셔서 더 눈길이 갔다.&lt;br&gt;너무 감사하게도 함께 프로젝트를 진행하고 싶다는 수민님의 러브콜을 받았다! 설레는 마음으로 톡방이 만들어지기를 기다렸다. 함께하는 채은님, 경탁님도 평소에 글또에서 자주 이름을 봤었고 궁금했던 분들이어서 함께할 수 있다는 것이 너무 기뻤다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;킥오프&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;첫 미팅은 온라인으로 진행했다. 수민님께서 해커톤을 위한 개발도 좋지만 해커톤 끝난 뒤에도 계속해서 운영할 수 있는 프로젝트를 해보고 싶다고 말씀하셨다. 수민님의 이 말이 너무 인상적이었고 이 포부 덕분에 해커톤이 끝난 뒤에도 운영과 개발을 하려면 프로젝트를 어떻게 설계하면 좋을 지 계속 고민했던 것 같다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;아이디에이션&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;노션 문서에 각자 만들고 싶은 아이디어들을 적어왔다. 여기서부터는 채은님의 진행이 너무 좋았다. 채은님께서 아이디어를 정할 수 있는 기준점을 먼저 적어와주셨는데 이 부분이 너무 인상적이었다. 채은님께서는 이 이후로도 계속 기준점에 대해 제시해주셨는데 여기서 PM의 존재 의미를 깨닫게 된 것 같다. 채은님의 진행 능력과 문제를 해결하는 과정을 바로 옆에서 보고 배울 수 있게 되어 너무 좋았고 다른 사람의 4~5년간 쌓아온 노하우를 배울 수 있어서 너무 귀중한 시간이었다.&lt;br&gt;채은님의 지휘에 따라 아이디어를 발산하고 다시 좁히고 발산하고 다시 좁히는 과정에서 한사람 한사람의 피드백들이 너무 좋았고 특히 은찬님의 피드백에서 인상적이었던 부분이 많았다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;그리고 개발..&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하면서 개인적으로는 좋았던 점도 많았고 아쉬웠던 점도 많았고 힘든 점도 많았다. 프로젝트를 개발하며 개인적으로 느꼈던 점들을 풀어보려고 한다.&lt;br&gt;우선 좋았던 점은 이전에 업무 하면서 스스로 부족하다고 느꼈던 점들을 많이 보완했다는 생각이 들었다. 폴더구조나 상태관리를 사용하면서, 그리고 인애님과 계속 토의를 하면서 내가 원하는 이상적인 개발을 할 수 있었다. 이전에는 명확하지 않은 폴더관리와 상태관리, 그리고 제대로 정리되지 않고 정의되지 않은 수많은 타입들 때문에 개발을 하는 것이 너무 힘들었던 기억이 있다. 이 부분을 보완하기 위해 폴더 관리와 상태 관리를 설계할 때 최대한 결합도를 낮추고 응집도를 높이는 개발을 하려고 노력했고 그 결과 DX가 너무 좋아졌다고 스스로 느꼈다.&lt;br&gt;그리고 shadcn/ui와 tailwind를 처음 사용해봤는데 개발 경험이 너무 좋았다. 코어가 되는 컴포넌트를 직접 설계 해야 하는 부담이 적었고 클래스 이름을 직접 지어주지 않았던 것이 너무 편했던 것 같다.(이전에는 styled-components를 사용해서 이름을 계속 지어줘야 했다.)&lt;br&gt;그리고 api 통신 기능을 개발할 때 @openapitools/openapi-generator-cli를 사용했는데 이것도 너무 편리했던 것 같다. 백엔드 스펙이 변경될 때 이전에는 하나하나 찾아가며 타입을 작성했는데 이번 프로젝트에서는 라이브러리를 사용해 타입을 자동으로 생성할 수 있었다.&lt;br&gt;아쉬웠던 점은 내가 사용해보고 싶었던 canvas api를 사용해보지 못했다는 점이다. 그래픽스를 조금 공부하면서 canvas를 사용해보고 싶다는 생각을 했었는데 UI나 기능 면에서 canvas가 들어갈만한 부분이 없어서 조금 아쉬웠다. 그리고 애니메이션을 적용해보고 싶었는데 시간적인 부분에서 애니메이션을 적용하기 어려웠던 것 같다. 이부분은 앞으로 프로젝트를 발전시키면서 적용해보고 싶은 욕심이 있다.&lt;br&gt;힘들었던 점은 프로젝트 초기 세팅 부분이었다. 설계를 잘 하고 싶다는 욕심이 있다보니 고민하는 시간이 길어지게 되어 실질적으로 개발하는 시간이 계속 미뤄졌던 것 같다. 이 부분도 앞으로 어떻게 해야 보완할 수 있을 지 고민해봐야 할 것 같다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;10일간의 해커톤의 끝, 그리고 수상! 출시!&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;10일 간 나름대로 여러 고민을 하면서 개발을 마쳤고 마지막 날에는 점심도 안먹고 계속 수정과 배포를 반복했다. 모든 팀원의 노고가 담긴 우리 서비스가 정말 마음에 들었지만 결과는 어떻게 될 지 몰라 기대를 하지는 않았는데 1pick에 선정되어 너무 기뻤다.&lt;br&gt;수상을 하고 나서도 부족하다고 느끼는 부분을 계속 수정하고 목요일에 글또 커뮤니티에도 우리 서비스를 공개했다. 서비스 체험 후기를 댓글에 계속 남겨주셨는데 너무 감사했다. 다른 사람들 통계를 볼 수 있는 부분이나 명언 카드에 긍정적인 피드백을 남겨주셨다. 개인적으로 페이지를 공유 했을 때 나오는 개인 맞춤 페르소나 이미지가 가장 마음에 든다.&lt;br&gt;여기에 다 적지는 못했지만 팀원들 하나하나 너무 감사하고 소중하다. &amp;nbsp;개발 초기에는 생각보다 조금 힘들었는데 눈을 빛내면서 빨리 출시해서 후기 받고 싶다는 팀원들 덕분에 더 열심히 끝까지 개발할 수 있었던 것 같다.&lt;br&gt;마지막으로 우리 팀원들, 정말 고생 많았어요. 감사합니다.☺️&lt;/p&gt;</description>
      <category>일상</category>
      <category>삶의지도</category>
      <category>후기</category>
      <author>jinlee0310</author>
      <guid isPermaLink="true">https://jin-lee.tistory.com/6</guid>
      <comments>https://jin-lee.tistory.com/6#entry6comment</comments>
      <pubDate>Sun, 22 Dec 2024 03:56:34 +0900</pubDate>
    </item>
    <item>
      <title>셰이더 함수 정리</title>
      <link>https://jin-lee.tistory.com/5</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;패스트 캠퍼스에서 수강한 강의 내용 중 셰이더 함수에 대해 정리하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 무작정 따라해볼 수 있도록 glsl에서 사용되는 함수들에 대해 알아볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경은 Vite+VanillaJS환경이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 그라데이션&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 1. 그라데이션
  float x = vUv.x;
  float y = vUv.y;

  float col = x;

  gl_FragColor=vec4(col, col, col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리1.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x5H9b/btsKTx7qbCR/bKBvWdtX3rc09s9gR8NMd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x5H9b/btsKTx7qbCR/bKBvWdtX3rc09s9gR8NMd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x5H9b/btsKTx7qbCR/bKBvWdtX3rc09s9gR8NMd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx5H9b%2FbtsKTx7qbCR%2FbKBvWdtX3rc09s9gR8NMd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;294&quot; height=&quot;273&quot; data-filename=&quot;셰이더함수정리1.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;744&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vUv는 vertex shader에서 가져온 값을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x좌표에 따라 color의 값이 0.0부터 1.0까지 변화한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 대각선 만들기&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 2. 대각선 그라데이션 만들기
  float x = vUv.x;
  float y = vUv.y;

  float gradient = (x + y) / 2.0;
  vec3 col = vec3(gradient);

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리2.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLLGHX/btsKUHBa4AT/Snm6UZfApBhrUbGbWxUhk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLLGHX/btsKUHBa4AT/Snm6UZfApBhrUbGbWxUhk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLLGHX/btsKUHBa4AT/Snm6UZfApBhrUbGbWxUhk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLLGHX%2FbtsKUHBa4AT%2FSnm6UZfApBhrUbGbWxUhk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;366&quot; height=&quot;348&quot; data-filename=&quot;셰이더함수정리2.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 사선 모양의 그라데이션을 만들고 싶다면 위와 같이 코드를 수정해주면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. step&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 3. step
  float x = vUv.x;
  float y = vUv.y;

  float strength = step(0.5, x); // 기준점, 변수
  vec3 col = vec3(strength);

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리3.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOlgTM/btsKUF4q0Bk/WDjiKY6pvb5VCFbt6I6J0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOlgTM/btsKUF4q0Bk/WDjiKY6pvb5VCFbt6I6J0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOlgTM/btsKUF4q0Bk/WDjiKY6pvb5VCFbt6I6J0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOlgTM%2FbtsKUF4q0Bk%2FWDjiKY6pvb5VCFbt6I6J0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;386&quot; data-filename=&quot;셰이더함수정리3.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;step 함수는 첫번째 인자로 기준점을 받고 두번째 인자로 변수를 받는다. 변수가 기준점을 넘지 않으면 0.0을 리턴하고 기준점을 넘으면 1.0을 리턴한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. min, max&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 4. min, max
  float x = vUv.x;
  float y = vUv.y;

  float strength = min(0.5, x);
  // float strength = max(0.5, x);
  vec3 col = vec3(strength);

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript의 Math.min, Math.max 함수와 같은 리턴값을 가진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. min, max&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 4. min, max
  float x = vUv.x;
  float y = vUv.y;

  float strength = min(0.5, x);
  // float strength = max(0.5, x);
  vec3 col = vec3(strength);

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리4_1.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7Qlav/btsKUFXE8mV/20vm1cTJNvy1P2idkKtT8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7Qlav/btsKUFXE8mV/20vm1cTJNvy1P2idkKtT8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7Qlav/btsKUFXE8mV/20vm1cTJNvy1P2idkKtT8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7Qlav%2FbtsKUFXE8mV%2F20vm1cTJNvy1P2idkKtT8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;447&quot; height=&quot;447&quot; data-filename=&quot;셰이더함수정리4_1.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리4.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AbK56/btsKUDSW3E4/lXKtbaYrvcTezKdNlSxf70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AbK56/btsKUDSW3E4/lXKtbaYrvcTezKdNlSxf70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AbK56/btsKUDSW3E4/lXKtbaYrvcTezKdNlSxf70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAbK56%2FbtsKUDSW3E4%2FlXKtbaYrvcTezKdNlSxf70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;432&quot; height=&quot;440&quot; data-filename=&quot;셰이더함수정리4.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript의 Math.min, Math.max 함수와 같은 리턴값을 가진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. clamp&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 5. clamp
  float x = vUv.x;
  float y = vUv.y;

  // 0.3 이하에서는 0.3을 리턴, 0.7 이상에서는 0.7을 리턴, 0.3 ~ 0.7에서는 x를 리턴
  float strength = clamp(x, 0.1, 0.8);
  vec3 col = vec3(strength);

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리5.png&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvVXu8/btsKUxyNP9w/2TNh8o2hEU7bN0ZLP7LKD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvVXu8/btsKUxyNP9w/2TNh8o2hEU7bN0ZLP7LKD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvVXu8/btsKUxyNP9w/2TNh8o2hEU7bN0ZLP7LKD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvVXu8%2FbtsKUxyNP9w%2F2TNh8o2hEU7bN0ZLP7LKD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;464&quot; data-filename=&quot;셰이더함수정리5.png&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clamp 함수는 2개의 기준점을 가진다.x가 min값보다 작으면 min, min과 max 사이라면 x, max보다 크면 max를 리턴한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. smoothstep&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 6. smoothstep
  float x = vUv.x;
  float y = vUv.y;
  
  float strength = smoothstep(0.2, 0.8, x); // 내장 함수

  vec3 col = vec3(strength);

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리6.png&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ekcGvp/btsKUhpqyuZ/DmOWkVMyjUKtDLyK3BtmQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ekcGvp/btsKUhpqyuZ/DmOWkVMyjUKtDLyK3BtmQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ekcGvp/btsKUhpqyuZ/DmOWkVMyjUKtDLyK3BtmQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FekcGvp%2FbtsKUhpqyuZ%2FDmOWkVMyjUKtDLyK3BtmQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;384&quot; height=&quot;391&quot; data-filename=&quot;셰이더함수정리6.png&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;step 함수에서 기준점을 그라데이션 처리하는 함수이다. 기준점의 간격이 늘어날수록 그라데이션 영역도 늘어난다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. mix&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
	// 7. mix
  float x = vUv.x;
  float y = vUv.y;

  vec3 green = vec3(0.0, 1.0, 0.0);
  vec3 blue = vec3(0.0, 0.0, 1.0);
  
  vec3 col = mix(green, blue, x);

  gl_FragColor = vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리7.png&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUHA4y/btsKTq8fBNi/1IqcdbYIraq8mQqcyA9RF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUHA4y/btsKTq8fBNi/1IqcdbYIraq8mQqcyA9RF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUHA4y/btsKTq8fBNi/1IqcdbYIraq8mQqcyA9RF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUHA4y%2FbtsKTq8fBNi%2F1IqcdbYIraq8mQqcyA9RF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;465&quot; height=&quot;465&quot; data-filename=&quot;셰이더함수정리7.png&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mix 함수는 첫번째 인자와 두번째 인자를 세번째 인자의 비율로 보간한 값을 리턴해주는 함수이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. pow, sqrt&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
	// 8. pow, sqrt
  float x = vUv.x;
  float y = vUv.y;

  vec3 col = vec3(x);
  vec3 green=vec3(0.0, 1.0, 0.0);

  if(pow(x, 2.0) - 0.005 &amp;lt;= y &amp;amp;&amp;amp; y &amp;lt;= pow(x, 2.0) + 0.005){
    col = green;
  }
  // if(sqrt(x) - 0.005 &amp;lt;= y &amp;amp;&amp;amp; y &amp;lt;= sqrt(x) + 0.005){
  //   col = green;
  // }

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리8_1.png&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNoMlt/btsKTqUF8id/6BbjZ5BDr9lRISpCttHKd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNoMlt/btsKTqUF8id/6BbjZ5BDr9lRISpCttHKd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNoMlt/btsKTqUF8id/6BbjZ5BDr9lRISpCttHKd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNoMlt%2FbtsKTqUF8id%2F6BbjZ5BDr9lRISpCttHKd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;370&quot; height=&quot;391&quot; data-filename=&quot;셰이더함수정리8_1.png&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리8_2.png&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bde1zR/btsKVt3vNJD/oYkJb2So3EqIpKut695g3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bde1zR/btsKVt3vNJD/oYkJb2So3EqIpKut695g3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bde1zR/btsKVt3vNJD/oYkJb2So3EqIpKut695g3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbde1zR%2FbtsKVt3vNJD%2FoYkJb2So3EqIpKut695g3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;339&quot; height=&quot;341&quot; data-filename=&quot;셰이더함수정리8_2.png&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pow와 sqrt함수 역시 JavaScript의 Math.pow, Math.sqrt와 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9. mod&lt;/h3&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 9. mod: (a % b)
  float x = vUv.x;
  float y = vUv.y;

  vec3 col = vec3(mod(x * 2.0, 1.0));

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리9.png&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lW4FP/btsKUi2Bi51/rx4SAKaJBvsBQp5BpPjVBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lW4FP/btsKUi2Bi51/rx4SAKaJBvsBQp5BpPjVBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lW4FP/btsKUi2Bi51/rx4SAKaJBvsBQp5BpPjVBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlW4FP%2FbtsKUi2Bi51%2Frx4SAKaJBvsBQp5BpPjVBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;373&quot; height=&quot;382&quot; data-filename=&quot;셰이더함수정리9.png&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mod함수는 나머지를 반환해주는 함수이다. 첫번째 인자로 들어온 값을 두번째 인자로 나눈 나머지를 반환한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10. fract&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소수점 이하의 값을 반환해주는 함수이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 mod 함수는 아래 코드로도 대체할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  float x = vUv.x;
  float y = vUv.y;

  vec3 col = vec3(fract(x * 2.0));

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리10_1.png&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMILCD/btsKVxkm7ac/whi0Av2wjYNz1zugyWKRTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMILCD/btsKVxkm7ac/whi0Av2wjYNz1zugyWKRTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMILCD/btsKVxkm7ac/whi0Av2wjYNz1zugyWKRTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMILCD%2FbtsKVxkm7ac%2Fwhi0Av2wjYNz1zugyWKRTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;446&quot; data-filename=&quot;셰이더함수정리10_1.png&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 아래 코드를 작성하면 이러한 효과를 만들 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // . fract: 소수점 이하의 값을 반환
	float x = vUv.x;
  float y = vUv.y;

  // vec3 col = vec3(fract(x * y));
  // col = step(0.5, col);

  vec3 col = vec3(fract((x - 0.11) * 7.0));
  vec3 col2 = vec3(fract((y - 0.11) * 7.0));

  col = step(0.5, col) * step(0.5, col2);

  fract(0.4); // 0.4
  fract(2.3); // 0.3

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리10_2.png&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfJ6Yn/btsKTCN5C6x/BkcsnOlN2GDo9uXlm1qwK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfJ6Yn/btsKTCN5C6x/BkcsnOlN2GDo9uXlm1qwK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfJ6Yn/btsKTCN5C6x/BkcsnOlN2GDo9uXlm1qwK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfJ6Yn%2FbtsKTCN5C6x%2FBkcsnOlN2GDo9uXlm1qwK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;445&quot; data-filename=&quot;셰이더함수정리10_2.png&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;11. sin, cos&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sin 함수와 cos 함수는 그래픽스에서 자주 사용되는 함수이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 코드를 작성한다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 11. sin, cos
  float x = vUv.x;
  float y = vUv.y;

  vec3 col = vec3(sin(x * 1.0));
  // vec3 col = vec3(cos(x * 20.0));

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.desmos.com/calculator/ifg3mwmqun?lang=ko&quot;&gt;desmos&lt;/a&gt; 사이트에 가면 수학 그래프들을 그려볼 수 있다. 0과 1 사이에서 그래프를 따라 위와 같은 분포를 가진다고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 그라데이션이 검은색부터 흰색까지 진행되다가 다시 검은색으로 돌아오는 그림을 그리고 싶다면 아래와 같이 코드를 작성하면 된다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 11. sin, cos
  float x = vUv.x;
  float y = vUv.y;

  vec3 col = vec3(sin(x * 3.0));
  // vec3 col = vec3(cos(x * 20.0));

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 원리를 사용해서 코드를 아래와 같이 작성하면 다음과 같은 그림을 만들 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;11. sin, cos&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sin 함수와 cos 함수는 그래픽스에서 자주 사용되는 함수이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 코드를 작성한다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 11. sin, cos
  float x = vUv.x;
  float y = vUv.y;

  vec3 col = vec3(sin(x * 1.0));
  // vec3 col = vec3(cos(x * 20.0));

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리11_1.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VnUJr/btsKVJx8m4z/LfoQU6n73llSeGj6C2H4KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VnUJr/btsKVJx8m4z/LfoQU6n73llSeGj6C2H4KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VnUJr/btsKVJx8m4z/LfoQU6n73llSeGj6C2H4KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVnUJr%2FbtsKVJx8m4z%2FLfoQU6n73llSeGj6C2H4KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;425&quot; height=&quot;424&quot; data-filename=&quot;셰이더함수정리11_1.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리11_2.png&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9Iqgv/btsKTCHlTwd/60TmhetJU45wkNKuT3vMxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9Iqgv/btsKTCHlTwd/60TmhetJU45wkNKuT3vMxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9Iqgv/btsKTCHlTwd/60TmhetJU45wkNKuT3vMxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9Iqgv%2FbtsKTCHlTwd%2F60TmhetJU45wkNKuT3vMxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;497&quot; data-filename=&quot;셰이더함수정리11_2.png&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.desmos.com/calculator/ifg3mwmqun?lang=ko&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;desmos&lt;/span&gt;&lt;/a&gt; 사이트에 가면 수학 그래프들을 그려볼 수 있다. 0과 1 사이에서 그래프를 따라 위와 같은 분포를 가진다고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 그라데이션이 검은색부터 흰색까지 진행되다가 다시 검은색으로 돌아오는 그림을 그리고 싶다면 아래와 같이 코드를 작성하면 된다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 11. sin, cos
  float x = vUv.x;
  float y = vUv.y;

  vec3 col = vec3(sin(x * 3.0));
  // vec3 col = vec3(cos(x * 20.0));

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리11_3.png&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bozDRd/btsKVQDVDl3/n0keGjHRVygkNKExKoEY30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bozDRd/btsKVQDVDl3/n0keGjHRVygkNKExKoEY30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bozDRd/btsKVQDVDl3/n0keGjHRVygkNKExKoEY30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbozDRd%2FbtsKVQDVDl3%2Fn0keGjHRVygkNKExKoEY30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;387&quot; height=&quot;390&quot; data-filename=&quot;셰이더함수정리11_3.png&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리11_4.png&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dePTIB/btsKUjtCSw3/bd8PLu16ZsMqYxu9euX7lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dePTIB/btsKUjtCSw3/bd8PLu16ZsMqYxu9euX7lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dePTIB/btsKUjtCSw3/bd8PLu16ZsMqYxu9euX7lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdePTIB%2FbtsKUjtCSw3%2Fbd8PLu16ZsMqYxu9euX7lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;376&quot; height=&quot;372&quot; data-filename=&quot;셰이더함수정리11_4.png&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 원리를 사용해서 코드를 아래와 같이 작성하면 다음과 같은 그림을 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 11. sin, cos
  float x = vUv.x;
  float y = vUv.y;

  vec3 col = vec3(sin(x * 20.0));
  // vec3 col = vec3(cos(x * 20.0));

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리11_5.png&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ogIaG/btsKVbB1G4Y/UXyWNnvxdnNeDNQfwy8nRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ogIaG/btsKVbB1G4Y/UXyWNnvxdnNeDNQfwy8nRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ogIaG/btsKVbB1G4Y/UXyWNnvxdnNeDNQfwy8nRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FogIaG%2FbtsKVbB1G4Y%2FUXyWNnvxdnNeDNQfwy8nRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;423&quot; data-filename=&quot;셰이더함수정리11_5.png&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리11_6.png&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwm3Ek/btsKU8FbHr0/uD9J2glAAjuK6MuoJlep7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwm3Ek/btsKU8FbHr0/uD9J2glAAjuK6MuoJlep7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwm3Ek/btsKU8FbHr0/uD9J2glAAjuK6MuoJlep7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwm3Ek%2FbtsKU8FbHr0%2FuD9J2glAAjuK6MuoJlep7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;414&quot; data-filename=&quot;셰이더함수정리11_6.png&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;12. abs&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;절대값을 반환해주는 함수이다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 12. abs
  float x = vUv.x;
  float y = vUv.y;

  vec3 col = vec3(abs(cos(x * 20.0)));

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리12_1.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bje6U4/btsKTCN5GdU/qxBRvt9iyM52NHOss0h5wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bje6U4/btsKTCN5GdU/qxBRvt9iyM52NHOss0h5wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bje6U4/btsKTCN5GdU/qxBRvt9iyM52NHOss0h5wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbje6U4%2FbtsKTCN5GdU%2FqxBRvt9iyM52NHOss0h5wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;293&quot; height=&quot;297&quot; data-filename=&quot;셰이더함수정리12_1.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리12_2.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deNBB5/btsKTESB4SA/vNRXNkgJ5dKtx5IUmqxaK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deNBB5/btsKTESB4SA/vNRXNkgJ5dKtx5IUmqxaK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deNBB5/btsKTESB4SA/vNRXNkgJ5dKtx5IUmqxaK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeNBB5%2FbtsKTESB4SA%2FvNRXNkgJ5dKtx5IUmqxaK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;367&quot; height=&quot;361&quot; data-filename=&quot;셰이더함수정리12_2.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;13. distance, length&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 정점 사이의 거리(길이)를 반환해주는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 코드를 작성해보자&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 13. distance: 두 정점 사이의 거리
  float x = vUv.x;
  float y = vUv.y;

  float dist = distance(x, 0.5);

  vec3 col = vec3(dist);

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리13_1.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnZ32f/btsKUCmcUXg/sMNRi57eSxuWl9zhMf2sS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnZ32f/btsKUCmcUXg/sMNRi57eSxuWl9zhMf2sS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnZ32f/btsKUCmcUXg/sMNRi57eSxuWl9zhMf2sS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnZ32f%2FbtsKUCmcUXg%2FsMNRi57eSxuWl9zhMf2sS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;308&quot; height=&quot;318&quot; data-filename=&quot;셰이더함수정리13_1.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.5에서부터 x위치까지의 거리를 반환해주어서 0.5와 가까울수록 어두운 색이고 멀어질수록 0.5와 가까운 색을 나타낸다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 13. distance: 두 정점 사이의 거리
  float x = vUv.x;
  float y = vUv.y;

  float dist = distance(x, 0.5);
  dist = step(0.3, dist);

  vec3 col = vec3(dist);

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리13_2.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dz4kk4/btsKVR3UXXy/hPP4xRBDAs3s942YKLhh5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dz4kk4/btsKVR3UXXy/hPP4xRBDAs3s942YKLhh5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dz4kk4/btsKVR3UXXy/hPP4xRBDAs3s942YKLhh5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdz4kk4%2FbtsKVR3UXXy%2FhPP4xRBDAs3s942YKLhh5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;354&quot; height=&quot;360&quot; data-filename=&quot;셰이더함수정리13_2.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수에 step 함수를 사용하면 0.3을 기준으로 0과 1을 반환하는 그림을 그릴 수 있다.&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;precision mediump float;

varying vec2 vUv;

void main(){
  // 13. distance: 두 정점 사이의 거리
  float x = vUv.x;
  float y = vUv.y;

  // float dist = distance(x, 0.5);
  float dist = distance(vec2(x, y), vec2(0.5));
  dist = step(0.3, dist);

  vec3 col = vec3(dist);

  gl_FragColor=vec4(col, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셰이더함수정리13_3.png&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1sDJG/btsKT7fXl0N/QF4vjtewnujVhAkjPi7i70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1sDJG/btsKT7fXl0N/QF4vjtewnujVhAkjPi7i70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1sDJG/btsKT7fXl0N/QF4vjtewnujVhAkjPi7i70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1sDJG%2FbtsKT7fXl0N%2FQF4vjtewnujVhAkjPi7i70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;363&quot; data-filename=&quot;셰이더함수정리13_3.png&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의에 나온 함수들을 설명한 것이고 더 자세히는 &lt;a href=&quot;https://thebookofshaders.com/glossary/&quot;&gt;The Book of Shaders&lt;/a&gt;에 가면 더 많은 함수들을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 함수들만 쭉 정리해본 이유는 이 &lt;a href=&quot;https://tympanus.net/Tutorials/SDFLensBlur/&quot;&gt;데모&lt;/a&gt;의 코드를 분석해보고 구현해보기 위해서이다. 다음 글에서는 해당 데모의 코드를 분석해보고 직접 구현해보겠다.&lt;/p&gt;</description>
      <category>Graphics</category>
      <category>shader #graphics</category>
      <author>jinlee0310</author>
      <guid isPermaLink="true">https://jin-lee.tistory.com/5</guid>
      <comments>https://jin-lee.tistory.com/5#entry5comment</comments>
      <pubDate>Sun, 24 Nov 2024 23:30:42 +0900</pubDate>
    </item>
    <item>
      <title>Scramble Text Animation</title>
      <link>https://jin-lee.tistory.com/4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;전부터 구현해 보고 싶었던 scramble text effect를 구현해 보았다.&lt;br /&gt;코드 레퍼런스: &lt;a href=&quot;https://codepen.io/soulwire/pen/mEMPrK&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://codepen.io/soulwire/pen/mEMPrK&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Scramble Text Effect&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-11-099.55.33-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zVpbK/btsKFMaKyqo/7PJyfNOmi7FKwU9PgpsWg0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zVpbK/btsKFMaKyqo/7PJyfNOmi7FKwU9PgpsWg0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zVpbK/btsKFMaKyqo/7PJyfNOmi7FKwU9PgpsWg0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/zVpbK/btsKFMaKyqo/7PJyfNOmi7FKwU9PgpsWg0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;292&quot; data-filename=&quot;2024-11-099.55.33-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이와 같이 텍스트가 임의의 문자로 변환되다가 점점 올바른 텍스트로 변해가는 애니메이션 효과를 말한다. 일반적으로 랜덤하게 문자들이 바뀌다가 올바른 텍스트가 서서히 자리 잡는 방식으로 구현된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 구현 코드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 1차 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 구현 아이디어는 다음과 같다.(chatGPT의 도움을 받았다.)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;텍스트를 배열에 넣고&lt;/li&gt;
&lt;li&gt;랜덤 index의 텍스트를 characters의 랜덤한 글자와 교체한다.&lt;/li&gt;
&lt;li&gt;믹스된 텍스트를 렌더링한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export default function ScrambleText() {
  const texts = useMemo(() =&amp;gt; { // text 배열 선언
    return [
      &quot;Neo,&quot;,
      &quot; Type messages everywhere!&quot;,
      &quot;Add a bio to your profile!&quot;,
      &quot;Add a description to your repo!&quot;,
      &quot;Make your readme stand out!&quot;,
      &quot;Unstyled, Composable Random Text Animation React Component&quot;,
    ];
  }, []);

  const duration = useMemo(() =&amp;gt; 1500, []); // 각 글자의 스크램블 지속 시간 (1초)
  const pauseDuration = useMemo(() =&amp;gt; 2000, []); // 각 글자 사이 멈춤 시간

  const [displayText, setDisplayText] = useState&amp;lt;string&amp;gt;(&quot;&quot;);
  const [textIndex, setTextIndex] = useState&amp;lt;number&amp;gt;(0); // 현재 보여줄 텍스트 인덱스

  const characters = &quot;!&amp;lt;&amp;gt;-_\\\\/[]{}&amp;mdash;=+*^?#________&quot;;

  // 랜덤한 순서로 인덱스를 확정하기 위한 배열 생성
  const shuffledIndices = useMemo(() =&amp;gt; {
    const indices = [...Array(texts[textIndex].length).keys()];
    for (let i = indices.length - 1; i &amp;gt; 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [indices[i], indices[j]] = [indices[j], indices[i]];
    }
    return indices;
  }, [textIndex, texts]);

  useEffect(() =&amp;gt; {
    let iterations = texts[textIndex].length;
    const confirmedChars: boolean[] = new Array(texts[textIndex].length).fill(
      false,
    );

    const scramble = () =&amp;gt; {
      const scrambledText = texts[textIndex]
        .split(&quot;&quot;)
        .map((_, index) =&amp;gt;
          confirmedChars[index]
            ? texts[textIndex][index]
            : characters[Math.floor(Math.random() * characters.length)],
        )
        .join(&quot;&quot;);

      const currentIndex =
        shuffledIndices[texts[textIndex].length - iterations];
      confirmedChars[currentIndex] = true;
      confirmedChars[currentIndex + 1] = true;

      setDisplayText(scrambledText);

      iterations -= 2;

      if (iterations &amp;lt;= 0) {
        clearInterval(intervalId);
        setDisplayText(texts[textIndex]); // 최종 텍스트 설정
        setTimeout(() =&amp;gt; {
          setTextIndex((prev) =&amp;gt; (prev + 1) % texts.length);
          iterations = texts[(textIndex + 1) % texts.length].length;
        }, pauseDuration);
      }
    };

    const intervalId = setInterval(scramble, 100); // 0.1초마다 scramble 함수 실행

    return () =&amp;gt; clearInterval(intervalId); // 컴포넌트 언마운트 시 interval 제거
  }, [duration, shuffledIndices, texts, textIndex, pauseDuration]);

  return &amp;lt;Text&amp;gt;{displayText}&amp;lt;/Text&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이렇게 하면 효과는 구현할 수 있지만 아래처럼 글자와 글자 사이의 전환이 뚝뚝 끊기면서 이루어지게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-11-099.45.31-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biYQ6F/btsKEbXoJsU/zRqmxAZwkufLVHEbpiqsT1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biYQ6F/btsKEbXoJsU/zRqmxAZwkufLVHEbpiqsT1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biYQ6F/btsKEbXoJsU/zRqmxAZwkufLVHEbpiqsT1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/biYQ6F/btsKEbXoJsU/zRqmxAZwkufLVHEbpiqsT1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;292&quot; data-filename=&quot;2024-11-099.45.31-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 부분이 마음에 들지 않아 레퍼런스 코드를 자세히 뜯어봤다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 2차 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트의 길이가 점점 늘어나거나 점점 줄어드는 듯한 효과를 어떻게 구현하나 봤는데 queue 배열을 선언하여 구현했다. queue의 길이는 현재 텍스트와 다음 텍스트의 길이 중 긴 것을 선택하고 from과 to에 현재 텍스트와 다음 텍스트의 글자를 하나씩 넣는다. 부족한 텍스트는 &amp;ldquo;&amp;rdquo;로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iteration을 queue의 길이만큼 반복하도록 로직을 구현했다.&lt;br /&gt;그리고 랜덤한 숫자를 가진 start와 end를 queue에 추가하고 iteration 횟수에 따라 start 이하일 때 현재 텍스트, start 이상일 때 랜덤 텍스트, end 이상일 때 다음 텍스트를 렌더하는 방식으로 텍스트를 믹스했다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;const PHASES = [
  &quot;Neo,&quot;,
  &quot;sooner or later&quot;,
  &quot;you're going to realize&quot;,
  &quot;just as I did&quot;,
  &quot;that there's a difference&quot;,
  &quot;between knowing the path&quot;,
  &quot;and walking the path&quot;,
  &quot;Unstyled, Composable Random Text Animation React Component&quot;,
];

const ScrambleText = () =&amp;gt; {
  const interval = 100;
  const pauseDuration = 2000;
  const characters = &quot;!&amp;lt;&amp;gt;-_\\\\/[]{}&amp;mdash;=+*^?#________&quot;;

  const [displayText, setDisplayText] = useState&amp;lt;(string | ReactNode)[]&amp;gt;([]);
  const [textIndex, setTextIndex] = useState&amp;lt;number&amp;gt;(0);
  const [queue, setQueue] = useState&amp;lt;
    { from: string; to: string; start: number; end: number }[]
  &amp;gt;([]);

  const text = PHASES[textIndex];

  const initializeQueue = useCallback(() =&amp;gt; {
    const nextText = PHASES[(textIndex + 1) % PHASES.length];
    const length = Math.max(text.length, nextText.length);
    const newQueue = Array.from({ length }, (_, i) =&amp;gt; {
      const from = text[i] || &quot;&quot;;
      const to = nextText[i] || &quot;&quot;;
      const start = Math.floor(
        (Math.random() * (text.length + nextText.length)) / 2,
      );
      const end =
        start +
        Math.floor((Math.random() * (text.length + nextText.length)) / 2);
      return { from, to, start, end };
    });

    setQueue(newQueue);
  }, [text, textIndex]);

  useEffect(() =&amp;gt; {
    let iterations = queue.length;

    const scramble = () =&amp;gt; {
      const scrambledText = queue.map(({ from, to, start, end }) =&amp;gt; {
        if (queue.length - iterations &amp;gt;= end) {
          return to;
        } else if (queue.length - iterations &amp;gt;= start) {
          const char =
            characters[Math.floor(Math.random() * characters.length)];
          return (
            &amp;lt;span className=&quot;dud&quot;&amp;gt;
              {char}
            &amp;lt;/span&amp;gt;
          );
        } else {
          return from;
        }
      });

      setDisplayText(scrambledText);
      iterations -= 1;

      if (iterations &amp;lt;= 0) {
        clearInterval(intervalId);
        setDisplayText(text.split(&quot;&quot;));
        setTimeout(() =&amp;gt; {
          initializeQueue();
          setTextIndex((prev) =&amp;gt; (prev + 1) % PHASES.length);
        }, pauseDuration);
      }
    };

    const intervalId = setInterval(scramble, interval);
    return () =&amp;gt; clearInterval(intervalId);
  }, [text, queue, initializeQueue, pauseDuration]);

  return &amp;lt;Text&amp;gt;{displayText}&amp;lt;/Text&amp;gt;;
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드에는 생략했지만 참고로 리액트의 diffing&amp;nbsp;algorithm 때문에 span의 key를 중복 없이 설정 해줘야 한다. key가 같으면 리액트가 이전에 렌더된 랜덤 텍스트를 다음 텍스트로 교체하지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Interval 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레퍼런스에서는 requestAnimationFrame 함수와 frame이라는 변수를 사용해 스크램블 하는 시간을 텍스트 길이에 상관 없이 거의 동일하게 처리했다.&lt;br /&gt;하지만 나는 인터벌 시간을 0.1sec로 설정하는 것이 보기에 가장 좋아서 길이가 길다면 스크램블 되는 시간을 늘리는 애니메이션을 선택했다.(이부분은 취향 차이인 것 같다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. css 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css는 레퍼런스와 동일한 색상으로 처리했다. 랜덤한 텍스트를 어두운 색상으로 설정하면 좀 더 극적인 scramble text 효과를 볼 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 텍스트 생성 효과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;scramble text effect는 여러개의 텍스트들을 전환해도 되지만 아래처럼 아무것도 없는 상태에서 스크램블 되면서 텍스트가 나타나는 효과도 만들어볼 수도 있다. PHASES 안에 &amp;ldquo;&amp;rdquo;를 추가하면 아래와 같은 효과를 만들어볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-11-118.29.02-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxYRzW/btsKFigZxP8/VQJgEXRXhsWqcViEBadxPK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxYRzW/btsKFigZxP8/VQJgEXRXhsWqcViEBadxPK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxYRzW/btsKFigZxP8/VQJgEXRXhsWqcViEBadxPK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dxYRzW/btsKFigZxP8/VQJgEXRXhsWqcViEBadxPK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;292&quot; data-filename=&quot;2024-11-118.29.02-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. 후기&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 레퍼런스 코드 복붙해도 되긴 할텐데 리액트에 맞게 코드를 수정하느라 구현에 좀 시간이 걸렸다. 그리고 chatGPT에게 리액트에 맞게 바꿔달라고 20번은 물어봤는데 레퍼런스와 비슷한 느낌이 전혀 나지 않아서 결국 하나하나 뜯어보며 코드를 작성해야 했다ㅋㅋ..&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ps. 혹시 부족한 부분이나 개선점을 발견하셨다면 댓글 부탁드립니다&lt;/p&gt;</description>
      <category>Web Animation</category>
      <category>webanimation</category>
      <author>jinlee0310</author>
      <guid isPermaLink="true">https://jin-lee.tistory.com/4</guid>
      <comments>https://jin-lee.tistory.com/4#entry4comment</comments>
      <pubDate>Sat, 9 Nov 2024 21:50:19 +0900</pubDate>
    </item>
    <item>
      <title>글또 10기 1주일 활동 후기</title>
      <link>https://jin-lee.tistory.com/2</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 글또 참여 계기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;글또에 참여한 계기에는 우연이 많았다.&lt;br&gt;주변 지인들이 추천해준 것은 아니고 깃헙에서 아주 예전에 팔로우 드린 분께서(그분은 아마 나를 모를 것이다) 글또 레포지토리에 속해 있었고 랠릿에서 본 인상 깊은 이력서의 주인분이 글또 활동을 하신 적이 있다고 해서 나도 참여해보고 싶었다.&lt;br&gt;하지만 딱히 글또 모집에 관심을 갖고 찾아본 것은 아닌데 그동안 관심을 갖고 찾아본 동아리나 모임 등은 거의 탈락했기 때문이다.(ㅠㅠ)&lt;br&gt;그리고 사실 나는 글을 쓰는 것과는 거리가 멀었다. 어릴 때는 백일장에서 상도 받았었지만 고등학교 때 이과에 가면서 상대적으로 중요성이 떨어지는 국어와 글과 책과는 이별을 하게 되었다.(덕분에 대입 자소서 한 장 써본 적이 없다.)&lt;br&gt;그러다 우연히 글또 10기 모집 글을 보게 되었는데 삶의 지도조차 마감날에 작성했음에도 붙은 것을 보면 인연인 것일까. 아무튼 감사하게도 글또에 합격하여 활동을 시작하게 되었다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 글또 활동 후기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;정말 많은 사람들의 자기소개를 읽고, 인사를 나누고, 또 모르는 사람에게도 말을 걸어보면서 글또에 소속감을 느끼게 되었다. 그리고 모르는 사람임에도 이모지를 30개나 달아주신 분들께 정말 감사했다.&lt;br&gt;OT가 끝나고 여러 소모임에 들어갔다. 다진마늘, 글읽었또, 밈또, 감사회고해또 등등.. 특히 OT때 언급이 있었던 다진마늘이 가장 궁금했다. 활동을 시작한 지 1주일 정도 지난 지금은 다진마늘 운영진(?)에 들어가있다ㅎㅎ(커피챗도 예정되어있다.) 1주일만에 승진(?)을 한 것 같은 느낌이다.&lt;br&gt;종이비행기도 날려보고 대나무숲에 고민도 써보았다. 특히 너무 힘든 날 대숲에 고민을 적었는데 많은 분들이 응원의 이모지와 댓글을 남겨 주셔서 너무 감사했다. 나도 꼭 나중에 사람들에게 힘이 되는 서비스를 운영해보고 싶어졌다.&lt;br&gt;글을 쓰려면 책을 좀 읽어야 할 것 같아 책읽어또에 들어갔고 그래픽스에 대한 공부도 계속 하고 싶어 쓰리제이에스해또에 들어갔다. 천천히 활동 영역을 넓혀 보면서 앞으로의 나의 진로도 같이 고민 할 예정이다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 다짐&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우선 미약하더라도 6개월을 꾸준히 활동해보는 것이 목표이다. 굵직한 활동을 하기에는 너무 대단한 분들이 많으셔서 내가 그런 활동을 할 수 있을지는 잘 모르겠지만 6개월 이상을 꾸준히 해본 것이 많지 않기에, 이번에 그 목록에 글또 활동을 넣어보려고 한다.&lt;br&gt;그리고 글 쓰는 것과 좀 더 친해질 수 있을 것 같다. 지금까지는 나를 표현하는 것에 너무 소홀했었다. 왠지 생색 내는 것 같기도 하고, 부끄럽기도 하고, 그리고 잘하는 엔지니어라면 굳이 표현하지 않아도 주변 사람이 다 알 것이라는 혼자만의 착각에 빠져 있었던 것 같다. 이번 활동을 통해 나 자신을 잘 표현하는 방법을 익히고 싶다.&lt;/p&gt;</description>
      <category>일상</category>
      <category>삶의지도</category>
      <author>jinlee0310</author>
      <guid isPermaLink="true">https://jin-lee.tistory.com/2</guid>
      <comments>https://jin-lee.tistory.com/2#entry2comment</comments>
      <pubDate>Thu, 10 Oct 2024 02:08:28 +0900</pubDate>
    </item>
  </channel>
</rss>