【问题标题】:How to get three.js LineSegments to only render visible lines如何让three.js LineSegments 只呈现可见线
【发布时间】:2019-06-29 00:41:28
【问题描述】:

我试图让 Three.js 只渲染几何图形的 FrontSide 轮廓。我想要实现的是尽可能接近这一点:

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

这是我目前尝试过的:

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

【问题讨论】:

  • 这可能是一个非常棘手的问题.. 只是提醒一下。一种部分方法是制作网格的 .clone() 并使用 meshBasicMaterial。通过 object.renderOrder 控制绘图顺序以使基本材质网格首先渲染..这会将其写入深度缓冲区,然后绘制您的线框...这将删除背面..但对圆柱体上的垂直线没有帮助边。
  • 另一种方法是将线条作为后期处理。渲染你的对象..然后运行一个全屏着色器,将深度缓冲区/或法线缓冲区作为输入,对于每个片段,查看相邻片段以查看法线/深度是否偏离某个阈值,如果为真则仅绘制像素.这更健壮/通用,但更难实现。
  • @Mary (1) 尝试 new THREE.EdgesGeometry( geometry, angle_in_degees ); 或 (2) 研究 conditionalLineVertShaderLDrawLoader 中的工作原理 threejs.org/examples/webgl_loader_ldraw.html`。
  • 谢谢大家!看起来像几个起点。我已经预料到它会是一个比看起来更复杂的问题。后处理似乎很难实现。我明天去看看。 ??????????
  • @WestLangley 感谢您指出 thresholdAngle。我没有注意到 EdgesGeometry 上的那个选项——看起来很有希望。

标签: three.js


【解决方案1】:

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


控制点模型

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

如果两个控制点位于剪裁平面的同一侧,通过将直线外推到无穷远而创建,则绘制直线。否则将被丢弃。

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

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

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


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

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

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

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

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

【讨论】:

  • 太棒了,非常感谢您的详细解释和小提琴!
【解决方案2】:

看起来你可以复制toon shader example中使用的方法

它不仅使用MeshToonMaterial作为纯色,还使用./jsm/effects/OutlineEffect.js中的OutlineEffect。可以看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 );

【讨论】:

  • 我认为 OP 正在寻找边缘轮廓而不是轮廓轮廓。
  • 是的,现在我看了上面 WestLangley 的评论,我认为他的例子可能更适合。
  • @Marquizzo 还是谢谢你——大纲总比没有好。 ;)
猜你喜欢
  • 1970-01-01
  • 2018-08-21
  • 1970-01-01
  • 2016-07-12
  • 2021-09-09
  • 2012-06-09
  • 1970-01-01
  • 2012-11-14
  • 2017-10-16
相关资源
最近更新 更多