Как добавить затухающее отражение с помощью THREE.js ?

#javascript #three.js #3d

#javascript #three.js #3D

Вопрос:

Я создал эту вращающуюся книгу, используя THREE.js . К сожалению, у меня очень мало знаний об OpenGL, и поэтому я не могу понять, как сделать затухающее отражение.

 window.onload = function() {
  // Create the renderer and add it to the page's body element
  var renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.shadowMap.enabled = true;

  document.body.appendChild(renderer.domElement);

  // Create the scene to hold the object
  var scene = new THREE.Scene();

  // Create the camera
  var camera = new THREE.PerspectiveCamera(
    35, // Field of view
    window.innerWidth / window.innerHeight, // Aspect ratio
    0.1, // Near plane distance
    1000 // Far plane distance
  );

  // Position the camera
  camera.position.set(-15, 10, 20);
  camera.lookAt(scene.position);

  // Add the lights

  var light = new THREE.PointLight(0xffffff, 0.4);
  light.position.set(10, 10, 10);
  scene.add(light);

  ambientLight = new THREE.AmbientLight(0xbbbbbb);
  scene.add(ambientLight);

  // Load the textures (book images)
  var textureLoader = new THREE.TextureLoader();
  //front
  var bookCoverTexture = textureLoader.load("https://i.imgur.com/sBzG9bm.jpeg");
  //side
  var bookSpineTexture = textureLoader.load("https://i.imgur.com/ZFgZgnx.png");
  //back
  var bookBackTexture = textureLoader.load("https://i.imgur.com/s4Kbbr9.jpeg");
  //pages
  var bookPagesTexture = textureLoader.load("https://i.imgur.com/2Mul6cU.jpeg");
  //top   bottom pages
  var bookPagesTopBottomTexture = textureLoader.load(
    "https://i.imgur.com/OXCKChN.jpg"
  );

  // Use the linear filter for the textures to avoid blurriness
  bookCoverTexture.minFilter = bookSpineTexture.minFilter = bookBackTexture.minFilter = bookPagesTexture.minFilter = bookPagesTopBottomTexture.minFilter =
    THREE.LinearFilter;

  // Create the materials

  var bookCover = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookCoverTexture
  });
  var bookSpine = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookSpineTexture
  });
  var bookBack = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookBackTexture
  });
  var bookPages = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookPagesTexture
  });
  var bookPagesTopBottom = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookPagesTopBottomTexture
  });

  var materials = [
    bookPages, // Right side
    bookSpine, // Left side
    bookPagesTopBottom, // Top side
    bookPagesTopBottom, // Bottom side
    bookCover, // Front side
    bookBack // Back side
  ];

  // Create the book and add it to the scene
  var book = new THREE.Mesh(
    new THREE.BoxGeometry(7, 10, 2.2, 4, 4, 1),
    materials
  );
  book.layers.enable(1);
  scene.add(book);

  var geometry = new THREE.PlaneBufferGeometry(20, 20);

  var mirror = new THREE.Reflector(geometry, {
    clipBias: 0.003,
    textureWidth: 1024 * window.devicePixelRatio,
    textureHeight: 1024 * window.devicePixelRatio,
    color: 0x889999,
    recursion: 1
  });
  mirror.rotateX(-Math.PI / 2);
  mirror.position.y = -5.2;
  mirror.material.transparent = true;
  mirror.material.alpha = 0.1;

  scene.add(mirror);

  // Create the orbit controls for the camera
  controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.dampingFactor = 0.25;
  controls.enablePan = false;
  controls.enableZoom = false;

  // Begin the animation
  animate();

  /*
    Animate a frame
  */

  function animate() {
    book.rotation.y  = 0.01;

    // Update the orbit controls
    controls.update();

    // Render the frame
    renderer.render(scene, camera);

    // Keep the animation going
    requestAnimationFrame(animate);
  }
};  
     <script src="https://unpkg.com/three@0.111.0/build/three.js"></script>
    <script src="https://unpkg.com/three@0.111.0/examples/js/controls/OrbitControls.js"></script>
    <script src="https://unpkg.com/three@0.111.0/examples/js/objects/Reflector.js"></script>
    
