공부/threeJS

Three.js 따라 강의 - 03 원시 모델(primitives)- geometry 종류

캄성 2022. 11. 28. 11:58

2022.05.17 - [공부/JS] - Three.js 따라 강의 - 00 먼저 알아야 할 것 들

2022.05.17 - [공부/JS] - Three.js 따라 강의 - 01 Threejs 란? (정육면체만들기)

2022.05.18 - [공부/threeJS] - Three.js 따라 강의 - 02 반응형 다자인 (화면 채우기

threejs 웹을 통하여 공부하며 개인적으로 정리한 내용을 개인경험을 추가하여 정리한 곳 입니다.

(예제가 실제 실행되지 않는 것을 고쳐 설명할경 정리하기 위하여 작성)

https://threejs.org/ 에서 제공 하는 Three.js Fundamentals 진행 이후,

유료 강의인 Three.js Journey에 대한 리뷰를 진행 할 예정입니다.

Learn Three.js의 경우 기본 이론의 심화 과정 느낌인데, 학생ID 이용시 무료 로 사용한 Ebook도 있습니다.


원시 모델(primitives)

 - ThreeJS 로 표현 가능한  기본 모델들 입니다. 평면 부터 다각형 까지 기본 제공하는 도형을 사용하여, 대부분의 기본 적인 배치 및 구현이 가능합니다. 

 

기본코드

아래 코드 부분에서 원시모델 수정 부분을 수정할 경우 테스트 가능

wire frame을 사용하여, 옵션에 따른 변화 확인 가능

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html,
        body {
            margin: 0;
            height: 100%;
        }

        #c {
            width: 100%;
            height: 100%;
            display: block;
        }
    </style>

</head>

<body>
    <canvas id="c"></canvas>
    <script type="importmap">{
      "imports": {
          "three": "https://threejs.org/build/three.module.js"
      }
  }</script><!-- Remove this when import maps will be widely supported -->
    <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
    <script type="module">
        import * as THREE from 'three';
        function main() {
            const canvas = document.querySelector('#c');
            const renderer = new THREE.WebGLRenderer({ canvas });


            const fov = 75;
            const aspect = 2;  // the canvas default
            const near = 0.1;
            const far = 50;
            const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
            camera.position.z = 20;


            const scene = new THREE.Scene();
            scene.background = new THREE.Color(0xbbbbbb);

            {
                const color = 0xFFFFFF;
                const intensity = 1;
                const light = new THREE.DirectionalLight(color, intensity);
                light.position.set(-1, 2, 4);
                scene.add(light);
            }


            // 원시 모델 수정 부분
            const width = 8;  // ui: width
            const height = 8;  // ui: height
            const depth = 8;  // ui: depth
            const widthSegments = 4;  // ui: widthSegments
            const heightSegments = 4;  // ui: heightSegments
            const depthSegments = 4;  // ui: depthSegments
            const geometry = new THREE.BoxGeometry(
                width, height, depth,
                widthSegments, heightSegments, depthSegments);
            // 원시 모델 수정 부분

            function makeInstance(geometry, color, x) {
                const material = new THREE.MeshBasicMaterial({ color });

                const cube = new THREE.Mesh(geometry, material);


                const cubeline = new THREE.LineSegments(
                    new THREE.WireframeGeometry(geometry))
                cubeline.material.depthTest = false;
                cubeline.material.opacity = 0.25;
                cubeline.material.transparent = true;

                const roomGroup = new THREE.Group();
                roomGroup.add(cubeline); // 주석처리할경우 사라짐
                roomGroup.add(cube);
                scene.add(roomGroup);

                roomGroup.position.x = x;

                return roomGroup;
            }




            let cubes = [
                makeInstance(geometry, 0Xa4a4a4, 0),
                // makeInstance(geometry, 0x8844aa, -2),
                // makeInstance(geometry, 0xaa8844, 2),
            ];
            console.log(cubes);

            function resizeRendererToDisplaySize(renderer) {
                const canvas = renderer.domElement;
                const pixelRatio = window.devicePixelRatio;
                const width = canvas.clientWidth * pixelRatio | 0;
                const height = canvas.clientHeight * pixelRatio | 0;
                const needResize = canvas.width !== width || canvas.height !== height;
                if (needResize) {
                    renderer.setSize(width, height, false);
                }
                return needResize;
            }

            function render(time) {
                time *= 0.001;  // convert time to seconds

                if (resizeRendererToDisplaySize(renderer)) {
                    const canvas = renderer.domElement;
                    camera.aspect = canvas.clientWidth / canvas.clientHeight;
                    camera.updateProjectionMatrix();
                }

                cubes.forEach((cube, ndx) => {
                    const speed = 1 + ndx * .1;
                    const rot = time * speed;
                    cube.rotation.x = rot;
                    cube.rotation.y = rot;
                });

                renderer.render(scene, camera);

                requestAnimationFrame(render);
            }
            requestAnimationFrame(render);

        }
        main();



    </script>
