如何让three.js LineSegments只渲染可见线 [英] How to get three.js LineSegments to only render visible lines

查看:63
本文介绍了如何让three.js LineSegments只渲染可见线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I’m trying to get Three.js to render only the FrontSide outlines of geometries. What I want to achieve is a look as close as possible to this:

使用 BoxGeomtry 我接近我想要的结果,但是在 CylinderGeometry 上使用 LineSegments 会产生垂直线,这是有道理的.你能想出一种方法,我只能画出可见"的轮廓吗?

With BoxGeomtry I came close to what I want, but using LineSegments on a CylinderGeometry gives vertical lines, which makes sense. Can you think of a way I can draw only the "visible" outlines?

这是我目前尝试过的:

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
  })
);

推荐答案

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

This answer is based on @WestLangley's suggestion on the comments, specifically the model used by LDrawLoader on conditional lines.

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

The idea behind conditional lines is to use control points to determine which lines should be drawn.

如果两个控制点位于裁剪平面的同一侧,通过将线外推到无穷大来创建,则绘制该线.否则丢弃.

If the two control points lie on the same side of the clip plane, created by extrapolating the line to infinity, then the line is drawn. Else it is discarded.

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

Let's consider 2 lines (E,B) and (F,C):

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

For (E,B), let's use (A) and (C) as controls points. We can clearly see that both control points are on the same side of the plane created by (E,B). Therefore, this line is drawn.

对于(F,C),我们使用(B)(D) 作为控制点.现在,两个控制点都位于平面的不同侧.所以,这条线被丢弃了.

For (F,C), let's use (B) and (D) as controls points. Now, both control points lie on different sides of the plane. So, this line is discarded.

由于这个模型的实现可能很长,我设置了一个可以使用的

As the implementation of this model can be quite lengthy, I've setup a JSFiddle that can be used as a reference. It's not quite perfect, but I believe it should be helpful enough.

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

对于非条件边缘,我们可以为两个控件使用相同的点,例如顶部和底部的圆圈.

We can't use CylinderBufferGeometry as a base for the edges geometry, because it uses an indexed buffer geometry. Since the control points are determined by each of the lines, and not the vertices, we don't use indices.

需要注意的重要一点是,使用此模型,我们无法确定一条线是否会被几何图形(您描述为 frontSide)遮挡.所以,我使用了实际的旋钮来遮挡背部线条.

For edges that aren't conditional, we can use the same point for both controls, e.g top and bottom circles.


<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>

这篇关于如何让three.js LineSegments只渲染可见线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