如何获取three.js LineSegments仅渲染可见线

时间:2019-06-27 16:12:31

标签: three.js

我正试图让Three.js仅渲染几何的FrontSide轮廓。我想要实现的是尽可能接近此外观:

enter image description here

使用BoxGeomtry可以达到我想要的效果,但是在LineSegments上使用CylinderGeometry可以得到垂直线,这很有意义。您能想到我只能绘制“可见”轮廓的方法吗?

Cylinder with LineSegments here

这是我到目前为止尝试过的:

let coloredMaterial = new THREE.MeshBasicMaterial({
  color: 0xFFD033,
  polygonOffset: true,
  polygonOffsetFactor: 1,
  polygonOffsetUnits: 1
});

let brick = new THREE.Mesh(geometry, coloredMaterial);

let edges = new THREE.EdgesGeometry(brick.geometry);
var outline = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({
  color: 0x1B3740,
  linewidth: 1.5
}));

let knobGeometry = new THREE.CylinderGeometry(7, 7, 7, 20);
let knob = new THREE.Mesh(knobGeometry, coloredMaterial);

let knobOutline = new THREE.LineSegments(
  new THREE.EdgesGeometry(knob.geometry),
  new THREE.LineBasicMaterial({
    color: 0x1B3740,
    linewidth: 1.5
  })
);

2 个答案:

答案 0 :(得分:2)

此答案基于@WestLangley对评论的建议,特别是LDrawLoader在条件行使用的模型。


控制点模型

条件线背后的想法是使用控制点来确定应绘制哪些线。

如果两个控制点位于剪切平面的同一侧(通过将线外推至无穷大而创建),则将绘制该线。否则将其丢弃。

https://www.ldraw.org/uploads/images/Articles/opline.gif

让我们考虑两行(E,B)(F,C)

对于(E,B),让我们使用(A)(C)作为控制点。我们可以清楚地看到两个控制点都在(E,B)创建的平面的同一侧。因此,绘制了这条线。

对于(F,C),让我们使用(B)(D)作为控制点。现在,两个控制点都位于平面的不同侧。因此,该行被丢弃。


由于此模型的实现可能很长,因此我设置了一个JSFiddle可以用作参考。它不是很完美,但是我认为它应该足够有用。

enter image description here

我们不能使用CylinderBufferGeometry作为边缘几何的基础,因为它使用索引缓冲区几何。由于控制点是由每条线而不是顶点确定的,因此我们不使用索引。

对于无条件的边,我们可以对两个控件使用相同的点,例如顶部和底部的圆。

需要注意的重要一点是,对于这种模型,我们无法确定几何形状(您所说的frontSide)是否会阻塞一条线。因此,我使用了实际的旋钮来遮盖后排线。

<html>

<head>

	<title> Edges Outline </title>

	<style>
		body { margin: 0; position: fixed;}
		canvas { width: 100%; height: 100%; display: block;}
	</style>

	<script src="https://threejs.org/build/three.js"></script>
	<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

