Трехмерная пунктирная линия относительно камеры - PullRequest
0 голосов
/ 04 февраля 2019

Я работаю над отображением геометрических фигур в 3D, используя three.js.

Когда вы рисуете (вручную) скрытые линии в виде пунктирных линий, «черточки» являются регулярными для всех них.Это означает, что линия, параллельная плоскости камеры или линия (почти), перпендикулярная плоскости камеры, должна иметь одинаковую длину и зазор.

Но, похоже, это не работает с LineDashedMaterial.

Для прилагаемого примера я использую этот (очень) базовый код:

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();

renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

var geometry = new THREE.BoxGeometry( 2, 2, 2 );
var LINES_DASHED = new THREE.LineSegments(
    new THREE.EdgesGeometry(geometry),
    new THREE.LineDashedMaterial({
        linewidth: 2,
        color: 0x000000,
        dashSize: 0.2,
        gapSize: 0.1,
        depthTest: false,
        polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: 1
    })
);
LINES_DASHED.computeLineDistances();
scene.add( LINES_DASHED );
scene.background = new THREE.Color( 0xffffff);
camera.position.z = 5;

var animate = function () {
    requestAnimationFrame( animate );
    LINES_DASHED.rotation.x += 0.01;
    LINES_DASHED.rotation.y += 0.01;
    renderer.render( scene, camera );
};

animate();
body { margin: 0; }
canvas { width: 100%; height: 100% }
<script src="https://threejs.org/build/three.min.js"></script>

Рабочий пример:

https://bs4.scolcours.ch/_dev/3js_ex.php

Я думаю, что используя:

line.computeLineDistance();

решит проблему.Но кажется, что он рассчитывает длину линии в трехмерном пространстве (что кажется логичным).

Есть что-то, что я пропустил?

Спасибо за помощь!

1 Ответ

0 голосов
/ 04 февраля 2019

Это задача за границей.Кажется, что THREE.LineDashedMaterial не поддерживает это.
Но можно написать шейдер и использовать THREE.ShaderMaterial.

Хитрость заключается в том, чтобы узнать начало строки в фрагментном шейдере.В общем, это легко, используя flat спецификатор интерполяции.
К сожалению WebGL 1.0 / GLSL ES 1.00 не поддерживает это.Поэтому мы должны использовать WebGL 2.0 / GLSL ES 3.00 .Возможно, кто-то может дать здесь информацию и знает расширение WebGL / OES, которое также поддерживает квалификатор flat.

Так что давайте создадим THREE.WebGLRenderer с контекстом WebGL2.См. Как использовать WebGL2 :

var canvas = document.createElement( 'canvas' );
var context = canvas.getContext( 'webgl2' );
var renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );

Вершинный шейдер должен передать нормализованную координату устройства фрагментному шейдеру.Один раз с интерполяцией по умолчанию и один раз без (flat) интерполяции.Это приводит к тому, что в оттенке фрагмента первый входной параметр содержит координату NDC фактической позиции на линии, а затем координату NDC начала строки.

flat out vec3 startPos;
out vec3 vertPos;

void main() {
    vec4 pos    = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    gl_Position = pos;
    vertPos     = pos.xyz / pos.w;
    startPos    = vertPos;
}

Кроме того, переменные входыФрагмент шейдера имеет одинаковые переменные.u_resolution содержит ширину и высоту области просмотра.u_dashSize содержит длину строки и u_gapSize длину пробела в пикселях.

Таким образом, длина строки от начала до фактического фрагмента может быть рассчитана:

vec2  dir  = (vertPos.xy-startPos.xy) * u_resolution/2.0;
float dist = length(dir);

И фрагмент на gab можно сбросить командой discard.

if (fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize))
    discard; 

Фрагмент шейдера:

precision highp float;

flat in vec3 startPos;
in vec3 vertPos;

uniform vec3  u_color;
uniform vec2  u_resolution;
uniform float u_dashSize;
uniform float u_gapSize;

void main(){

    vec2  dir  = (vertPos.xy-startPos.xy) * u_resolution/2.0;
    float dist = length(dir);

    if ( fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize) )
        discard; 
    gl_FragColor = vec4(u_color.rgb, 1.0);
}

Настройка THREE.ShaderMaterial и униформа:

var uniforms = {
    u_resolution: {type: 'v2', value: {x: vpSize[0], y: vpSize[1]}},
    u_dashSize : {type:'f', value: 10.0},
    u_gapSize : {type:'f', value: 5.0},
    u_color : {type: 'v3', value: {x:0.0, y:0.0, z:0.0} }
};

var material = new THREE.ShaderMaterial({  
        uniforms: uniforms,
        vertexShader: document.getElementById('vertex-shader').textContent,
        fragmentShader: document.getElementById('fragment-shader').textContent
});

var LINES_DASHED = new THREE.LineSegments(
    new THREE.EdgesGeometry(geometry),
    material);

Обратите внимание, что при изменении разрешения холста необходимо установить значения u_resolution:

например,

LINES_DASHED.material.uniforms.u_resolution.value.x = window.innerWidth;
LINES_DASHED.material.uniforms.u_resolution.value.y = window.innerHeight;

Я применил предложения к вашему исходному коду.Смотрите предварительный просмотр и пример:

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 60, window.innerWidth/window.innerHeight, 0.1, 1000 );
var canvas = document.createElement( 'canvas' );
var context = canvas.getContext( 'webgl2' );
var renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );

var vpSize = [window.innerWidth, window.innerHeight];
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

var geometry = new THREE.BoxGeometry( 2, 2, 2 );

var uniforms = {
    u_resolution: {type: 'v2', value: {x: vpSize[0], y: vpSize[1]}},
    u_dashSize : {type:'f', value: 10.0},
    u_gapSize : {type:'f', value: 5.0},
    u_color : {type: 'v3', value: {x:0.0, y:0.0, z:0.0} }
};
    
var material = new THREE.ShaderMaterial({  
        uniforms: uniforms,
        vertexShader: document.getElementById('vertex-shader').textContent,
        fragmentShader: document.getElementById('fragment-shader').textContent
});

var LINES_DASHED = new THREE.LineSegments(
    new THREE.EdgesGeometry(geometry),
    material);

LINES_DASHED.computeLineDistances();
scene.add( LINES_DASHED );
scene.background = new THREE.Color( 0xffffff);
camera.position.z = 5;

var animate = function () {
    requestAnimationFrame( animate );
    LINES_DASHED.rotation.x += 0.01;
    LINES_DASHED.rotation.y += 0.01;
    renderer.render( scene, camera );
};

window.onresize = function() {
    vpSize = [window.innerWidth, window.innerHeight];
    LINES_DASHED.material.uniforms.u_resolution.value.x = window.innerWidth;
    LINES_DASHED.material.uniforms.u_resolution.value.y = window.innerHeight;
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
}

animate();
<script type='x-shader/x-vertex' id='vertex-shader'>
flat out vec3 startPos;
out vec3 vertPos;

void main() {
    vec4 pos    = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    gl_Position = pos;
    vertPos     = pos.xyz / pos.w;
    startPos    = vertPos;
}
</script>

<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;

flat in vec3 startPos;
in vec3 vertPos;

uniform vec3  u_color;
uniform vec2  u_resolution;
uniform float u_dashSize;
uniform float u_gapSize;

void main(){

    vec2  dir  = (vertPos.xy-startPos.xy) * u_resolution.xy/2.0;
    float dist = length(dir);
    
    if (fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize))
        discard; 
    gl_FragColor = vec4(u_color.rgb, 1.0);
}
</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js"></script>
...