<div style="padding: 20px; position: absolute;">
</div>  

После некоторых исследований я наткнулся на этот пост для создания затухающего отражения, но я не могу сделать это самостоятельно.

https://discourse.threejs.org/t/creating-a-fading-reflection/3831

Был бы признателен за небольшую помощь.

Ответ №1:

Я обновил ваш код модифицированной версией THREE.Reflector с three.js форума.

 window.onload = function() {
  // Create the renderer and add it to the page's body element
  var renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.shadowMap.enabled = true;

  document.body.appendChild(renderer.domElement);

  // Create the scene to hold the object
  var scene = new THREE.Scene();

  // Create the camera
  var camera = new THREE.PerspectiveCamera(
    35, // Field of view
    window.innerWidth / window.innerHeight, // Aspect ratio
    0.1, // Near plane distance
    1000 // Far plane distance
  );

  // Position the camera
  camera.position.set(-15, 10, 20);
  camera.lookAt(scene.position);

  // Add the lights

  var light = new THREE.PointLight(0xffffff, 0.4);
  light.position.set(10, 10, 10);
  scene.add(light);

  ambientLight = new THREE.AmbientLight(0xbbbbbb);
  scene.add(ambientLight);

  // Load the textures (book images)
  var textureLoader = new THREE.TextureLoader();
  //front
  var bookCoverTexture = textureLoader.load("https://i.imgur.com/sBzG9bm.jpeg");
  //side
  var bookSpineTexture = textureLoader.load("https://i.imgur.com/ZFgZgnx.png");
  //back
  var bookBackTexture = textureLoader.load("https://i.imgur.com/s4Kbbr9.jpeg");
  //pages
  var bookPagesTexture = textureLoader.load("https://i.imgur.com/2Mul6cU.jpeg");
  //top   bottom pages
  var bookPagesTopBottomTexture = textureLoader.load(
    "https://i.imgur.com/OXCKChN.jpg"
  );

  // Use the linear filter for the textures to avoid blurriness
  bookCoverTexture.minFilter = bookSpineTexture.minFilter = bookBackTexture.minFilter = bookPagesTexture.minFilter = bookPagesTopBottomTexture.minFilter =
    THREE.LinearFilter;

  // Create the materials

  var bookCover = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookCoverTexture
  });
  var bookSpine = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookSpineTexture
  });
  var bookBack = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookBackTexture
  });
  var bookPages = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookPagesTexture
  });
  var bookPagesTopBottom = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    map: bookPagesTopBottomTexture
  });

  var materials = [
    bookPages, // Right side
    bookSpine, // Left side
    bookPagesTopBottom, // Top side
    bookPagesTopBottom, // Bottom side
    bookCover, // Front side
    bookBack // Back side
  ];

  // Create the book and add it to the scene
  var book = new THREE.Mesh(
    new THREE.BoxGeometry(7, 10, 2.2, 4, 4, 1),
    materials
  );
  book.layers.enable(1);
  scene.add(book);

  var geometry = new THREE.PlaneBufferGeometry(20, 20);

  var mirror = new THREE.Reflector(geometry, {
    clipBias: 0.003,
    textureWidth: 1024 * window.devicePixelRatio,
    textureHeight: 1024 * window.devicePixelRatio,
    color: 0x889999,
    recursion: 1
  });
  mirror.rotateX(-Math.PI / 2);
  mirror.position.y = -5.2;
  mirror.material.transparent = true;
  mirror.material.alpha = 0.1;

  scene.add(mirror);

  // Create the orbit controls for the camera
  controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.dampingFactor = 0.25;
  controls.enablePan = false;
  controls.enableZoom = false;

  // Begin the animation
  animate();

  /*
    Animate a frame
  */

  function animate() {
    book.rotation.y  = 0.01;

    // Update the orbit controls
    controls.update();

    // Render the frame
    renderer.render(scene, camera);

    // Keep the animation going
    requestAnimationFrame(animate);
  }
};  
 <script src="https://unpkg.com/three@0.116.1/build/three.js"></script>
<script src="https://unpkg.com/three@0.116.1/examples/js/controls/OrbitControls.js"></script>

