<script>
	var conditionalLineVertShader = /* glsl */`
	attribute vec3 control0;
	attribute vec3 control1;
	attribute vec3 direction;
  attribute float collapse;
  attribute vec3 instPos;

	#include <common>
	#include <color_pars_vertex>
	#include <fog_pars_vertex>
	#include <logdepthbuf_pars_vertex>
	#include <clipping_planes_pars_vertex>
	void main() {
		#include <color_vertex>

		// Transform the line segment ends and control points into camera clip space
		vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0 + instPos, 1.0 );
		vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1 + instPos, 1.0 );
		vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position + instPos, 1.0 );
		vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + instPos + direction, 1.0 );

		c0.xy /= c0.w;
		c1.xy /= c1.w;
		p0.xy /= p0.w;
		p1.xy /= p1.w;

		// Get the direction of the segment and an orthogonal vector
		vec2 dir = p1.xy - p0.xy;
		vec2 norm = vec2( -dir.y, dir.x );

		// Get control point directions from the line
		vec2 c0dir = c0.xy - p1.xy;
		vec2 c1dir = c1.xy - p1.xy;

		// If the vectors to the controls points are pointed in different directions away
		// from the line segment then the line should not be drawn.
		float d0 = dot( normalize( norm ), normalize( c0dir ) );
		float d1 = dot( normalize( norm ), normalize( c1dir ) );
		float discardFlag = float( sign( d0 ) != sign( d1 ) );
    
    vec3 p = position + instPos + ((discardFlag > 0.5) ? direction * collapse : vec3(0));    
    vec4 mvPosition = modelViewMatrix * vec4( p, 1.0 );
		gl_Position = projectionMatrix * mvPosition;

		#include <logdepthbuf_vertex>
		#include <clipping_planes_vertex>
		#include <fog_vertex>
	}
	`;

	var conditionalLineFragShader = /* glsl */`
	uniform vec3 diffuse;
	uniform float opacity;

	#include <common>
	#include <color_pars_fragment>
	#include <fog_pars_fragment>
	#include <logdepthbuf_pars_fragment>
	#include <clipping_planes_pars_fragment>
	void main() {
		#include <clipping_planes_fragment>
		vec3 outgoingLight = vec3( 0.0 );
		vec4 diffuseColor = vec4( diffuse, opacity );
		#include <logdepthbuf_fragment>
		#include <color_fragment>
		outgoingLight = diffuseColor.rgb; // simple shader
		gl_FragColor = vec4( outgoingLight, diffuseColor.a );
		#include <tonemapping_fragment>
		#include <encodings_fragment>
		#include <fog_fragment>
		#include <premultiplied_alpha_fragment>
	}
	`;
</script>
body{
  overflow: hidden;
  margin: 0;
}
import * as THREE from 'https://cdn.skypack.dev/[email protected]';
import {OrbitControls} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls.js';
import {GLTFLoader} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/loaders/GLTFLoader.js';
import {GUI} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/libs/dat.gui.module.js';

