=================================
=================================
=================================
WebGL 에서 굵은 선을 나타낸 매쉬를 이용하려고 "LineBasicMaterial"의 "lineWidth" 을 이용하려고 하는데
대략 코드는 이렇다.
let obj3DMeBG = new THREE.Object3D();
var materialBG = new THREE.LineBasicMaterial( { color: 0xaaff0f, opacity:1, linewidth:5 } );
var line = new THREE.Line(geometry, materialBG);
objMeBG.add(line);
하지만 "lineWidth" 선의 굵기 부분이 제대로 동작을 하지 않는것이다. 구글에 찾아보니 "lineWidth" 는 무슨 문제가 있어서
작동이 잘 되지 않는다고 하는 것이다. 그래서 대안이 Three를 이용한
JS "THREE.MeshLine"
링크: https://github.com/spite/THREE.MeshLine
코드다운로드:
여기에서 지원하는 THREE.MeshLine를 이용하면 된다.
여기서 보면 예제가 나오는 데로
var geometry= new THREE.Geometry();
geometry.vertices.push(
new THREE.Vector3( -0.5, 0.5, 0 ),
new THREE.Vector3( 0.5, 0.5, 0 ),
new THREE.Vector3( 0.5, -0.5, 0 ),
new THREE.Vector3( -0.5, -0.5, 0 ),
new THREE.Vector3( -0.5, 0.5, 0 ),
);
let gFig = new MeshLine();
gFig.setGeometry( geometry);
let obj3DMeBG = new THREE.Object3D();
let resolution = new THREE.Vector2( 800, 600); //화면비율 width, height
let opt = {
color: new THREE.Color( 0xff0000 ), //컬러 부분은 THREE.Color 써야한다.
opacity: 1,
resolution: resolution,
lineWidth: 1/1000, //화면대비 비율로 굵기가 정해진다. (대충 1000으로 정함)
};
var material = new MeshLineMaterial(OPTIONS);
let meshFig = new THREE.Mesh(gFig.geometry, material );
meshFig.position.set(0.0, 0.0, 0.0);
obj3DMeBG .add( meshFig );
이런식으로 하면된다.
=================================
=================================
=================================
JS "THREE.MeshLine" 모듈화 과련
링크: https://github.com/spite/THREE.MeshLine
이부분을 ES6 의 클래스을 사용 하여 모듈화부분으로 개조 하였다.
요즘 추세에 맞게 Class로 모듈화 하여 만들어 보았다.
파일링크:
수정 내용:
//import 부분에 자신의 JS라이브러리 Three부분을 링크한 위치를 적어두면 된다.
import * as THREE from "../lib/three.module.js"; //import * as THREE from "THREE URL";
export class MeshLineModule
{
constructor()
{
this.positions = [];
this.previous = [];
this.next = [];
this.side = [];
this.width = [];
this.indices_array = [];
this.uvs = [];
this.counters = [];
this.geometry = new THREE.BufferGeometry();
this.widthCallback = null;
}
setGeometry(g, c)
{
this.widthCallback = c;
this.positions = [];
this.counters = [];
if( g instanceof THREE.Geometry ) {
for( let j = 0; j < g.vertices.length; j++ ) {
let v = g.vertices[ j ];
let c = j/g.vertices.length;
this.positions.push( v.x, v.y, v.z );
this.positions.push( v.x, v.y, v.z );
this.counters.push(c);
this.counters.push(c);
}
}
if( g instanceof THREE.BufferGeometry ) {
// read attribute positions ?
}
if( g instanceof Float32Array || g instanceof Array ) {
for( let j = 0; j < g.length; j += 3 ) {
let c = j/g.length;
this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] );
this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] );
this.counters.push(c);
this.counters.push(c);
}
}
this.process();
}
compareV3( a, b )
{
let aa = a * 6;
let ab = b * 6;
return ( this.positions[ aa ] === this.positions[ ab ] ) && ( this.positions[ aa + 1 ] === this.positions[ ab + 1 ] ) && ( this.positions[ aa + 2 ] === this.positions[ ab + 2 ] );
}
copyV3( a )
{
let aa = a * 6;
return [ this.positions[ aa ], this.positions[ aa + 1 ], this.positions[ aa + 2 ] ];
}
process()
{
let l = this.positions.length / 6;
this.previous = [];
this.next = [];
this.side = [];
this.width = [];
this.indices_array = [];
this.uvs = [];
for( let j = 0; j < l; j++ ) {
this.side.push( 1 );
this.side.push( -1 );
}
let w;
for( let j = 0; j < l; j++ ) {
if( this.widthCallback ) w = this.widthCallback( j / ( l -1 ) );
else w = 1;
this.width.push( w );
this.width.push( w );
}
for( let j = 0; j < l; j++ ) {
this.uvs.push( j / ( l - 1 ), 0 );
this.uvs.push( j / ( l - 1 ), 1 );
}
let v;
if( this.compareV3( 0, l - 1 ) ){
v = this.copyV3( l - 2 );
} else {
v = this.copyV3( 0 );
}
this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
for( let j = 0; j < l - 1; j++ ) {
v = this.copyV3( j );
this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
}
for( let j = 1; j < l; j++ ) {
v = this.copyV3( j );
this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
}
if( this.compareV3( l - 1, 0 ) ){
v = this.copyV3( 1 );
} else {
v = this.copyV3( l - 1 );
}
this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
for( let j = 0; j < l - 1; j++ ) {
let n = j * 2;
this.indices_array.push( n, n + 1, n + 2 );
this.indices_array.push( n + 2, n + 1, n + 3 );
}
if (!this.attributes) {
this.attributes = {
position: new THREE.BufferAttribute( new Float32Array( this.positions ), 3 ),
previous: new THREE.BufferAttribute( new Float32Array( this.previous ), 3 ),
next: new THREE.BufferAttribute( new Float32Array( this.next ), 3 ),
side: new THREE.BufferAttribute( new Float32Array( this.side ), 1 ),
width: new THREE.BufferAttribute( new Float32Array( this.width ), 1 ),
uv: new THREE.BufferAttribute( new Float32Array( this.uvs ), 2 ),
index: new THREE.BufferAttribute( new Uint16Array( this.indices_array ), 1 ),
counters: new THREE.BufferAttribute( new Float32Array( this.counters ), 1 )
}
} else {
this.attributes.position.copyArray(new Float32Array(this.positions));
this.attributes.position.needsUpdate = true;
this.attributes.previous.copyArray(new Float32Array(this.previous));
this.attributes.previous.needsUpdate = true;
this.attributes.next.copyArray(new Float32Array(this.next));
this.attributes.next.needsUpdate = true;
this.attributes.side.copyArray(new Float32Array(this.side));
this.attributes.side.needsUpdate = true;
this.attributes.width.copyArray(new Float32Array(this.width));
this.attributes.width.needsUpdate = true;
this.attributes.uv.copyArray(new Float32Array(this.uvs));
this.attributes.uv.needsUpdate = true;
this.attributes.index.copyArray(new Uint16Array(this.indices_array));
this.attributes.index.needsUpdate = true;
}
this.geometry.addAttribute( 'position', this.attributes.position );
this.geometry.addAttribute( 'previous', this.attributes.previous );
this.geometry.addAttribute( 'next', this.attributes.next );
this.geometry.addAttribute( 'side', this.attributes.side );
this.geometry.addAttribute( 'width', this.attributes.width );
this.geometry.addAttribute( 'uv', this.attributes.uv );
this.geometry.addAttribute( 'counters', this.attributes.counters );
this.geometry.setIndex( this.attributes.index );
}
memcpy (src, srcOffset, dst, dstOffset, length)
{
let i;
src = src.subarray || src.slice ? src : src.buffer
dst = dst.subarray || dst.slice ? dst : dst.buffer
src = srcOffset ? src.subarray ?
src.subarray(srcOffset, length && srcOffset + length) :
src.slice(srcOffset, length && srcOffset + length) : src
if (dst.set) {
dst.set(src, dstOffset)
} else {
for (i=0; i<src.length; i++) {
dst[i + dstOffset] = src[i]
}
}
return dst
}
/**
* Fast method to advance the line by one position. The oldest position is removed.
* @param position
*/
advance( position )
{
let positions = this.attributes.position.array;
let previous = this.attributes.previous.array;
let next = this.attributes.next.array;
let l = positions.length;
// PREVIOUS
this.memcpy( positions, 0, previous, 0, l );
// POSITIONS
this.memcpy( positions, 6, positions, 0, l - 6 );
positions[l - 6] = position.x;
positions[l - 5] = position.y;
positions[l - 4] = position.z;
positions[l - 3] = position.x;
positions[l - 2] = position.y;
positions[l - 1] = position.z;
// NEXT
this.memcpy( positions, 6, next, 0, l - 6 );
next[l - 6] = position.x;
next[l - 5] = position.y;
next[l - 4] = position.z;
next[l - 3] = position.x;
next[l - 2] = position.y;
next[l - 1] = position.z;
this.attributes.position.needsUpdate = true;
this.attributes.previous.needsUpdate = true;
this.attributes.next.needsUpdate = true;
}
//MeshLineMaterial.prototype = Object.create( THREE.Material.prototype );
//MeshLineMaterial.prototype.constructor = MeshLineMaterial;
}
export class MeshLineMaterialModule extends THREE.Material
{
constructor( parameters = {} )
{
super();
let vertexShaderSource = [
'precision highp float;',
'',
'attribute vec3 position;',
'attribute vec3 previous;',
'attribute vec3 next;',
'attribute float side;',
'attribute float width;',
'attribute vec2 uv;',
'attribute float counters;',
'',
'uniform mat4 projectionMatrix;',
'uniform mat4 modelViewMatrix;',
'uniform vec2 resolution;',
'uniform float lineWidth;',
'uniform vec3 color;',
'uniform float opacity;',
'uniform float near;',
'uniform float far;',
'uniform float sizeAttenuation;',
'',
'varying vec2 vUV;',
'varying vec4 vColor;',
'varying float vCounters;',
'',
'vec2 fix( vec4 i, float aspect ) {',
'',
' vec2 res = i.xy / i.w;',
' res.x *= aspect;',
' vCounters = counters;',
' return res;',
'',
'}',
'',
'void main() {',
'',
' float aspect = resolution.x / resolution.y;',
' float pixelWidthRatio = 1. / (resolution.x * projectionMatrix[0][0]);',
'',
' vColor = vec4( color, opacity );',
' vUV = uv;',
'',
' mat4 m = projectionMatrix * modelViewMatrix;',
' vec4 finalPosition = m * vec4( position, 1.0 );',
' vec4 prevPos = m * vec4( previous, 1.0 );',
' vec4 nextPos = m * vec4( next, 1.0 );',
'',
' vec2 currentP = fix( finalPosition, aspect );',
' vec2 prevP = fix( prevPos, aspect );',
' vec2 nextP = fix( nextPos, aspect );',
'',
' float pixelWidth = finalPosition.w * pixelWidthRatio;',
' float w = 1.8 * pixelWidth * lineWidth * width;',
'',
' if( sizeAttenuation == 1. ) {',
' w = 1.8 * lineWidth * width;',
' }',
'',
' vec2 dir;',
' if( nextP == currentP ) dir = normalize( currentP - prevP );',
' else if( prevP == currentP ) dir = normalize( nextP - currentP );',
' else {',
' vec2 dir1 = normalize( currentP - prevP );',
' vec2 dir2 = normalize( nextP - currentP );',
' dir = normalize( dir1 + dir2 );',
'',
' vec2 perp = vec2( -dir1.y, dir1.x );',
' vec2 miter = vec2( -dir.y, dir.x );',
' //w = clamp( w / dot( miter, perp ), 0., 4. * lineWidth * width );',
'',
' }',
'',
' //vec2 normal = ( cross( vec3( dir, 0. ), vec3( 0., 0., 1. ) ) ).xy;',
' vec2 normal = vec2( -dir.y, dir.x );',
' normal.x /= aspect;',
' normal *= .5 * w;',
'',
' vec4 offset = vec4( normal * side, 0.0, 1.0 );',
' finalPosition.xy += offset.xy;',
'',
' gl_Position = finalPosition;',
'',
'}' ];
let fragmentShaderSource = [
'#extension GL_OES_standard_derivatives : enable',
'precision mediump float;',
'',
'uniform sampler2D map;',
'uniform sampler2D alphaMap;',
'uniform float useMap;',
'uniform float useAlphaMap;',
'uniform float useDash;',
'uniform float dashArray;',
'uniform float dashOffset;',
'uniform float dashRatio;',
'uniform float visibility;',
'uniform float alphaTest;',
'uniform vec2 repeat;',
'',
'varying vec2 vUV;',
'varying vec4 vColor;',
'varying float vCounters;',
'',
'void main() {',
'',
' vec4 c = vColor;',
' if( useMap == 1. ) c *= texture2D( map, vUV * repeat );',
' if( useAlphaMap == 1. ) c.a *= texture2D( alphaMap, vUV * repeat ).a;',
' if( c.a < alphaTest ) discard;',
' if( useDash == 1. ){',
' c.a *= ceil(mod(vCounters + dashOffset, dashArray) - (dashArray * dashRatio));',
' }',
' gl_FragColor = c;',
' gl_FragColor.a *= step(vCounters, visibility);',
'}' ];
this.lineWidth = this.check( parameters.lineWidth, 1 );
this.map = this.check( parameters.map, null );
this.useMap = this.check( parameters.useMap, 0 );
this.alphaMap = this.check( parameters.alphaMap, null );
this.useAlphaMap = this.check( parameters.useAlphaMap, 0 );
this.color = this.check( parameters.color, new THREE.Color( 0xffffff ) );
this.opacity = this.check( parameters.opacity, 1 );
this.resolution = this.check( parameters.resolution, new THREE.Vector2( 1, 1 ) );
this.sizeAttenuation = this.check( parameters.sizeAttenuation, 1 );
this.near = this.check( parameters.near, 1 );
this.far = this.check( parameters.far, 1 );
this.dashArray = this.check( parameters.dashArray, 0 );
this.dashOffset = this.check( parameters.dashOffset, 0 );
this.dashRatio = this.check( parameters.dashRatio, 0.5 );
this.useDash = ( this.dashArray !== 0 ) ? 1 : 0;
this.visibility = this.check( parameters.visibility, 1 );
this.alphaTest = this.check( parameters.alphaTest, 0 );
this.repeat = this.check( parameters.repeat, new THREE.Vector2( 1, 1 ) );
let material = new THREE.RawShaderMaterial( {
uniforms:{
lineWidth: { type: 'f', value: this.lineWidth },
map: { type: 't', value: this.map },
useMap: { type: 'f', value: this.useMap },
alphaMap: { type: 't', value: this.alphaMap },
useAlphaMap: { type: 'f', value: this.useAlphaMap },
color: { type: 'c', value: this.color },
opacity: { type: 'f', value: this.opacity },
resolution: { type: 'v2', value: this.resolution },
sizeAttenuation: { type: 'f', value: this.sizeAttenuation },
near: { type: 'f', value: this.near },
far: { type: 'f', value: this.far },
dashArray: { type: 'f', value: this.dashArray },
dashOffset: { type: 'f', value: this.dashOffset },
dashRatio: { type: 'f', value: this.dashRatio },
useDash: { type: 'f', value: this.useDash },
visibility: {type: 'f', value: this.visibility},
alphaTest: {type: 'f', value: this.alphaTest},
repeat: { type: 'v2', value: this.repeat }
},
vertexShader: vertexShaderSource.join( '\r\n' ),
fragmentShader: fragmentShaderSource.join( '\r\n' )
});
delete parameters.lineWidth;
delete parameters.map;
delete parameters.useMap;
delete parameters.alphaMap;
delete parameters.useAlphaMap;
delete parameters.color;
delete parameters.opacity;
delete parameters.resolution;
delete parameters.sizeAttenuation;
delete parameters.near;
delete parameters.far;
delete parameters.dashArray;
delete parameters.dashOffset;
delete parameters.dashRatio;
delete parameters.visibility;
delete parameters.alphaTest;
delete parameters.repeat;
material.type = 'MeshLineMaterial';
material.setValues( parameters );
return material;
}
check( v, d )
{
if( v === undefined ) return d;
return v;
}
copy( source )
{
super.copy( this, source );
this.lineWidth = source.lineWidth;
this.map = source.map;
this.useMap = source.useMap;
this.alphaMap = source.alphaMap;
this.useAlphaMap = source.useAlphaMap;
this.color.copy( source.color );
this.opacity = source.opacity;
this.resolution.copy( source.resolution );
this.sizeAttenuation = source.sizeAttenuation;
this.near = source.near;
this.far = source.far;
this.dashArray.copy( source.dashArray );
this.dashOffset.copy( source.dashOffset );
this.dashRatio.copy( source.dashRatio );
this.useDash = source.useDash;
this.visibility = source.visibility;
this.alphaTest = source.alphaTest;
this.repeat.copy( source.repeat );
return this;
}
}
위에 JS파일을 import 하여 아래 코드예제참고하여 같이 써주면 된다.
사용예제) [모듈화로 이름을 모듈('module'을 붙여 바꾸었다) ]
import * as THREELINE from "../lib/THREE.MeshLine.module.js";
class CTest
{
//코드들...
//..
TestLine()
{
//코드들...
//..
var geometry= new THREE.Geometry();
geometry.vertices.push(
new THREE.Vector3( -0.5, 0.5, 0 ),
new THREE.Vector3( 0.5, 0.5, 0 ),
new THREE.Vector3( 0.5, -0.5, 0 ),
new THREE.Vector3( -0.5, -0.5, 0 ),
new THREE.Vector3( -0.5, 0.5, 0 ),
);
let gFig = new THREELINE.MeshLineModule();
gFig.setGeometry( geometry);
let obj3DMeBG = new THREE.Object3D();
let resolution = new THREE.Vector2( 800, 600); //화면비율 width, height
let opt = {
color: new THREE.Color( 0xff0000 ), //컬러 부분은 THREE.Color 써야한다.
opacity: 1,
resolution: resolution,
lineWidth: 1/1000, //화면대비 비율로 굵기가 정해진다. (대충 1000으로 정함)
};
var material = new THREELINE.MeshLineMaterialModule(OPTIONS);
let meshFig = new THREE.Mesh(gFig.geometry, material );
meshFig.position.set(0.0, 0.0, 0.0);
obj3DMeBG .add( meshFig );
}
}
=================================
=================================
=================================
'WEB > WebGL' 카테고리의 다른 글
[WebGL] Three.js - Three 예제 모음 링크들 (0) | 2020.09.19 |
---|---|
[html5] webgl+html5 로 만든 에뮬레이터 (0) | 2020.09.15 |
[WebGL] Three.js ES6이상의 모듈화 방식으로 사용하기 관련 (0) | 2018.04.11 |
[WebGL] Three.js, Canvas - 3DText, 2DText, Font, 텍스트 Draw, Render (1) | 2017.12.28 |
[WebGL] Three.js, CSS3D 등등 - 유용라이브러리 관련 사이트 Workshop: dat.GUI (0) | 2017.12.21 |