<script>
THREE.Reflector = function ( geometry, options ) {

    THREE.Mesh.call( this, geometry );

    this.type = 'Reflector';

    var scope = this;

    options = options || {};

    var color = ( options.color !== undefined ) ? new THREE.Color( options.color ) : new THREE.Color( 0x7F7F7F );
    var textureWidth = options.textureWidth || 512;
    var textureHeight = options.textureHeight || 512;
    var clipBias = options.clipBias || 0;
    var shader = options.shader || THREE.Reflector.ReflectorShader;

    //

    var reflectorPlane = new THREE.Plane();
    var normal = new THREE.Vector3();
    var reflectorWorldPosition = new THREE.Vector3();
    var cameraWorldPosition = new THREE.Vector3();
    var rotationMatrix = new THREE.Matrix4();
    var lookAtPosition = new THREE.Vector3( 0, 0, - 1 );
    var clipPlane = new THREE.Vector4();
    var viewport = new THREE.Vector4();

    var view = new THREE.Vector3();
    var target = new THREE.Vector3();
    var q = new THREE.Vector4();

    var textureMatrix = new THREE.Matrix4();
    var virtualCamera = new THREE.PerspectiveCamera();

    var parameters = {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        format: THREE.RGBFormat,
        stencilBuffer: false
    };

    var renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters );
    
    renderTarget.depthBuffer = true;
    renderTarget.depthTexture = new THREE.DepthTexture();
    renderTarget.depthTexture.type = THREE.UnsignedShortType;

    if ( ! THREE.Math.isPowerOfTwo( textureWidth ) || ! THREE.Math.isPowerOfTwo( textureHeight ) ) {

        renderTarget.texture.generateMipmaps = false;

    }

    var material = new THREE.ShaderMaterial( {
        uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
        fragmentShader: shader.fragmentShader,
        vertexShader: shader.vertexShader,
        transparent: true
    } );

    material.uniforms.tDiffuse.value = renderTarget.texture;
    material.uniforms.tDepth.value = renderTarget.depthTexture;
    material.uniforms.color.value = color;
    material.uniforms.textureMatrix.value = textureMatrix;

    this.material = material;

    this.onBeforeRender = function ( renderer, scene, camera ) {

        reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld );
        cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );

        rotationMatrix.extractRotation( scope.matrixWorld );

        normal.set( 0, 0, 1 );
        normal.applyMatrix4( rotationMatrix );

        view.subVectors( reflectorWorldPosition, cameraWorldPosition );

        // Avoid rendering when reflector is facing away

        if ( view.dot( normal ) > 0 ) return;

        view.reflect( normal ).negate();
        view.add( reflectorWorldPosition );

        rotationMatrix.extractRotation( camera.matrixWorld );

        lookAtPosition.set( 0, 0, - 1 );
        lookAtPosition.applyMatrix4( rotationMatrix );
        lookAtPosition.add( cameraWorldPosition );

        target.subVectors( reflectorWorldPosition, lookAtPosition );
        target.reflect( normal ).negate();
        target.add( reflectorWorldPosition );

        virtualCamera.position.copy( view );
        virtualCamera.up.set( 0, 1, 0 );
        virtualCamera.up.applyMatrix4( rotationMatrix );
        virtualCamera.up.reflect( normal );
        virtualCamera.lookAt( target );

        virtualCamera.far = camera.far; // Used in WebGLBackground

        virtualCamera.updateMatrixWorld();
        virtualCamera.projectionMatrix.copy( camera.projectionMatrix );

        this.material.uniforms.cameraNear.value = camera.near;
        this.material.uniforms.cameraFar.value = camera.far;

        // Update the texture matrix
        textureMatrix.set(
            0.5, 0.0, 0.0, 0.5,
            0.0, 0.5, 0.0, 0.5,
            0.0, 0.0, 0.5, 0.5,
            0.0, 0.0, 0.0, 1.0
        );
        textureMatrix.multiply( virtualCamera.projectionMatrix );
        textureMatrix.multiply( virtualCamera.matrixWorldInverse );
        textureMatrix.multiply( scope.matrixWorld );

        // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
        // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
        reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition );
        reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );

        clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant );

        var projectionMatrix = virtualCamera.projectionMatrix;

        q.x = ( Math.sign( clipPlane.x )   projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
        q.y = ( Math.sign( clipPlane.y )   projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];
        q.z = - 1.0;
        q.w = ( 1.0   projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];

        // Calculate the scaled plane vector
        clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) );

        // Replacing the third row of the projection matrix
        projectionMatrix.elements[ 2 ] = clipPlane.x;
        projectionMatrix.elements[ 6 ] = clipPlane.y;
        projectionMatrix.elements[ 10 ] = clipPlane.z   1.0 - clipBias;
        projectionMatrix.elements[ 14 ] = clipPlane.w;

        // Render

        renderTarget.texture.encoding = renderer.outputEncoding;

        scope.visible = false;

        var currentRenderTarget = renderer.getRenderTarget();

        var currentXrEnabled = renderer.xr.enabled;
        var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;

        renderer.xr.enabled = false; // Avoid camera modification
        renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows

        renderer.setRenderTarget( renderTarget );

        renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897

        if ( renderer.autoClear === false ) renderer.clear();
        renderer.render( scene, virtualCamera );

        renderer.xr.enabled = currentXrEnabled;
        renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;

        renderer.setRenderTarget( currentRenderTarget );
        // Restore viewport

        var bounds = camera.bounds;

        if ( bounds !== undefined ) {

            var size = renderer.getSize();
            var pixelRatio = renderer.getPixelRatio();

            viewport.x = bounds.x * size.width * pixelRatio;
            viewport.y = bounds.y * size.height * pixelRatio;
            viewport.z = bounds.z * size.width * pixelRatio;
            viewport.w = bounds.w * size.height * pixelRatio;

            renderer.state.viewport( viewport );

        }

        scope.visible = true;

    };

    this.getRenderTarget = function () {

        return renderTarget;

    };

};

