GLSL를 이용한 그래픽 효과

3차원 그래픽에서 특수 효과는 쉐이더를 통해 대부분 구현됩니다. 이 글은 간단한 GLSL 쉐이더 코드를 통해 물과 불에 대한 효과를 소개합니다.

먼저 불에 대해 구현하고자 하는 모습은 다음과 같습니다.

다음은 물에 대한 결과입니다.

전체 소스코드는 아래 링크를 통해 다운로드 받으실 수 있습니다. 웹기반에서 구현된 코드이므로 js와 css, html 파일로 구성되어 있으며 WebGL 2.0으로 쉐이더 코드가 실행됩니다.

chart.js 코드 정리

MIT 라이선스인 chart.js를 예제를 통해 간단하게 정리해 봅니다. 구현하고자 하는 모습은 아래의 영상과 같습니다.

꺽은선 차트이며, 2개의 데이터 축을 가지고 1초마다 데이터가 변경됩니다.

먼저 DOM 구조입니다.

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div>
    <canvas id="chart"></canvas>
</div>

CSS는 다음과 같구요.

@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap');

body {
    margin: 0;
    padding: 0;
}

div {
    width: 600px;
    height: 400px;
    padding: 20px;
}

이제 JS 코드에 대한 내용인데, 먼저 차트를 위한 기본 골격은 다음과 같습니다.

Chart.defaults.font.family = 'Noto Sans KR';

const ctx = document.getElementById('chart');

const config = {
    // CORE PART !!
};

const chart = new Chart(ctx, config);

위의 config에 속성값을 지정함으로써 차트를 정의하는 세세한 내용을 결정할 수 있습니다. config에 대한 속성값은 다음과 같이 정의했습니다.

const config = {
    type: 'line',
    data: {
        labels: ['빨강', 'Blue', 'Yellow', '초록', 'Purple', '오렌지'],
        datasets: [
            {
                label: '변곡점1',
                data: [12, 19, 3, 5, 2, 3],
                backgroundColor: 'yellow',
                borderColor: 'black',
                borderWidth: 1
            },
            {
                label: '변곡점2',
                data: [10, 16, 7, 6, 4, 2],
                backgroundColor: 'white',
                borderColor: 'gray',
                borderWidth: 1
            }
        ]
    },
    options: {
        maintainAspectRatio: false,
        plugins: {
            title: {
                display: true,
                text: '차트, 그것이 알고 싶다.'
            }
        },
        scales: {
            x: {
                title: {
                    display: true,
                    text: '색상'
                }
            },
            y: {
                title: {
                    display: true,
                    text: '변곡량'
                }
            }
        },

    }
}

앞서 실행 화면을 보면 1초마다 차트의 값이 변경되는 것을 볼 수 있습니다. 이에 대한 코드는 다음과 같습니다.

setInterval(() => {
    const datasets = config.data.datasets;

    for (let iDataset = 0; iDataset < datasets.length; iDataset++) {
        const data = datasets[iDataset].data;
        for (let iValue = 0; iValue < data.length; iValue++) {
            data[iValue] += Math.random() * 2.0 - 1.0;
        }
    }

    chart.update();
}, 1000);

Javascript의 Array, Set, Object, Map에 대한 데이터 순회하기

자바스크립트에서 데이터 컨테이너로써의 관점에서 데이터를 순회하는 코드를 정리합니다.

먼저 Array입니다.

const arr = new Array(1, 2, 3, 4, 5)

console.log('Array Iteration 1')
for (let i = 0; i < arr.length; i++) {
    console.log(arr[i])
}

console.log('Array Iteration 2')
arr.forEach(v => {
    console.log(v)
})

console.log('Array Iteration 3')
for (let v of arr) {
    console.log(v)
}

console.log('Array Iteration 4')
for (let i in arr) {
    console.log(arr[i])
}

다음은 Set입니다.

const set = new Set([1, 2, 3, 4, 'Hello'])

console.log('Set Iteration 1')
for (let v of set) {
    console.log(v)
}

console.log('Set Iteration 2')
for (let v of set.values()) {
    console.log(v)
}

console.log('Set Iteration 3')
set.forEach(v => {
    console.log(v)
});

다음은 Object입니다.

const obj = { a: 1, b: 2, c: 3, 9: 4, e: 'Hello' }

console.log('Object Iteration 1')
const keys = Object.keys(obj) // [ 'a', 'b', 'c', '9', 'e' ]

for (let i = 0; i < keys.length; i++) {
    const k = keys[i]
    const v = obj[k]
    console.log(k, v)
}

console.log('Object Iteration 2')
const values = Object.values(obj) // [ 1, 2, 3, 4, 'Hello' ]
for (let i = 0; i < values.length; i++) {
    const v = values[i]
    console.log(v)
}

console.log('Object Iteration 3')
const entries = Object.entries(obj) // [ ['a', 1], ['b', 2], ['c', 3], ['9', 4], ['e', 'Hello'] ]
for (let i = 0; i < entries.length; i++) {
    const k = entries[i][0]
    const v = entries[i][1]

    console.log(k, v)
}

console.log('Object Iteration 4')
for (let k in obj) {
    const v = obj[k]
    console.log(k, v)
}

끝으로 Map입니다.

const map = new Map( [ ['a', 1], ['b', 2], ['c', 3], ['9', 4], ['e', 'Hello'] ])

