【问题标题】:three js blend two background image三个js混合两个背景图片
【发布时间】:2018-10-09 02:57:39
【问题描述】:

我做了一个游戏,它有一个带有绿色图像材质的地面网格,我想做的是当一个特定的事件发生时,比如说一个按钮被点击,背景图像可以逐渐变成另一个图像,比如混合,但我不知道threejs中的哪个函数可以做到这一点。

【问题讨论】:

  • 有几种方法可以实现它,例如编写自己的shader 以将两个图像平滑地混合在一起,但最简单的方法是制作带纹理的画布并简单地淡入图像.不过,这有很多边缘情况。

标签: three.js


【解决方案1】:

第一种方法是简单地使用canvas 作为纹理并重绘canvas 元素本身。这里的缺点是每次更改它的值时都需要将更新后的画布带入绘图缓冲区,因此并不理想,但可以工作(点击渲染器淡化纹理):

// I have compressed this function as its just an utility to just draw a circle in two colors to use as example images and prevent cross-origin issues.
function anImage( color1, color2 ){ var canvas = document.createElement( 'canvas' ), ctx = canvas.getContext( '2d' ); canvas.width = 256; canvas.height = 256; ctx.fillStyle = color1; ctx.fillRect( 0, 0, 400, 400 ); ctx.fillStyle = color2; ctx.arc( 200, 200, 200, 0, Math.PI * 2 ); ctx.fill(); return canvas; }

function drawTexture(){
	
	// Draw the first image full opaque
	
	ctx.globalAlpha = 1;

	ctx.drawImage( image1, 0, 0, 512, 512 );

	// Draw the second image as transparent as its transition requires
	
	ctx.globalAlpha = state;

	ctx.drawImage( image2, 0, 0, 512, 512 );

	texture.needsUpdate = true;

}
function render(){

	state = targetState >= state ? state + .01 : state - .01;
	state = state < 0 ? 0 : state > 1 ? 1 : state;

	drawTexture();
	
	cube.rotation.y += .01;

	renderer.render( scene, camera );

	window.requestAnimationFrame( render );

}

var image1 = anImage( 'red', 'yellow' );
var image2 = anImage( 'blue', 'green' );
var renderer = new THREE.WebGLRenderer;
var camera = new THREE.PerspectiveCamera;
var scene = new THREE.Scene;
var canvas = document.createElement( 'canvas' );
var ctx = canvas.getContext( '2d' );
var state = 0;
var targetState = 0;

// Lets set up our canvas for the texture

canvas.width = 512;
canvas.height = 512;

ctx.drawImage( image1, 0, 0, 512, 512 );

var texture = new THREE.Texture( canvas );
var cube = new THREE.Mesh(
  new THREE.BoxGeometry,
  new THREE.MeshBasicMaterial({ map: texture })
);

scene.add( cube );

camera.position.set( 0, 0, 5 );
camera.lookAt( scene.position );

renderer.domElement.addEventListener( 'click', event => {
	targetState = targetState === 1 ? 0 : 1
});
renderer.setSize( 512, 512 );

render();

document.body.appendChild( renderer.domElement );
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"&gt;&lt;/script&gt;

第二种方法是使用一个特殊的ShaderMaterial,你可以自己构造它。它要求一些制服(包括我们的textures)和一个我们可以修改的值,我们将调用fade,这将是我们的状态。然后我们需要编写自己的vertex 着色器——这很简单,我们只需使用我们可以在 THREE.js 文档中找到的默认位置。然后我们需要添加我们的fragment 着色器,它将为我们的像素着色。在这里,我们读取了我们的颜色并将它们混合在一起。我们还必须从vertex 着色器中引入uv,这样我们就知道在哪里寻找我们的像素。这种方法效率更高,因为纹理被缓冲一次,之后就再也不会被触及,让我们的 GPU 处理繁重的工作。

function anImage( color1, color2 ){ var canvas = document.createElement( 'canvas' ), ctx = canvas.getContext( '2d' ); canvas.width = 256; canvas.height = 256; ctx.fillStyle = color1; ctx.fillRect( 0, 0, 400, 400 ); ctx.fillStyle = color2; ctx.arc( 200, 200, 200, 0, Math.PI * 2 ); ctx.fill(); return canvas; }
function render(){

  state = targetState >= state ? state + .01 : state - .01;
  state = state < 0 ? 0 : state > 1 ? 1 : state;
	
  // Set the fade value in the uniforms
  cube.material.uniforms.fade.value = state;
  cube.rotation.y += .01;

  renderer.render( scene, camera );

  window.requestAnimationFrame( render );

}

var texture1 = new THREE.Texture( anImage( 'red', 'yellow' ) );
var texture2 = new THREE.Texture( anImage( 'blue', 'green' ) );
var renderer = new THREE.WebGLRenderer;
var camera = new THREE.PerspectiveCamera;
var scene = new THREE.Scene;
var canvas = document.createElement( 'canvas' );
var ctx = canvas.getContext( '2d' );
var state = 0;
var targetState = 0;

texture1.needsUpdate = true;
texture2.needsUpdate = true;

var cube = new THREE.Mesh(
  new THREE.BoxGeometry,
  new THREE.ShaderMaterial({
    uniforms: {
  	
      fade: { type: 'f', value: 0 },
      texture1: { value: texture1 },
      texture2: { value: texture2 }
  		
    },
    vertexShader: `

      // Define the uv we want to pass to our fragment shader
      varying vec2 vUv;
  		
      void main(){
  		
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
			
      }
		
    `,
    fragmentShader: `
  	
      // Read our uv and textures, as well as fade value
      uniform sampler2D texture1;
      uniform sampler2D texture2;
      uniform float fade;
      varying vec2 vUv;
  		
      void main(){
  		
        // Read our pixel colors as rgba
        vec4 t1 = texture2D( texture1, vUv );
        vec4 t2 = texture2D( texture2, vUv );
  			
        // Mix our pixels together based on fade value
        gl_FragColor = mix( t1, t2, fade );
  			
      }
  		
    `
  })
);

scene.add( cube );

camera.position.set( 0, 0, 5 );
camera.lookAt( scene.position );

renderer.domElement.addEventListener( 'click', event => {
  targetState = targetState === 1 ? 0 : 1
});
renderer.setSize( 512, 512 );

render();

document.body.appendChild( renderer.domElement );
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"&gt;&lt;/script&gt;

【讨论】:

  • shader代码好熟悉,code struct好用,棒棒哒!
  • 如果我使用两种颜色而不是两种纹理会怎样?我在片段着色器 vec4 类型中定义了制服,但它无法编译。代码要点在这里gist.github.com/haolly/02de6dc530f00343a725fe39852a0f3d
  • @liuhao 我回复了您的要点,但仅供将来参考:THREE.Color 在技术上是vec3,因为它仅包含rgb,因此您需要将它们转换为vec4 以将它们用作实际颜色。使用 vec4 color1 = vec4( myColor0, 1.0 ) 添加完整的 alpha 值。然后与vec4 合作。并确保将它们定义为uniform vec3 myColor0
猜你喜欢
  • 1970-01-01
  • 2013-11-20
  • 2021-12-10
  • 2017-06-21
  • 2015-07-05
  • 2020-06-29
  • 1970-01-01
  • 2013-05-14
  • 1970-01-01
相关资源
最近更新 更多