THREE.Reflector.prototype = Object.create( THREE.Mesh.prototype );
THREE.Reflector.prototype.constructor = THREE.Reflector;

THREE.Reflector.ReflectorShader = {

    uniforms: {

        'color': {
            type: 'c',
            value: null
        },

        'tDiffuse': {
            type: 't',
            value: null
        },
        
        'tDepth': {
            type: 't',
            value: null
        },

        'textureMatrix': {
            type: 'm4',
            value: null
        },
        
        'cameraNear': {
            type: 'f',
            value: 0
        },
        
        'cameraFar': {
            type: 'f',
            value: 0
        },

    },

    vertexShader: [
        'uniform mat4 textureMatrix;',
        'varying vec4 vUv;',

        'void main() {',

        '   vUv = textureMatrix * vec4( position, 1.0 );',

        '   gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',

        '}'
    ].join( 'n' ),

    fragmentShader: [
        '#include <packing>',
        'uniform vec3 color;',
        'uniform sampler2D tDiffuse;',
        'uniform sampler2D tDepth;',
        'uniform float cameraNear;',
        'uniform float cameraFar;',
        'varying vec4 vUv;',

        'float blendOverlay( float base, float blend ) {',

        '   return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );',

        '}',

        'vec3 blendOverlay( vec3 base, vec3 blend ) {',

        '   return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );',

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

        'void main() {',

        '   vec4 base = texture2DProj( tDiffuse, vUv );',
        ' float depth = readDepth( tDepth, vUv );',
        '   gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 - ( depth * 50000.0 ) );',

        '}'
    ].join( 'n' )
};
</script>
    
<div style="padding: 20px; position: absolute;">
</div>  

Комментарии:

1. Он работает отлично, спасибо!. Я вижу, вы изменили стандартный отражатель, чтобы добавить поддержку затухания. Как вы думаете, есть ли какой-то другой способ сделать это, не переписывая все целиком? Просто для моих знаний (хотя код отлично подходит для моего варианта использования).

2. Нет, Reflector для достижения этого эффекта требуется текстура глубины. Это не поддерживается в версии по умолчанию Reflector (чтобы избежать соответствующих накладных расходов, поскольку большинство вариантов использования не требуют затухания).

3. Большое спасибо за это. Не могли бы вы также объяснить, как добавить размытие только к отражателю, а не ко всей сцене. Вот с чем я борюсь.