폴리머로 광선검 만들기

광선검 스크린샷

요약

Polymer를 사용해 모듈식 및 구성 가능한 고성능 WebGL 모바일 제어 라이트세이버를 만든 방법 Google은 https://lightsaber.withgoogle.com/ 프로젝트의 몇 가지 주요 세부정보를 검토하여 다음에 성난 스톰트루퍼를 만날 때 시간을 절약할 수 있도록 지원합니다.

개요

어떤 Polymer 또는 WebComponents가 있는지 궁금하다면 실제 작업 프로젝트의 발췌문을 공유하는 것으로 시작하는 것이 가장 좋습니다. 다음은 프로젝트 https://lightsaber.withgoogle.com의 방문 페이지에서 가져온 샘플입니다. 일반 HTML 파일이지만 몇 가지 놀라운 기능이 포함되어 있습니다.

<!-- Element-->
<dom-module id="sw-page-landing">
    <!-- Template-->
    <template>
    <style>
        <!-- include elements/sw/pages/sw-page-landing/styles/sw-page-landing.css-->
    </style>
    <div class="centered content">
        <sw-ui-logo></sw-ui-logo>
        <div class="connection-url-wrapper">
        <sw-t key="landing.type" class="type"></sw-t>
        <div id="url" class="connection-url">.</div>
        <sw-ui-toast></sw-ui-toast>
        </div>
    </div>
    <div class="disclaimer epilepsy">
        <sw-t key="disclaimer.epilepsy" class="type"></sw-t>
    </div>
    <sw-ui-footer state="extended"></sw-ui-footer>
    </template>
    <!-- Polymer element script-->
    <script src="scripts/sw-page-landing.js"></script>
</dom-module>

따라서 요즘에는 HTML5 기반 애플리케이션을 만들 때 선택할 수 있는 방법이 많습니다. API, 프레임워크, 라이브러리, 게임 엔진 등 여러 가지 옵션을 선택할 수 있지만, 그래픽의 고성능 제어와 깔끔한 모듈식 구조 및 확장성 사이에서 적절한 조합을 갖추기란 쉽지 않습니다. 그리고 Polymer가 프로젝트를 체계적으로 유지하는 동시에 하위 수준의 성능 최적화를 허용할 수 있다는 것을 알게 되었고 Polymer의 기능을 최대한 활용할 수 있도록 프로젝트를 구성요소로 세분화하는 방법을 신중하게 설계했습니다.

폴리머를 사용한 모듈성

Polymer는 재사용 가능한 맞춤 요소에서 프로젝트를 빌드하는 방법을 상당 부분 제어할 수 있는 라이브러리입니다. 이 라이브러리를 사용하면 단일 HTML 파일에 포함된 독립형이고 모든 기능을 갖춘 모듈을 사용할 수 있습니다. 이러한 형식에는 구조 (HTML 마크업)뿐만 아니라 인라인 스타일과 로직도 포함됩니다.

아래 예를 참고하시기 바랍니다.

<link rel="import" href="bower_components/polymer/polymer.html">

<dom-module id="picture-frame">
    <template>
    <!-- scoped CSS for this element -->
    <style>
        div {
        display: inline-block;
        background-color: #ccc;
        border-radius: 8px;
        padding: 4px;
        }
    </style>
    <div>
        <!-- any children are rendered here -->
        <content></content>
    </div>
    </template>

    <script>
    Polymer({
        is: "picture-frame",
    });
    </script>
</dom-module>

하지만 대규모 프로젝트에서는 이 세 가지 논리적 구성요소 (HTML, CSS, JS)를 분리하고 컴파일 시에만 병합하는 것이 유용할 수도 있습니다. 따라서 프로젝트의 각 요소에 별도의 폴더를 부여했습니다.

