Как отрендерить только ближайший уровень глубины прозрачного объекта в three.js? - PullRequest
0 голосов
/ 14 мая 2019

Я пытаюсь визуализировать трехмерный объект так, как он будет выглядеть как голограмма или рентгеновский снимок в webGL с использованием библиотеки three.js. Он должен быть прозрачным в центре (чтобы видеть фон и, возможно, позже некоторые объекты будут внутри этого объема) и иметь яркие непрозрачные цвета по краям. Лица задней скрытой стороны не должны быть обработаны. Я очень новичок в веб-графике, поэтому я не знаю, стоит ли мне работать с шейдером GLSL или играть с параметрами наложения. Извините за глупый вопрос.

Я могу достичь аналогичного результата, используя пользовательский световой шейдер в соответствии с этим учебным пособием . Но это не решает проблему с задними лицами. Я получил достаточное появление в Blender, создавая шейдер, который устраняет такие грани, ограничивая путь света глубиной прозрачности больше 0,5. Есть узлов моего материала Blender. Есть ли способ сделать подобное в WebGL? Скриншоты текущей ситуации и ожидаемой (вторая строка): здесь .

В настоящее время я использую OBJLoader, WebGLRenderer и ShaderMaterial из библиотеки three.js. Материал определяет как следующее. CustomShader.js:

const customBrainShader = () => { return {

uniforms: 
{ 
    "c":   { type: "f", value: 1.0 },
    "p":   { type: "f", value: 1.9 },
    glowColor: { type: "c", value: new THREE.Color(0xcfdfff) },
    viewVector: { type: "v3", value: new Vector3(0, 100, 400) }
},

    vertexShader: vertexShaderSource,
    fragmentShader: fragmentShaderSource,

    side: THREE.FrontSide,
    blending: THREE.AdditiveBlending,
    depthTest: true,
    depthWrite: true,
    opacity: 0.5
}};
export { customBrainShader };

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

 uniform vec3 glowColor;
 varying float intensity;

 void main() 
 {
  vec3 glow = glowColor * intensity;
  gl_FragColor = vec4( glow, 1.0 );
 }

Вершинный шейдер:

uniform vec3 viewVector;
uniform float c;
uniform float p;
varying float intensity;

void main() 
{
    vec3 vNormal = normalize( normalMatrix * normal );
vec3 vCamera = vec3(0.0,0.0,1.0);
intensity = pow( c - dot(vNormal, vCamera), p );
    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

Ответы [ 2 ]

1 голос
/ 15 мая 2019

Если я не ошибаюсь, приведенный ниже эффект - это то, чего вы пытаетесь достичь.

Здесь происходит несколько интересных вещей.

Во-первых, я устанавливаю renderer.autoClear = false, который не позволяет рендереру очищать свои буферы между вызовами на renderer.render. Это позволяет вызывать эту функцию несколько раз для записи в буферы несколько раз.

Далее я делаю именно это. Я рендерил одну и ту же сцену дважды. Но вы заметите, что в первый раз при рендеринге я устанавливаю scene.overrideMaterial, который заменяет все материалы в сцене с переопределением. Мне нужно сделать это по причинам, связанным с материалом переопределения.

В материале переопределения я устанавливаю colorWrite: false. Это означает, что, хотя объект будет «визуализирован», он не будет рисовать какие-либо цвета, поэтому нет видимого эффекта (пока). Он выполняет запись в буфер глубины, чего мы и хотим, потому что объект будет скрывать вещи за ним. Это как спрятать что-то за волшебный кусок стекла. (Я также установил здесь смещение многоугольника, чтобы избежать z-боя, что является совершенно другой темой, поэтому я не буду вдаваться в подробности этого ответа).

Наконец, я снова визуализирую сцену, используя материал шейдера, который вы определили. Рендеринг noColor перекрывает фигуры, которые должны быть закрыты, поэтому вы не получите нежелательного пролистывания, когда передняя грань находится позади другой части меша. Ваш шейдер обрабатывает все остальное, создавая эффект свечения.

// Your shader code
const fragmentShaderSource = `
 uniform vec3 glowColor;
 varying float intensity;

 void main() 
 {
  vec3 glow = glowColor * intensity;
  gl_FragColor = vec4( glow, 1.0 );
 }
`
const vertexShaderSource = `
uniform vec3 viewVector;
uniform float c;
uniform float p;
varying float intensity;

void main() 
{
  vec3 vNormal = normalize( normalMatrix * normal );
  vec3 vCamera = vec3(0.0,0.0,1.0);
  intensity = pow( c - dot(vNormal, vCamera), p );
  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`

const customBrainShader = new THREE.ShaderMaterial({
  uniforms: {
    c: {
      value: 1.0
    },
    p: {
      value: 1.9
    },
    glowColor: {
      value: new THREE.Color(0xcfdfff)
    },
    viewVector: {
      value: new THREE.Vector3(0, 100, 400)
    }
  },
  vertexShader: vertexShaderSource,
  fragmentShader: fragmentShaderSource,
  side: THREE.FrontSide,
  opacity: 0.5
})

// male02 model from the three.js examples
const modelPath = "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/obj/male02/male02.obj"

const renderer = new THREE.WebGLRenderer({
  antialias: true
})
renderer.autoClear = false
renderer.setSize(200, 200)
document.body.appendChild(renderer.domElement)

const scene = new THREE.Scene()

const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000)
camera.position.set(0, 90, 500)
const cameraTarget = new THREE.Vector3(0, 90, 0)
camera.lookAt(cameraTarget)