console.log('Map Iteration 1')
for (let [k, v] of map) {
    console.log(k, v)
}

console.log('Map Iteration 2')
for (let k of map.keys()) {
    console.log(k, map.get(k))
}

console.log('Map Iteration 3')
for (let v of map.values()) {
    console.log(v)
}

console.log('Map Iteration 4')
for (let [k, v] of map.entries()) {
    console.log(k, v)
}

console.log('Map Iteration 5')
map.forEach(function(v, k) {
    console.log(k, v)
})

사실 Map의 출현으로 더 이상 Object를 데이터 컨테이너로써 사용하는 것은 옳지 않습니다. Object는 기본적으로 생성될 때 기본 키값을 갖지만 Map은 개발자가 추가하지 않은 데이터는 갖지 않습니다. 또한 Map의 Key는 문자열 객체 이외에도 타입도 가능합니다. 또한 저장한 순서대로 값을 얻을 수 있으며 데이터의 개수도 size 함수를 통해 바로 얻을 수 있습니다. 게다가 Map은 데이터의 추가와 삭제 시 Object보다 성능이 뛰어납니다.

matter.js를 이용한 강체 시뮬레이션

matter.js 라이브러리를 이용하여 구현하고자 하는 결과는 아래와 같습니다.

먼저 HTML 코드입니다.

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="style_matter.js.css">
    <script src="decomp.js"></script>
    <script src="pathseg.js"></script>
    <script src="matter.0.16.1.js"></script>
    <script src="app_matter.js.js" defer></script>
</head>
<body>
    <div></div>
</body>
</html> 

지형은 SVG 데이터를 통해 좌표를 뽑아오는데 이를 위해 matter.js 이외에도 decomp.js와 pathseg.js 라이브러리가 필요합니다. div 요소에 강체 시뮬레이션의 결과가 표시됩니다. 다음은 CSS입니다.

* {
    outline: none;
    padding: 0;
    margin: 0;
}
    
body {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100vh;
}

div 요소를 화면 중심에 놓기 위한 것이 전부입니다. 이제 js 코드를 살펴보겠습니다.

먼저 아래의 코드로 기본 코드를 작성합니다.

const engine = Matter.Engine.create();
const world = engine.world;

const render = Matter.Render.create({
    element: document.querySelector("div"),
    engine: engine,
    options: {
        width: 800,
        height: 600,
        wireframes: false,
        background: 'black'
    }
});

Matter.Render.run(render);

const runner = Matter.Runner.create();
Matter.Runner.run(runner, engine);

다음으로 지형과 지형 속에 사각형 물체를 구성하는 코드입니다.

fetch("./terrain.svg")
    .then((response) => { return response.text(); })
    .then((raw) => { return (new window.DOMParser()).parseFromString(raw, "image/svg+xml"); })
    .then(function(root) {
        const paths = Array.prototype.slice.call(root.querySelectorAll('path'));
        const vertices = paths.map((path) => { return Matter.Svg.pathToVertices(path, 30); });
        const terrain = Matter.Bodies.fromVertices(400, 350, vertices, {
            isStatic: true,
            render: {
                fillStyle: '#2c3e50',
                strokeStyle: '#2c3e50',
                lineWidth: 1,
            }
        }, true);

        Matter.World.add(world, terrain);

        const bodyOptions = {
            frictionAir: 0.1, 
            friction: 0.5,
            restitution: 0.1
        };
        
        Matter.World.add(world, Matter.Composites.stack(100, 200, 40, 10, 15, 15, (x, y) => {
            if (Matter.Query.point([terrain], { x: x, y: y }).length === 0) {
                return Matter.Bodies.polygon(x, y, 4, 10, bodyOptions);
            }
        }));
    }
);

끝으로 마우스를 통해 사각형 객체를 드레그하여 옮길 수 있도록 합니다.

const mouse = Matter.Mouse.create(render.canvas),
mouseConstraint = Matter.MouseConstraint.create(engine, {
    mouse: mouse,
    constraint: {
        stiffness: 0.2,
        render: {
            visible: false
        }
    }
});

Matter.World.add(world, mouseConstraint);

matter.js는 매우 정교한 강체 시뮬레이션에는 적합하지 않으나 시각적인 면에서 다양한 물리 효과를 2차원에서 폭넓게 응용할 수 있다는 점이 매우 큰 장점입니다.

아래는 필요한 코드와 데이터 전체를 다운로드 받을 수 있는 링크입니다.

Javascript 객체(object)의 키(key)와 값(value)을 배열로 얻기

만약 다음과 같은 자바스크립트 객체가 있다고 할 때..

const obj = {
  a: 'Dip2K',
  b: 30,
  c: true,
  d: {}
};

obj 객체를 구성하는 전체 키를 배열로 얻는 코드는 다음과 같습니다.

console.log(Object.keys(obj)); 
// Array ["a", "b", "c", "d"]

다시 obj 객체를 구성하는 전체 값을 배열로 얻는 코드는 다음과 같구요.

console.log(Object.values(obj)); 
// Array ["Dip2K", 30, true, Object {  }]

배열에 대해서 각 구성 항목을 참조하는 코드는 다음과 같습니다.

for(i of Object.values(obj)) {
    console.log(i);
}

/* 
"Dip2K"
30
true
Object {  }
*/