</body>

</html>

육면체 (BoxGeometry)

 

const width = 8;  // ui: width
const height = 8;  // ui: height
const depth = 8;  // ui: depth
const geometry = new THREE.BoxGeometry(width, height, depth);

 

 

const width = 8;  // ui: width
const height = 8;  // ui: height
const depth = 8;  // ui: depth
const widthSegments = 4;  // ui: widthSegments
const heightSegments = 4;  // ui: heightSegments
const depthSegments = 4;  // ui: depthSegments
const geometry = new THREE.BoxGeometry(
    width, height, depth,
    widthSegments, heightSegments, depthSegments);

 

원 (CircleGeometry)

const radius = 7;  // ui: radius
const segments = 24;  // ui: segments
const geometry = new THREE.CircleGeometry(radius, segments);

 

const radius = 7;  // ui: radius
const segments = 24;  // ui: segments
const thetaStart = Math.PI * 0.25;  // ui: thetaStart
const thetaLength = Math.PI * 1.5;  // ui: thetaLength
const geometry = new THREE.CircleGeometry(
    radius, segments, thetaStart, thetaLength);

원뿐 (ConeGeometry)

 

 

const radius = 6;  // ui: radius
const height = 8;  // ui: height
const radialSegments = 16;  // ui: radialSegments
const geometry = new THREE.ConeGeometry(radius, height, radialSegments);

const radius = 6;  // ui: radius
const height = 8;  // ui: height
const radialSegments = 16;  // ui: radialSegments
const heightSegments = 2;  // ui: heightSegments
const openEnded = true;  // ui: openEnded
const thetaStart = Math.PI * 0.25;  // ui: thetaStart
const thetaLength = Math.PI * 1.5;  // ui: thetaLength
const geometry = new THREE.ConeGeometry(
    radius, height,
    radialSegments, heightSegments,
    openEnded,
    thetaStart, thetaLength);

원통(CylinderGeometry)

const radiusTop = 4;  // ui: radiusTop
const radiusBottom = 4;  // ui: radiusBottom
const height = 8;  // ui: height
const radialSegments = 12;  // ui: radialSegments
const geometry = new THREE.CylinderGeometry(
    radiusTop, radiusBottom, height, radialSegments);

const radiusTop = 4;  // ui: radiusTop
const radiusBottom = 4;  // ui: radiusBottom
const height = 8;  // ui: height
const radialSegments = 12;  // ui: radialSegments
const heightSegments = 2;  // ui: heightSegments
const openEnded = false;  // ui: openEnded
const thetaStart = Math.PI * 0.25;  // ui: thetaStart
const thetaLength = Math.PI * 1.5;  // ui: thetaLength
const geometry = new THREE.CylinderGeometry(
    radiusTop, radiusBottom, height,
    radialSegments, heightSegments,
    openEnded,
    thetaStart, thetaLength);

 

12면체 ( DodecahedronGeometry )

구의 기본형이라고 보시면 됩니다. 디테일 증가에 따라 구에 더 가까워 집니다.

 