</head>
<body>

	<script>

		var conditionalLineVertShader = /* glsl */`

		attribute vec3 control0;
		attribute vec3 control1;
		attribute vec3 direction;

		varying float discardFlag;

		#include <common>
		#include <color_pars_vertex>
		#include <fog_pars_vertex>
		#include <logdepthbuf_pars_vertex>
		#include <clipping_planes_pars_vertex>

		void main() {

		#include <color_vertex>

		vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
		gl_Position = projectionMatrix * mvPosition;

		// Transform the line segment ends and control points into camera clip space
		vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 );
		vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 );
		vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
		vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + 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 ) );

		discardFlag = float( sign( d0 ) != sign( d1 ) );

		#include <logdepthbuf_vertex>
		#include <clipping_planes_vertex>
		#include <fog_vertex>

		}
		`;

		var conditionalLineFragShader = /* glsl */`

		uniform vec3 diffuse;
		varying float discardFlag;

		#include <common>
		#include <color_pars_fragment>
		#include <fog_pars_fragment>
		#include <logdepthbuf_pars_fragment>
		#include <clipping_planes_pars_fragment>

		void main() {

		if ( discardFlag > 0.5 ) discard;

		#include <clipping_planes_fragment>

		vec3 outgoingLight = vec3( 0.0 );
		vec4 diffuseColor = vec4( diffuse, 1.0 );

		#include <logdepthbuf_fragment>
		#include <color_fragment>

		outgoingLight = diffuseColor.rgb; // simple shader

		gl_FragColor = vec4( outgoingLight, diffuseColor.a );

		#include <premultiplied_alpha_fragment>
		#include <tonemapping_fragment>
		#include <encodings_fragment>
		#include <fog_fragment>

		}
		`;

	</script>

	<script>

		var renderer = new THREE.WebGLRenderer( { antialias: true } );
		renderer.setSize( window.innerWidth, window.innerHeight );
		document.body.appendChild( renderer.domElement );

		var scene = new THREE.Scene();
		scene.background = new THREE.Color( 0xffffff );
		var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
		var controls = new THREE.OrbitControls( camera, renderer.domElement );

		camera.position.set( 10, 13, 10 );
		controls.target.set( 0, 0, 0 );


		// cube

		var cubeGeometry = new THREE.BoxBufferGeometry( 10, 5, 10 );
		var cubeMaterial = new THREE.MeshBasicMaterial( {
			color: 0xFFD033,
			polygonOffset: true,
			polygonOffsetFactor: 1,
			polygonOffsetUnits: 1
		} );
		var cube = new THREE.Mesh( cubeGeometry, cubeMaterial );
		scene.add( cube );

		var edgesGeometry = new THREE.EdgesGeometry( cubeGeometry );
		var edgesCube = new THREE.LineSegments( edgesGeometry, new THREE.LineBasicMaterial( { color: 0x1B3740, linewidth: 1.5 } ) );
		edgesCube.position.y += 0.6;
		scene.add( edgesCube );


		// knob

		var knobGeometry = new THREE.CylinderGeometry( 1.4, 1.4, 0.8, 30 );
		var knob = new THREE.Mesh(knobGeometry, cubeMaterial);
		knob.position.set( -2.5, 2.9, -2.5 );
		scene.add( knob );

		var knob = new THREE.Mesh(knobGeometry, cubeMaterial);
		knob.position.set( 2.5, 2.9, 2.5 );
		scene.add( knob );

		var knob = new THREE.Mesh(knobGeometry, cubeMaterial);
		knob.position.set( -2.5, 2.9, 2.5 );
		scene.add( knob );

		var knob = new THREE.Mesh(knobGeometry, cubeMaterial);
		knob.position.set( 2.5, 2.9, -2.5 );
		scene.add( knob );


		// knob edges

		var edgesMaterial = new THREE.ShaderMaterial( {
			vertexShader: conditionalLineVertShader,
			fragmentShader: conditionalLineFragShader,
			uniforms: {
				diffuse: { value: new THREE.Color( 0x1B3740 ) }
			},
			linewidth: 1.5
		} );

		var edgesKnob = createCylinderEdges( 1.4, 0.8, 30 );
		edgesKnob.position.set( -2.5, 2.9 + 0.6, -2.5 );
		scene.add( edgesKnob );

		var edgesKnob = createCylinderEdges( 1.4, 0.8, 30 );
		edgesKnob.position.set( 2.5, 2.9 + 0.6, 2.5 );
		scene.add( edgesKnob );

		var edgesKnob = createCylinderEdges( 1.4, 0.8, 30 );
		edgesKnob.position.set( -2.5, 2.9 + 0.6, 2.5 );
		scene.add( edgesKnob );

		var edgesKnob = createCylinderEdges( 1.4, 0.8, 30 );
		edgesKnob.position.set( 2.5, 2.9 + 0.6, -2.5 );
		scene.add( edgesKnob );


		window.addEventListener( 'resize', onResize );


		function animate() {

			requestAnimationFrame( animate );
			controls.update();
			renderer.render( scene, camera );

		};


		function createCylinderEdges( radius, height, segments ) {

			var geometry = new THREE.BufferGeometry();

			var v0 = new THREE.Vector3();
			var v1 = new THREE.Vector3();

			var vertices = [];
			var control0 = [];
			var control1 = [];
			var directions = [];

			// top / bottom circles

			for ( var v = 0; v <= 1; v ++ ) {

				for ( var x = 0; x < segments; x ++ ) {

					var th = ( x / segments ) * Math.PI * 2;
					var c0 = ( (x-1) / segments ) * Math.PI * 2;
					var c1 = ( (x+1) / segments ) * Math.PI * 2;

					var sinTheta = Math.sin( th );
					var cosTheta = Math.cos( th );

					v0.x = radius * sinTheta;
					v0.y = - v * height + height/2;
					v0.z = radius * cosTheta;

					sinTheta = Math.sin( c1 );
					cosTheta = Math.cos( c1 );

					v1.x = radius * sinTheta;
					v1.y = - v * height + height/2;
					v1.z = radius * cosTheta;

					vertices.push( v0.x, v0.y, v0.z );
					vertices.push( v1.x, v1.y, v1.z );

					control0.push( v0.x, v0.y, v0.z );
					control0.push( v0.x, v0.y, v0.z );

					control1.push( v0.x, v0.y, v0.z );
					control1.push( v0.x, v0.y, v0.z );

					directions.push( v1.x - v0.x, v1.y - v0.y, v1.z - v0.z );
					directions.push( v1.x - v0.x, v1.y - v0.y, v1.z - v0.z );

				}


			}

			// vertical edges

			for ( var x = 0; x < segments; x ++ ) {

				var th = ( x / segments ) * Math.PI * 2;
				var c0 = ( (x-1) / segments ) * Math.PI * 2;
				var c1 = ( (x+1) / segments ) * Math.PI * 2;

				var sinTheta = Math.sin( th );
				var cosTheta = Math.cos( th );

				v0.x = radius * sinTheta;
				v0.y = height/2;
				v0.z = radius * cosTheta;

				v1.x = radius * sinTheta;
				v1.y = - height + height/2;
				v1.z = radius * cosTheta;

				vertices.push( v0.x, v0.y, v0.z );
				vertices.push( v1.x, v1.y, v1.z );

				directions.push( v1.x - v0.x, v1.y - v0.y, v1.z - v0.z );
				directions.push( v1.x - v0.x, v1.y - v0.y, v1.z - v0.z );

				var sinTheta = Math.sin( c0 );
				var cosTheta = Math.cos( c0 );

				v0.x = radius * sinTheta;
				v0.y = height/2;
				v0.z = radius * cosTheta;

				control0.push( v0.x, v0.y, v0.z );
				control0.push( v0.x, v0.y, v0.z );

				var sinTheta = Math.sin( c1 );
				var cosTheta = Math.cos( c1 );

				v0.x = radius * sinTheta;
				v0.y = height/2;
				v0.z = radius * cosTheta;

				control1.push( v0.x, v0.y, v0.z );
				control1.push( v0.x, v0.y, v0.z );

			}


			geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
			geometry.addAttribute( 'control0', new THREE.Float32BufferAttribute( control0, 3, false ) );
			geometry.addAttribute( 'control1', new THREE.Float32BufferAttribute( control1, 3, false ) );
			geometry.addAttribute( 'direction', new THREE.Float32BufferAttribute( directions, 3, false ) );

			return new THREE.LineSegments( geometry, edgesMaterial );

		}

		function onResize() {

			var w = window.innerWidth;
			var h = window.innerHeight;

			camera.aspect = w / h;
			camera.updateProjectionMatrix();

			renderer.setSize( w, h );

		}

		animate();

	</script>

</body>

</html>

答案 1 :(得分:0)

您似乎可以复制toon shader example

中使用的方法

它不仅使用MeshToonMaterial作为纯色,而且还使用OutlineEffect中的./jsm/effects/OutlineEffect.js。您可以看到in the source code,相关要点是:

import { OutlineEffect } from './jsm/effects/OutlineEffect.js';

// Pass renderer to effect constructor
renderer = new THREE.WebGLRenderer();
effect = new OutlineEffect( renderer );

// In the render loop:
effect.render( scene, camera );