src/elements/
|-- elements.jade
`-- sw
    |-- debug
    |   |-- sw-debug
    |   |-- sw-debug-performance
    |   |-- sw-debug-version
    |   `-- sw-debug-webgl
    |-- experience
    |   |-- effects
    |   |-- sw-experience
    |   |-- sw-experience-controller
    |   |-- sw-experience-engine
    |   |-- sw-experience-input
    |   |-- sw-experience-model
    |   |-- sw-experience-postprocessor
    |   |-- sw-experience-renderer
    |   |-- sw-experience-state
    |   `-- sw-timer
    |-- input
    |   |-- sw-input-keyboard
    |   `-- sw-input-remote
    |-- pages
    |   |-- sw-page-calibration
    |   |-- sw-page-connection
    |   |-- sw-page-connection-error
    |   |-- sw-page-error
    |   |-- sw-page-experience
    |   `-- sw-page-landing
    |-- sw-app
    |   |-- bower.json
    |   |-- scripts
    |   |-- styles
    |   `-- sw-app.jade
    |-- system
    |   |-- sw-routing
    |   |-- sw-system
    |   |-- sw-system-audio
    |   |-- sw-system-config
    |   |-- sw-system-environment
    |   |-- sw-system-events
    |   |-- sw-system-remote
    |   |-- sw-system-social
    |   |-- sw-system-tracking
    |   |-- sw-system-version
    |   |-- sw-system-webrtc
    |   `-- sw-system-websocket
    |-- ui
    |   |-- experience
    |   |-- sw-preloader
    |   |-- sw-sound
    |   |-- sw-ui-button
    |   |-- sw-ui-calibration
    |   |-- sw-ui-disconnected
    |   |-- sw-ui-final
    |   |-- sw-ui-footer
    |   |-- sw-ui-help
    |   |-- sw-ui-language
    |   |-- sw-ui-logo
    |   |-- sw-ui-mask
    |   |-- sw-ui-menu
    |   |-- sw-ui-overlay
    |   |-- sw-ui-quality
    |   |-- sw-ui-select
    |   |-- sw-ui-toast
    |   |-- sw-ui-toggle-screen
    |   `-- sw-ui-volume
    `-- utils
        `-- sw-t

또한 각 요소의 폴더는 로직 (커피 파일), 스타일 (scss 파일), 템플릿 (옥 파일)에 대한 별도의 디렉터리와 파일이 있는 동일한 내부 구조를 갖습니다.

다음은 sw-ui-logo 요소의 예입니다.