const radius = 7;  // ui: radius
const geometry = new THREE.DodecahedronGeometry(radius);

 

 

const radius = 7;  // ui: radius
const detail = 2;  // ui: detail
const geometry = new THREE.DodecahedronGeometry(radius, detail);

돌출형상(ExtrudeGeometry)

돌출 형상이라고하며, 다각형의 기본으로 다양한 다각형을 만들 수 있고, 경우에 따라 행렬에서좌표 값을 가져와 만들 수도 있습니다.  

 

            const shape = new THREE.Shape();
const x = -2.5;
const y = -5;
shape.moveTo(x + 2.5, y + 2.5);
shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);

const extrudeSettings = {
  steps: 2,  // ui: steps
  depth: 2,  // ui: depth
  bevelEnabled: true,  // ui: bevelEnabled
  bevelThickness: 1,  // ui: bevelThickness
  bevelSize: 1,  // ui: bevelSize
  bevelSegments: 2,  // ui: bevelSegments
};

const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);

위 이미지의 경우 하기 이미지를 작성 이후에, 포이트 별로 회전 시킨 모양이다.

            const outline = new THREE.Shape([
                [-2, -0.1], [2, -0.1], [2, 0.6],
                [1.6, 0.6], [1.6, 0.1], [-2, 0.1],
            ].map(p => new THREE.Vector2(...p)));

            const x = -2.5;
            const y = -5;
            const shape = new THREE.CurvePath();
            const points = [
                [x + 2.5, y + 2.5],
                [x + 2.5, y + 2.5], [x + 2, y], [x, y],
                [x - 3, y], [x - 3, y + 3.5], [x - 3, y + 3.5],
                [x - 3, y + 5.5], [x - 1.5, y + 7.7], [x + 2.5, y + 9.5],
                [x + 6, y + 7.7], [x + 8, y + 4.5], [x + 8, y + 3.5],
                [x + 8, y + 3.5], [x + 8, y], [x + 5, y],
                [x + 3.5, y], [x + 2.5, y + 2.5], [x + 2.5, y + 2.5],
            ].map(p => new THREE.Vector3(...p, 0));
            for (let i = 0; i < points.length; i += 3) {
                shape.add(new THREE.CubicBezierCurve3(...points.slice(i, i + 4)));
            }

            const extrudeSettings = {
                steps: 100,  // ui: steps
                bevelEnabled: false,
                extrudePath: shape,
            };

            const geometry = new THREE.ExtrudeGeometry(outline, extrudeSettings);

이십면체 ( IcosahedronGeometry )

            const radius = 7;  // ui: radius
            const geometry = new THREE.IcosahedronGeometry(radius);

            const radius = 7;  // ui: radius
            const detail = 2;  // ui: detail
            const geometry = new THREE.IcosahedronGeometry(radius, detail);

선반형상 (LatheGeometry)

const points = [];
for (let i = 0; i < 10; ++i) {
  points.push(new THREE.Vector2(Math.sin(i * 0.2) * 3 + 3, (i - 5) * .8));
}
const geometry = new THREE.LatheGeometry(points);

 

const points = [];
for (let i = 0; i < 10; ++i) {
  points.push(new THREE.Vector2(Math.sin(i * 0.2) * 3 + 3, (i - 5) * .8));
}
const segments = 12;  // ui: segments
const phiStart = Math.PI * 0.25;  // ui: phiStart
const phiLength = Math.PI * 1.5;  // ui: phiLength
const geometry = new THREE.LatheGeometry(
    points, segments, phiStart, phiLength);

팔면체 (OctahedronGeometry)

const radius = 7;  // ui: radius
const geometry = new THREE.OctahedronGeometry(radius);

 

const radius = 7;  // ui: radius
const detail = 2;  // ui: detail
const geometry = new THREE.OctahedronGeometry(radius, detail);

2D 평면 ( PlaneGeometry )

