我正试图让Three.js仅渲染几何的FrontSide
轮廓。我想要实现的是尽可能接近此外观:
使用BoxGeomtry
可以达到我想要的效果,但是在LineSegments
上使用CylinderGeometry
可以得到垂直线,这很有意义。您能想到我只能绘制“可见”轮廓的方法吗?
这是我到目前为止尝试过的:
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
})
);
答案 0 :(得分:2)
此答案基于@WestLangley对评论的建议,特别是LDrawLoader
在条件行使用的模型。
条件线背后的想法是使用控制点来确定应绘制哪些线。
如果两个控制点位于剪切平面的同一侧(通过将线外推至无穷大而创建),则将绘制该线。否则将其丢弃。
让我们考虑两行(E,B)和(F,C):
对于(E,B),让我们使用(A)和(C)作为控制点。我们可以清楚地看到两个控制点都在(E,B)创建的平面的同一侧。因此,绘制了这条线。
对于(F,C),让我们使用(B)和(D)作为控制点。现在,两个控制点都位于平面的不同侧。因此,该行被丢弃。
由于此模型的实现可能很长,因此我设置了一个JSFiddle可以用作参考。它不是很完美,但是我认为它应该足够有用。
我们不能使用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 );