const light = new THREE.PointLight(0xffffff, 1)
camera.add(light)

scene.add(camera)

function render() {
  renderer.clear()
  scene.overrideMaterial = noColor
  renderer.render(scene, camera)
  scene.overrideMaterial = null
  renderer.render(scene, camera)
}

const axis = new THREE.Vector3(0, 1, 0)
const noColor = new THREE.MeshBasicMaterial({
  colorWrite: false,
  polygonOffset: true,
  polygonOffsetUnits: 1,
  polygonOffsetFactor: 1
})

function animate() {
  requestAnimationFrame(animate)
  camera.position.applyAxisAngle(axis, 0.0025)
  camera.lookAt(cameraTarget)
  render()
}
animate()

const loader = new THREE.OBJLoader()
loader.load(modelPath, (results) => {
  results.traverse(node => {
    if (node instanceof THREE.Mesh) {
      node.material = customBrainShader
    }
  })
  scene.add(results)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/104/three.js"></script>
<script src="https://threejs.org/examples/js/loaders/OBJLoader.js"></script>
0 голосов
/ 15 мая 2019

Как оказалось, моя идея с комментариями была не так плоха, как я думал. Пример JSFiddle

Чтобы создать глубинную окклюзионную отбраковку внутри вашего фрагментного шейдера, вам нужно создать уникальный WebGLRenderTarget с включенным глубинным буфером и глубиной текстуры.

target = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight );
target.texture.format = THREE.RGBFormat;;
target.stencilBuffer = false;
target.depthBuffer = true;
target.depthTexture = new THREE.DepthTexture();
target.depthTexture.type = THREE.UnsignedShortType;

Затем, внутри цикла анимации, вам нужно визуализировать обычную сцену с сетками, используя заполнитель MeshBasicMaterial, до WebGLRenderTarget, обновить унифицированную текстуру вашего пользовательского ShaderMaterial, чтобы использовать недавно визуализированную глубину текстуры.

Наконец, вы можете назначить ShaderMaterial для меша и нормально визуализировать сцену.

function animate() {

    requestAnimationFrame( animate );

    // render scene into target
    mesh.material = basicMaterial;
    renderer.setRenderTarget( target );
    renderer.render( scene, camera );

    // update custom shader uniform
    shaderMaterial.uniforms.tDepth.value = target.depthTexture;

    // render scene into scene
    mesh.material = shaderMaterial;
    renderer.setRenderTarget( null );
    renderer.render( scene, camera );

}

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

#include <packing>

uniform sampler2D tDepth;
uniform vec3 glowColor;
uniform vec2 viewportSize;
uniform float cameraNear;
uniform float cameraFar;

varying float intensity;

float readDepth( sampler2D depthSampler, vec2 coord ) {
    float fragCoordZ = texture2D( depthSampler, coord ).x;
    float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );
    return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
}

void main() {

    float zDepth = readDepth( tDepth, gl_FragCoord.xy / viewportSize.xy );
    float fragDepth = gl_FragCoord.z/gl_FragCoord.w/cameraFar;

    if ( fragDepth > zDepth + 0.001 ) discard; // 0.001 offset to prevent self-culling.

    vec3 glow = glowColor * intensity;
    gl_FragColor = vec4( glow, 1.0 );

}
...