const width = 9;  // ui: width
const height = 9;  // ui: height
const geometry = new THREE.PlaneGeometry(width, height);

 

const width = 9;  // ui: width
const height = 9;  // ui: height
const widthSegments = 2;  // ui: widthSegments
const heightSegments = 2;  // ui: heightSegments
const geometry = new THREE.PlaneGeometry(
    width, height,
    widthSegments, heightSegments);

 

다면체 (PolyhedronGeometry)

 

const verticesOfCube = [
    -1, -1, -1,    1, -1, -1,    1,  1, -1,    -1,  1, -1,
    -1, -1,  1,    1, -1,  1,    1,  1,  1,    -1,  1,  1,
];
const indicesOfFaces = [
    2, 1, 0,    0, 3, 2,
    0, 4, 7,    7, 3, 0,
    0, 1, 5,    5, 4, 0,
    1, 2, 6,    6, 5, 1,
    2, 3, 7,    7, 6, 2,
    4, 5, 6,    6, 7, 4,
];
const radius = 7;  // ui: radius
const detail = 2;  // ui: detail
const geometry = new THREE.PolyhedronGeometry(
    verticesOfCube, indicesOfFaces, radius, detail);

링 형상(Ring Geometry)

const innerRadius = 2;  // ui: innerRadius
const outerRadius = 7;  // ui: outerRadius
const thetaSegments = 18;  // ui: thetaSegments
const geometry = new THREE.RingGeometry(
    innerRadius, outerRadius, thetaSegments);

 

const innerRadius = 2;  // ui: innerRadius
const outerRadius = 7;  // ui: outerRadius
const thetaSegments = 18;  // ui: thetaSegments
const phiSegments = 2;  // ui: phiSegments
const thetaStart = Math.PI * 0.25;  // ui: thetaStart
const thetaLength = Math.PI * 1.5;  // ui: thetaLength
const geometry = new THREE.RingGeometry(
    innerRadius, outerRadius,
    thetaSegments, phiSegments,
    thetaStart, thetaLength);

 

윤관선 형상 ( Shape Geometry)

 

 

const shape = new THREE.Shape();
const x = -2.5;
const y = -5;
shape.moveTo(x + 2.5, y + 2.5);
shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
const geometry = new THREE.ShapeGeometry(shape);

 

const shape = new THREE.Shape();
const x = -2.5;
const y = -5;
shape.moveTo(x + 2.5, y + 2.5);
shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
const curveSegments = 5;  // ui: curveSegments
const geometry = new THREE.ShapeGeometry(shape, curveSegments);

 

구 형상 ( Sphere Geometry)

const radius = 7;  // ui: radius
const widthSegments = 12;  // ui: widthSegments
const heightSegments = 8;  // ui: heightSegments
const geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);

 

const radius = 7;  // ui: radius
const widthSegments = 12;  // ui: widthSegments
const heightSegments = 8;  // ui: heightSegments
const phiStart = Math.PI * 0.25;  // ui: phiStart
const phiLength = Math.PI * 1.5;  // ui: phiLength
const thetaStart = Math.PI * 0.25;  // ui: thetaStart
const thetaLength = Math.PI * 0.5;  // ui: thetaLength
const geometry = new THREE.SphereGeometry(
    radius,
    widthSegments, heightSegments,
    phiStart, phiLength,
    thetaStart, thetaLength);

사면체 (TetrahedronGeometry )

const radius = 7;  // ui: radius
const geometry = new THREE.TetrahedronGeometry(radius);

 

const radius = 7;  // ui: radius
const detail = 2;  // ui: detail
const geometry = new THREE.TetrahedronGeometry(radius, detail);

문자 형상 (TextGeometry)

문자형상의 경우 부득이하게, Json 파일을 로드해야하므로, 별도의 코드가 필요하다.

