如何在THREE.js中添加淡入淡出的反射效果?

3

我使用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个回答

5

我已经使用来自three.js论坛的修改过的THREE.Reflector版本更新了您的代码。

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>


它运行得非常完美,谢谢!我看到你修改了库反射器以添加淡入淡出支持。你认为还有其他方法可以在不重写整个代码的情况下实现吗?只是为了我的知识(虽然对于我的用例来说,代码运行得很好)。 - supersan
1
不行,Reflector 需要深度纹理才能实现这种效果。默认版本的 Reflector 不支持此功能(为了避免相应的开销,因为大多数用例不需要淡化效果)。 - Mugen87
非常感谢您的帮助。您能否解释一下如何仅对反射器添加模糊效果,而不是整个场景。这是我遇到的难题。 - Dezze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接