function EdgesGeometry( geometry, thresholdAngle ) {

	let g = new THREE.InstancedBufferGeometry();

	g.type = 'EdgesGeometry';

	g.parameters = {
		thresholdAngle: thresholdAngle
	};

	thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1;

	// buffer

	const vertices = [];
  const control0 = [];
  const control1 = [];
  const direction = [];
  const collapse = [];

	// helper variables

	const thresholdDot = Math.cos( THREE.MathUtils.DEG2RAD * thresholdAngle );
	const edge = [ 0, 0 ], edges = {};
	let edge1, edge2, key;
	const keys = [ 'a', 'b', 'c' ];

	// prepare source geometry

	let geometry2;

	if ( geometry.isBufferGeometry ) {

		geometry2 = new THREE.Geometry();
		geometry2.fromBufferGeometry( geometry );

	} else {

		geometry2 = geometry.clone();

	}

	geometry2.mergeVertices();
	geometry2.computeFaceNormals();

	const sourceVertices = geometry2.vertices;
	const faces = geometry2.faces;

	// now create a data structure where each entry represents an edge with its adjoining faces

	for ( let i = 0, l = faces.length; i < l; i ++ ) {

		const face = faces[ i ];

		for ( let j = 0; j < 3; j ++ ) {

			edge1 = face[ keys[ j ] ];
			edge2 = face[ keys[ ( j + 1 ) % 3 ] ];
			edge[ 0 ] = Math.min( edge1, edge2 );
			edge[ 1 ] = Math.max( edge1, edge2 );

			key = edge[ 0 ] + ',' + edge[ 1 ];

			if ( edges[ key ] === undefined ) {

				edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ], face1: i, face2: undefined };

			} else {

				edges[ key ].face2 = i;

			}

		}

	}

	// generate vertices
  const v3 = new THREE.Vector3();
  const n = new THREE.Vector3();
  const n1 = new THREE.Vector3();
  const n2 = new THREE.Vector3();
  const d = new THREE.Vector3();
	for ( key in edges ) {

		const e = edges[ key ];

		// an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.

		if ( e.face2 === undefined || faces[ e.face1 ].normal.dot( faces[ e.face2 ].normal ) <= thresholdDot ) {

			let vertex1 = sourceVertices[ e.index1 ];
      let vertex2 = sourceVertices[ e.index2 ];
      
			vertices.push( vertex1.x, vertex1.y, vertex1.z );
      vertices.push( vertex2.x, vertex2.y, vertex2.z );
      
      d.subVectors(vertex2, vertex1);
      collapse.push(0, 1);
      n.copy(d).normalize();
      direction.push(d.x, d.y, d.z);
      n1.copy(faces[ e.face1 ].normal);
      n1.crossVectors(n, n1);
      d.subVectors(vertex1, vertex2);
      n.copy(d).normalize();
      n2.copy(faces[ e.face2 ].normal);
      n2.crossVectors(n, n2);
      direction.push(d.x, d.y, d.z);
      
      v3.copy(vertex1).add(n1); // control0
      control0.push(v3.x, v3.y, v3.z);
      v3.copy(vertex1).add(n2); // control1
      control1.push(v3.x, v3.y, v3.z);
      
      v3.copy(vertex2).add(n1); // control0
      control0.push(v3.x, v3.y, v3.z);
      v3.copy(vertex2).add(n2); // control1
      control1.push(v3.x, v3.y, v3.z);
    }

	}

	// build geometry

	g.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
  g.setAttribute( 'control0', new THREE.Float32BufferAttribute( control0, 3 ) );
  g.setAttribute( 'control1', new THREE.Float32BufferAttribute( control1, 3 ) );
  g.setAttribute( 'direction', new THREE.Float32BufferAttribute( direction, 3 ) );
  g.setAttribute( 'collapse', new THREE.Float32BufferAttribute( collapse, 1 ) );
  return g;

}

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 10000);
camera.position.set(300, 80, 0);
camera.lookAt(scene.position);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setClearColor(0x444444);
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

let controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 80, 0);
controls.update();

var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.setScalar(10);
scene.add(light);
scene.add(new THREE.DirectionalLight(0xffffff, 0.5));

var loader = new GLTFLoader();
loader.load( "https://threejs.org/examples/models/gltf/Horse.glb", function ( gltf ) {
  
  let container = new THREE.Group();
  
  let mesh = gltf.scene.children[ 0 ];
  mesh.material.color.set(0x222222);
  mesh.material.vertexColors = false;
  mesh.material.polygonOffset = true;
  mesh.material.polygonOffsetFactor = 1;
  
  let outline = createOutlineSegments(mesh.geometry, 0xffffff);
  let instPos = [];
  
  var instanceObj = new THREE.InstancedMesh( mesh.geometry, mesh.material, 20 )

const matrix = new THREE.Matrix4();


instanceObj.instanceMatrix.setUsage( THREE.DynamicDrawUsage );

for ( let bm = 0; bm < 20 ; bm ++ ) {

      randomizeMatrixA( matrix, instPos );
      instanceObj.setMatrixAt( bm, matrix );

}
   console.log(instPos);
    outline.geometry.setAttribute("instPos", new THREE.InstancedBufferAttribute(new Float32Array(instPos), 3));
    console.log(outline.geometry);
   container.add(instanceObj);
  container.add(outline);

  //container.add(outline);
  //container.add(mesh);
  
  scene.add(container);
	console.log(mesh);
  
  let gui = new GUI();
  gui.add(mesh, "visible").name("mesh");
  gui.add(outline, "visible").name("outline");

} );

function randomizeMatrixA( matrix, instPos ) {
  
  var trashPosition = new THREE.Vector3();
  var scale = new THREE.Vector3()
const quaternion = new THREE.Quaternion();


            trashPosition.x = Math.random() * 800 - 400;
            trashPosition.y = Math.random() * 800 - 400;
            trashPosition.z = Math.random() * 800 - 400;
  
            instPos.push(trashPosition.x, trashPosition.y, trashPosition.z);
            scale.x = scale.y = scale.z = 1;

            matrix.compose( trashPosition, quaternion, scale );
            
     
}

renderer.setAnimationLoop(()=>{
  renderer.render(scene, camera);
});

function createOutlineSegments(geometry, color){
  let eg = EdgesGeometry(geometry);
  let m = new THREE.ShaderMaterial({
    vertexShader: conditionalLineVertShader,
    fragmentShader: conditionalLineFragShader,
    uniforms: {
      diffuse: {
        value: new THREE.Color(color)
      },
      opacity: {
        value: 0
      }
    },
    transparent: false
  });
  let o = new THREE.LineSegments(eg, m);
  return o;
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.