아래 코드로 변경 및, 기존 cube 코드가 에러 안나게 교체 필요

 

            function createMaterial() {
                const material = new THREE.MeshPhongMaterial({
                    side: THREE.DoubleSide,
                });

                const hue = Math.random();
                const saturation = 1;
                const luminance = .5;
                material.color.setHSL(hue, saturation, luminance);

                return material;
            }

            {
                const loader = new THREE.FontLoader();
                // promisify font loading
                function loadFont(url) {
                    return new Promise((resolve, reject) => {
                        loader.load(url, resolve, undefined, reject);
                    });
                }

                async function doit() {
                    const font = await loadFont('./helvetiker_regular.typeface.json');  /* threejsfundamentals: url */
                    const geometry = new THREE.TextGeometry('three.js', {
                        font: font,
                        size: 3.0,
                        height: .2,
                        curveSegments: 12,
                        bevelEnabled: true,
                        bevelThickness: 0.15,
                        bevelSize: .3,
                        bevelSegments: 5,
                    });
                    const mesh = new THREE.Mesh(geometry, createMaterial());
                    geometry.computeBoundingBox();
                    geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1);

                    const parent = new THREE.Object3D();
                    parent.add(mesh);

                    scene.add(parent);
                }
                doit();
            }

원환체 형상 ( TorusGeometry)

 

const radius = 5;  // ui: radius
const tubeRadius = 2;  // ui: tubeRadius
const radialSegments = 8;  // ui: radialSegments
const tubularSegments = 24;  // ui: tubularSegments
const geometry = new THREE.TorusGeometry(
    radius, tubeRadius,
    radialSegments, tubularSegments);

 

원환체 매듭 형상 (TorusKnotGeometry)

const radius = 3.5;  // ui: radius
const tubeRadius = 1.5;  // ui: tubeRadius
const radialSegments = 8;  // ui: radialSegments
const tubularSegments = 64;  // ui: tubularSegments
const p = 2;  // ui: p
const q = 3;  // ui: q
const geometry = new THREE.TorusKnotGeometry(
    radius, tubeRadius, tubularSegments, radialSegments, p, q);

 

튜브 형상 (TubeGeometry)

class CustomSinCurve extends THREE.Curve {
  constructor(scale) {
    super();
    this.scale = scale;
  }
  getPoint(t) {
    const tx = t * 3 - 1.5;
    const ty = Math.sin(2 * Math.PI * t);
    const tz = 0;
    return new THREE.Vector3(tx, ty, tz).multiplyScalar(this.scale);
  }
}

const path = new CustomSinCurve(4);
const tubularSegments = 20;  // ui: tubularSegments
const radius = 1;  // ui: radius
const radialSegments = 8;  // ui: radialSegments
const closed = false;  // ui: closed
const geometry = new THREE.TubeGeometry(
    path, tubularSegments, radius, radialSegments, closed);

 

엣지 형상 (EdgesGeometry)

 

const size = 8;
const widthSegments = 2;
const heightSegments = 2;
const depthSegments = 2;
const boxGeometry = new THREE.BoxGeometry(
    size, size, size,
    widthSegments, heightSegments, depthSegments);
const geometry = new THREE.EdgesGeometry(boxGeometry);

const radius = 7;
const widthSegments = 6;
const heightSegments = 3;
const sphereGeometry = new THREE.SphereGeometry(
    radius, widthSegments, heightSegments);
const thresholdAngle = 1;  // ui: thresholdAngle
const geometry = new THREE.EdgesGeometry(sphereGeometry, thresholdAngle);

와이어프레임 ( WireframeGeometry)

const size = 8;
const widthSegments = 2;  // ui: widthSegments
const heightSegments = 2;  // ui: heightSegments
const depthSegments = 2;  // ui: depthSegments
const geometry = new THREE.WireframeGeometry(
    new THREE.BoxGeometry(
      size, size, size,
      widthSegments, heightSegments, depthSegments));

이상 기본 도형의 표현이며,

실제 Three.js Fundamentals의 경우 약간의 형상의 차이,

그냥 실행하면 안되는 경우 들로 인하여, 약간 코드적으로 수정하여 표현 하였습니다.