sw-ui-logo/
|-- bower.json
|-- scripts
|   `-- sw-ui-logo.coffee
|-- styles
|   `-- sw-ui-logo.scss
`-- sw-ui-logo.jade

.jade 파일을 살펴보면 다음과 같습니다.

// Element
dom-module(id='sw-ui-logo')

    // Template
    template
    style
        include elements/sw/ui/sw-ui-logo/styles/sw-ui-logo.css

    img(src='[[url]]')

    // Polymer element script
    script(src='scripts/sw-ui-logo.js')

별도의 파일의 스타일과 로직을 포함하면 항목이 어떻게 정리되는지 확인할 수 있습니다. Polymer 요소에 스타일을 포함하기 위해 Jade의 include 문을 사용하므로 컴파일 후 실제 인라인 CSS 파일 콘텐츠가 생성됩니다. sw-ui-logo.js 스크립트 요소는 런타임에 실행됩니다.

Bower를 사용한 모듈식 종속 항목

일반적으로 라이브러리 및 기타 종속 항목은 프로젝트 수준에서 유지됩니다. 그러나 위 설정에서 요소의 폴더인 요소 수준 종속 항목에 있는 bower.json을 확인할 수 있습니다. 이 접근 방식의 핵심은 종속 항목이 다른 요소가 많은 상황에서 실제로 사용되는 종속 항목만 로드하도록 할 수 있다는 것입니다. 요소를 삭제해도 종속 항목을 삭제할 필요가 없습니다. 이러한 종속 항목을 선언하는 bower.json 파일도 삭제되기 때문입니다. 각 요소는 관련된 종속 항목을 독립적으로 로드합니다.

그러나 종속 항목의 중복을 피하기 위해 각 요소의 폴더에도 .bowerrc 파일을 포함합니다. 이렇게 하면 종속 항목을 저장할 위치를 시스템에 알려주므로 같은 디렉터리의 끝에 종속 항목이 하나만 있는지 확인할 수 있습니다.

{
    "directory" : "../../../../../bower_components"
}

이렇게 하면 여러 요소가 THREE.js를 종속 항목으로 선언하는 경우, Bower에서 첫 번째 요소에 관해 설치하고 두 번째 요소의 파싱을 시작하면 이 종속 항목이 이미 설치되어 있으므로 이를 다시 다운로드하거나 복제하지 않습니다. 마찬가지로, bower.json에 종속 항목 파일을 정의하는 요소가 하나 이상 있는 한 이 종속 항목 파일이 유지됩니다.

bash 스크립트는 중첩된 요소 구조의 모든 bower.json 파일을 찾습니다. 그런 다음 이러한 디렉터리를 하나씩 입력하고 각 디렉터리에 bower install를 실행합니다.

echo installing bower components...
modules=$(find /vagrant/app -type f -name "bower.json" -not -path "*node_modules*" -not -path "*bower_components*")
for module in $modules; do
    pushd $(dirname $module)
    bower install --allow-root -q
    popd
done

간단한 새 요소 템플릿

새 요소를 만들 때마다 약간의 시간이 걸립니다. 올바른 이름으로 폴더와 기본 파일 구조를 생성하는 것입니다. 따라서 Slush를 사용하여 간단한 요소 생성기를 작성해 보겠습니다.

명령줄에서 스크립트를 호출할 수 있습니다.

$ slush element path/to/your/element-name

그러면 모든 파일 구조와 콘텐츠를 포함한 새 요소가 생성됩니다.

요소 파일의 템플릿을 정의했습니다.예를 들어 .jade 파일 템플릿은 다음과 같습니다.

// Element
dom-module(id='<%= name %>')

    // Template
    template
    style
        include elements/<%= path %>/styles/<%= name %>.css

    span This is a '<%= name %>' element.

    // Polymer element script
    script(src='scripts/<%= name %>.js')

Slush 생성기가 변수를 실제 요소 경로 및 이름으로 바꿉니다.

Gulp를 사용하여 요소 빌드

Gulp는 빌드 프로세스를 적절하게 제어합니다. 구조에서 요소를 빌드하려면 Gulp가 다음 단계를 따라야 합니다.

  1. 요소의 .coffee 파일을 .js로 컴파일합니다.
  2. 요소의 .scss 파일을 .css로 컴파일합니다.
  3. 요소의 .jade 파일을 .html로 컴파일하여 .css 파일을 삽입합니다.

자세한 내용은 다음과 같습니다.

요소의 .coffee 파일을 .js로 컴파일

gulp.task('elements-coffee', function () {
    return gulp.src(abs(config.paths.app + '/elements/**/*.coffee'))
    .pipe($.replaceTask({
        patterns: [{json: getVersionData()}]
    }))
    .pipe($.changed(abs(config.paths.static + '/elements'), {extension: '.js'}))
    .pipe($.coffeelint())
    .pipe($.coffeelint.reporter())
    .pipe($.sourcemaps.init())
    .pipe($.coffee({
    }))
    .on('error', gutil.log)
    .pipe($.sourcemaps.write())
    .pipe(gulp.dest(abs(config.paths.static + '/elements')));
});

2단계와 3단계에서는 위의 2와 비슷한 방식으로 gulp와 나침반 플러그인을 사용하여 scss.css로, .jade.html로 컴파일합니다.

폴리머 원소 포함

Polymer 요소를 실제로 포함하기 위해 HTML 가져오기를 사용합니다.

<link rel="import" href="elements.html">

<!-- Polymer -->
<link rel="import" href="../bower_components/polymer/polymer.html">

<!-- Custom elements -->
<link rel="import" href="sw/sw-app/sw-app.html">
<link rel="import" href="sw/system/sw-system/sw-system.html">
<link rel="import" href="sw/system/sw-routing/sw-routing.html">
<link rel="import" href="sw/system/sw-system-version/sw-system-version.html">
<link rel="import" href="sw/system/sw-system-environment/sw-system-environment.html">
<link rel="import" href="sw/pages/sw-page-landing/sw-page-landing.html">
<link rel="import" href="sw/pages/sw-page-connection/sw-page-connection.html">
<link rel="import" href="sw/pages/sw-page-calibration/sw-page-calibration.html">
<link rel="import" href="sw/pages/sw-page-experience/sw-page-experience.html">
<link rel="import" href="sw/ui/sw-preloader/sw-preloader.html">
<link rel="import" href="sw/ui/sw-ui-overlay/sw-ui-overlay.html">
<link rel="import" href="sw/ui/sw-ui-button/sw-ui-button.html">
<link rel="import" href="sw/ui/sw-ui-menu/sw-ui-menu.html">

프로덕션을 위해 폴리머 요소 최적화

큰 프로젝트에는 많은 Polymer 요소가 있을 수 있습니다. 우리 프로젝트에는 50개가 넘습니다. 각 요소에 별도의 .js 파일이 있고 일부 요소에 참조된 라이브러리가 있다고 생각하면 개별 파일이 100개를 초과하게 됩니다. 즉, 브라우저에서 요청해야 하는 요청이 많아지면서 성능 손실이 발생합니다. Angular 빌드에 적용하는 연결 및 축소 프로세스와 마찬가지로 프로덕션의 마지막 단계에서 Polymer 프로젝트를 '가소화'합니다.

Vulcanize는 종속 항목 트리를 단일 html 파일로 평면화하여 요청 수를 줄이는 Polymer 도구입니다. 이는 웹 구성요소를 기본적으로 지원하지 않는 브라우저에 특히 유용합니다.

CSP (콘텐츠 보안 정책) 및 Polymer

안전한 웹 애플리케이션을 개발할 때는 CSP를 구현해야 합니다. CSP는 안전하지 않은 소스에서 스크립트를 실행하거나 HTML 파일에서 인라인 스크립트를 실행하는 등 교차 사이트 스크립팅 (XSS) 공격을 방지하는 일련의 규칙입니다.

이제 Vulcanize에서 생성되고 최적화, 연결, 축소된 .html 파일에 모든 자바스크립트 코드가 CSP 규정을 준수하지 않는 형식의 인라인으로 포함되어 있습니다. 이 문제를 해결하기 위해 Crisper라는 도구를 사용합니다.

Crisper는 HTML 파일에서 인라인 스크립트를 분할하여 CSP 규정 준수를 위해 단일 외부 자바스크립트 파일에 배치합니다. 따라서 가공된 HTML 파일을 Crisper를 통해 전달하고 elements.htmlelements.js의 두 파일을 만듭니다. elements.html 내부에서는 생성된 elements.js를 로드하는 작업도 처리합니다.

애플리케이션 논리 구조

Polymer에서 요소는 비시각적 유틸리티부터 버튼 같은 작은 독립형 UI 요소, '페이지'와 같은 더 큰 모듈, 전체 애플리케이션 구성에 이르기까지 무엇이든 될 수 있습니다.

애플리케이션의 최상위 논리적 구조
Polymer 요소로 표시되는 애플리케이션의 최상위 논리적 구조입니다.

Polymer 및 상위-하위 아키텍처를 사용한 후처리

모든 3D 그래픽 파이프라인에는 언제나 마지막 단계가 있으며, 여기서 전체 그림 위에 일종의 오버레이로 효과가 추가됩니다. 이 단계는 후처리 단계이며 발광 효과, 광선, 피사계 심도, 빛망울 효과, 블러와 같은 효과가 포함됩니다. 효과가 결합되어 장면이 구축되는 방식에 따라 다양한 요소에 적용됩니다. THREE.js에서는 자바스크립트의 후처리를 위한 맞춤 셰이더를 만들거나 상위-하위 구조 덕분에 Polymer를 사용하여 이 작업을 수행할 수 있습니다.

포스트 프로세서의 요소 HTML 코드를 살펴보면 다음과 같습니다.

<dom-module id="sw-experience-postprocessor">
    <!-- Template-->
    <template>
    <sw-experience-effect-bloom class="effect"></sw-experience-effect-bloom>
    <sw-experience-effect-dof class="effect"></sw-experience-effect-dof>
    <sw-experience-effect-vignette class="effect"></sw-experience-effect-vignette>
    </template>
    <!-- Polymer element script-->
    <script src="scripts/sw-experience-postprocessor.js"></script>
</dom-module>

공통 클래스 아래에 중첩된 Polymer 요소로 효과를 지정합니다. 그런 다음 sw-experience-postprocessor.js에서 다음을 실행합니다.

effects = @querySelectorAll '.effect'
@composer.addPass effect.getPass() for effect in effects

Google에서는 HTML 기능과 자바스크립트의 querySelectorAll를 사용하여 포스트 프로세서 내에서 HTML 요소로 중첩된 모든 효과를 지정된 순서대로 찾습니다. 그런 다음 그것을 반복하고 작성기에 추가합니다.

이제 DOF (심도) 효과를 삭제하고 블룸 및 비네트 효과의 순서를 변경한다고 가정해 보겠습니다. 포스트 프로세서의 정의를 다음과 같이 수정하기만 하면 됩니다.

<dom-module id="sw-experience-postprocessor">
    <!-- Template-->
    <template>
    <sw-experience-effect-vignette class="effect"></sw-experience-effect-vignette>
    <sw-experience-effect-bloom class="effect"></sw-experience-effect-bloom>
    </template>
    <!-- Polymer element script-->
    <script src="scripts/sw-experience-postprocessor.js"></script>
</dom-module>

실제 코드를 한 줄도 변경하지 않고 장면이 실행됩니다.

Polymer의 렌더링 루프 및 업데이트 루프

Polymer를 사용하면 렌더링과 엔진 업데이트에도 매끄럽게 접근할 수 있습니다. requestAnimationFrame를 사용하고 현재 시간(t) 및 델타 시간(마지막 프레임(dt)에서 경과한 시간)과 같은 값을 계산하는 timer 요소를 만들었습니다.

Polymer
    is: 'sw-timer'

    properties:
    t:
        type: Number
        value: 0
        readOnly: true
        notify: true
    dt:
        type: Number
        value: 0
        readOnly: true
        notify: true

    _isRunning: false
    _lastFrameTime: 0

    ready: ->
    @_isRunning = true
    @_update()

    _update: ->
    if !@_isRunning then return
    requestAnimationFrame => @_update()
    currentTime = @_getCurrentTime()
    @_setT currentTime
    @_setDt currentTime - @_lastFrameTime
    @_lastFrameTime = @_getCurrentTime()

    _getCurrentTime: ->
    if window.performance then performance.now() else new Date().getTime()

그런 다음 데이터 결합을 사용하여 tdt 속성을 엔진 (experience.jade)에 결합합니다.

sw-timer(
    t='{ % templatetag openvariable % }t}}',
    dt='{ % templatetag openvariable % }dt}}'
)

sw-experience-engine(
    t='[t]',
    dt='[dt]'
)

또한 엔진에서 tdt 변경사항을 수신 대기하고 값이 변경될 때마다 _update 함수가 호출됩니다.

Polymer
    is: 'sw-experience-engine'

    properties:
    t:
        type: Number

    dt:
        type: Number

    observers: [
    '_update(t)'
    ]

    _update: (t) ->
    dt = @dt
    @_physics.update dt, t
    @_renderer.render dt, t

그래도 FPS가 필요한 경우 렌더링 루프에서 Polymer의 데이터 결합을 제거하여 요소에 변경사항을 알리는 데 필요한 몇 밀리초를 절약할 수 있습니다. 맞춤 관찰자를 다음과 같이 구현했습니다.

sw-timer.coffee:

addUpdateListener: (listener) ->
    if @_updateListeners.indexOf(listener) == -1
    @_updateListeners.push listener
    return

removeUpdateListener: (listener) ->
    index = @_updateListeners.indexOf listener
    if index != -1
    @_updateListeners.splice index, 1
    return

_update: ->
    # ...
    for listener in @_updateListeners
        listener @dt, @t
    # ...

addUpdateListener 함수는 콜백을 받아 콜백 배열에 저장합니다. 그런 다음 업데이트 루프에서 모든 콜백을 반복하고 dtt 인수로 직접 실행하여 데이터 결합이나 이벤트 실행을 우회합니다. 콜백이 더 이상 활성 상태가 아닌 경우 이전에 추가한 콜백을 삭제할 수 있는 removeUpdateListener 함수를 추가했습니다.

THREE.js로 작성된 광선검

THREE.js는 WebGL의 낮은 수준의 세부정보를 추상화하여 문제에 집중할 수 있습니다. 우리 문제는 스톰트루퍼와 싸우는 것이고 무기가 필요합니다. 이제 광선검을 만들어 볼까요.

반짝이는 칼날은 기존의 양손 무기와 구별되는 광선검입니다. 빔은 주로 빔과 움직일 때 보이는 흔적이라는 두 부분으로 구성됩니다. 밝은 원통 형태와 플레이어의 움직임에 따라 이어지는 역동적인 트레일로 만들었습니다.

더 블레이드

날은 2개의 보조 날로 구성되어 있습니다. 안쪽과 바깥쪽입니다. 둘 다 각각의 머티리얼을 갖춘 3REE.js 메시입니다.

안쪽 블레이드

내부 블레이드에는 맞춤 셰이더가 있는 맞춤 머티리얼을 사용했습니다. 두 지점에 의해 생성된 선을 사용하여 평면에서 이 두 점 사이의 선을 투영합니다. 이 비행기는 기본적으로 모바일과 싸울 때 제어하는 것으로, 세이버에 깊이와 방향감을 제공합니다.

빛나는 둥근 물체의 느낌을 주기 위해 아래와 같이 두 점 A와 B를 연결하는 기본선에서 평면상의 한 점의 직각점 거리를 살펴봅니다. 지점이 기본 축에 가까울수록 더 밝아집니다.

안쪽 날 발광 효과

아래 소스는 vFactor를 계산하여 꼭짓점 셰이더의 강도를 제어한 다음 이를 사용하여 프래그먼트 셰이더의 장면과 블렌딩하는 방법을 보여줍니다.

THREE.LaserShader = {

    uniforms: {
    "uPointA": {type: "v3", value: new THREE.Vector3(0, -1, 0)},
    "uPointB": {type: "v3", value: new THREE.Vector3(0, 1, 0)},
    "uColor": {type: "c", value: new THREE.Color(1, 0, 0)},
    "uMultiplier": {type: "f", value: 3.0},
    "uCoreColor": {type: "c", value: new THREE.Color(1, 1, 1)},
    "uCoreOpacity": {type: "f", value: 0.8},
    "uLowerBound": {type: "f", value: 0.4},
    "uUpperBound": {type: "f", value: 0.8},
    "uTransitionPower": {type: "f", value: 2},
    "uNearPlaneValue": {type: "f", value: -0.01}
    },

    vertexShader: [

    "uniform vec3 uPointA;",
    "uniform vec3 uPointB;",
    "uniform float uMultiplier;",
    "uniform float uNearPlaneValue;",
    "varying float vFactor;",

    "float getDistanceFromAB(vec2 a, vec2 b, vec2 p) {",

        "vec2 l = b - a;",
        "float l2 = dot( l, l );",
        "float t = dot( p - a, l ) / l2;",
        "if( t < 0.0 ) return distance( p, a );",
        "if( t > 1.0 ) return distance( p, b );",
        "vec2 projection = a + (l * t);",
        "return distance( p, projection );",

    "}",

    "vec3 getIntersection(vec4 a, vec4 b) {",

        "vec3 p = a.xyz;",
        "vec3 q = b.xyz;",
        "vec3 v = normalize( q - p );",
        "float t = ( uNearPlaneValue - p.z ) / v.z;",
        "return p + (v * t);",

    "}",

    "void main() {",

        "vec4 a = modelViewMatrix * vec4(uPointA, 1.0);",
        "vec4 b = modelViewMatrix * vec4(uPointB, 1.0);",
        "if(a.z > uNearPlaneValue) a.xyz = getIntersection(a, b);",
        "if(b.z > uNearPlaneValue) b.xyz = getIntersection(a, b);",
        "a = projectionMatrix * a; a /= a.w;",
        "b = projectionMatrix * b; b /= b.w;",
        "vec4 p = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
        "gl_Position = p;",
        "p /= p.w;",
        "float d = getDistanceFromAB(a.xy, b.xy, p.xy) * gl_Position.z;",
        "vFactor = 1.0 - clamp(uMultiplier * d, 0.0, 1.0);",

    "}"

    ].join( "\n" ),

    fragmentShader: [

    "uniform vec3 uColor;",
    "uniform vec3 uCoreColor;",
    "uniform float uCoreOpacity;",
    "uniform float uLowerBound;",
    "uniform float uUpperBound;",
    "uniform float uTransitionPower;",
    "varying float vFactor;",

    "void main() {",

        "vec4 col = vec4(uColor, vFactor);",
        "float factor = smoothstep(uLowerBound, uUpperBound, vFactor);",
        "factor = pow(factor, uTransitionPower);",
        "vec4 coreCol = vec4(uCoreColor, uCoreOpacity);",
        "vec4 finalCol = mix(col, coreCol, factor);",
        "gl_FragColor = finalCol;",

    "}"

    ].join( "\n" )

};

아우터 블레이드 발광 효과

외부 발광 효과의 경우 별도의 렌더 버퍼로 렌더링하고 후처리 블룸 효과를 사용하고 최종 이미지와 혼합하여 원하는 발광 효과를 얻습니다. 아래 이미지는 괜찮은 수준의 세이버를 원하는 경우 필요한 세 가지 영역을 보여줍니다. 즉, 흰색 코어, 중간의 푸른 발광 효과, 외부의 발광등입니다.

바깥쪽 날

광선검 트레일

광선검의 흔적은 스타워즈 시리즈에서 볼 수 있는 원작의 효과를 제대로 발휘하는 데 중요합니다. 광선검의 움직임에 따라 동적으로 생성된 삼각형 팬으로 트레일을 만들었습니다. 그런 다음 이러한 팬은 포스트 프로세서로 전달되어 시각적으로 더욱 개선됩니다. 팬 도형을 만들기 위해 선 세그먼트가 있으며 이전 변환 및 현재 변환을 기반으로 메시에 새 삼각형을 생성하여 특정 길이 후에 꼬리 부분을 삭제합니다.

왼쪽 광선검 흔적
오른쪽 광선검 트레일

메시가 생성되면 여기에 간단한 머티리얼을 할당하고 포스트 프로세서에 전달하여 매끄러운 효과를 만듭니다. 바깥쪽 날 발광 효과에 적용한 것과 동일한 블룸 효과를 사용하여 다음과 같이 매끄러운 흔적을 얻습니다.

전체 트레일

트레일 주변에 빛나는 장소

최종 조각을 완성하려면 다양한 방법으로 만들 수 있는 실제 트레일 주위의 발광 효과를 처리해야 했습니다. 여기서는 성능상의 이유로 여기서 자세히 다루지 않겠습니다. 이 솔루션은 렌더버퍼의 클램프 주위에 매끄러운 가장자리를 만드는 이 버퍼의 맞춤 셰이더를 만드는 것이었습니다. 그런 다음 이 출력을 최종 렌더에서 결합합니다. 여기에서 트레일을 둘러싼 발광 효과를 확인할 수 있습니다.

반짝이는 등산로

결론

Polymer는 강력한 라이브러리이자 개념입니다 (일반적으로 WebComponents와 동일함). 그것으로 무엇을 만들 것인지는 여러분이 결정할 뿐입니다. 간단한 UI 버튼부터 전체 크기의 WebGL 애플리케이션에 이르기까지 무엇이든 될 수 있습니다. 이전 챕터에서는 프로덕션에서 Polymer를 효율적으로 사용하는 방법과 성능이 좋은 더 복잡한 모듈을 구조화하는 방법에 관한 도움말 및 유용한 정보를 알아보았습니다. 또한 WebGL에서 멋진 광선검을 만드는 방법도 보여드렸습니다. 따라서 이 모든 요소를 결합하는 경우 프로덕션 서버에 배포하기 전에 Polymer 요소를 가변화하는 것을 잊지 마세요. CSP 규정을 준수하려면 Crisper를 사용하는 것을 잊지 마세요.

게임 플레이