THREE混合计算

328 阅读4分钟

ThreeJS混合计算

webgl_材料_混合

源码分析案例

import * as THREE from "three";
let camera, scene, renderer;
let mapBg;
const textureLoader = new THREE.TextureLoader();
init();
animate();
function init() {
	camera = new THREE.PerspectiveCamera(
		70,
		window.innerWidth / window.innerHeight,
		1,
		1000
	);
	camera.position.z = 600;
	scene = new THREE.Scene();
	// 这里将canvas纹理转为材质供场景的背景使用,重复排列
	const canvas = document.createElement("canvas");
	const ctx = canvas.getContext("2d");
	canvas.width = canvas.height = 128;
	ctx.fillStyle = "#ddd";
	ctx.fillRect(0, 0, 128, 128);
	ctx.fillStyle = "#555";
	ctx.fillRect(0, 0, 64, 64);
	ctx.fillStyle = "#999";
	ctx.fillRect(32, 32, 32, 32);
	ctx.fillStyle = "#555";
	ctx.fillRect(64, 64, 64, 64);
	ctx.fillStyle = "#777";
	ctx.fillRect(96, 96, 32, 32);
	// 这里将canvas纹理转为材质供场景的背景使用,重复排列
	mapBg = new THREE.CanvasTexture(canvas);
	mapBg.colorSpace = THREE.SRGBColorSpace;
	mapBg.wrapS = mapBg.wrapT = THREE.RepeatWrapping;
	// 决定纹理在表面的重复次数,两个方向分别表示U和V,
	// 如果重复次数在任何方向上设置了超过1的数值
	// 对应的Wrap需要设置为THREE.RepeatWrapping或者THREE.MirroredRepeatWrapping来达到想要的平铺效果。
	mapBg.repeat.set(64, 32); // U canvas重复64次,V canvas重复32次
	// 若不为空,在渲染场景的时候将设置背景,且背景总是首先被渲染的。
	// 可以设置一个用于的“clear”的Color(颜色)、
	// 一个覆盖canvas的Texture(纹理), 或是
	//  a cubemap as a CubeTexture or an equirectangular as a Texture。默认值为null。
	scene.background = mapBg;

	// 这里事混合的算法
	const blendings = [
		{ name: "No", constant: THREE.NoBlending },
		{ name: "Normal", constant: THREE.NormalBlending },
		{ name: "Additive", constant: THREE.AdditiveBlending },
		{ name: "Subtractive", constant: THREE.SubtractiveBlending },
		{ name: "Multiply", constant: THREE.MultiplyBlending },
	];

	const assignSRGB = (texture) => {
		texture.colorSpace = THREE.SRGBColorSpace;
	};
	const map0 = textureLoader.load("textures/uv_grid_opengl.jpg", assignSRGB);
	const map1 = textureLoader.load("textures/sprite0.jpg", assignSRGB);
	const map2 = textureLoader.load("textures/sprite0.png", assignSRGB);
	const map3 = textureLoader.load(
		"textures/lensflare/lensflare0.png", //
		assignSRGB
	);
	const map4 = textureLoader.load(
		"textures/lensflare/lensflare0_alpha.png",
		assignSRGB
	);
	const geo1 = new THREE.PlaneGeometry(100, 100);
	const geo2 = new THREE.PlaneGeometry(100, 25);
	addImageRow(map0, 300); // 文字
	addImageRow(map1, 150); // 文字
	addImageRow(map2, 0); // 文字
	addImageRow(map3, -150); // 文字
	addImageRow(map4, -300); // 文字

	function addImageRow(map, y) {
		for (let i = 0; i < blendings.length; i++) {
			const blending = blendings[i];
			const material = new THREE.MeshBasicMaterial({ map: map });
			material.transparent = true;
			material.blending = blending.constant;
			const x = (i - blendings.length / 2) * 110;
			const z = 0;
			let mesh = new THREE.Mesh(geo1, material);
			mesh.position.set(x, y, z);
			scene.add(mesh);
			mesh = new THREE.Mesh(geo2, generateLabelMaterial(blending.name));
			mesh.position.set(x, y - 75, z);
			scene.add(mesh);
		}
	}

	renderer = new THREE.WebGLRenderer();
	renderer.setPixelRatio(window.devicePixelRatio);
	renderer.setSize(window.innerWidth, window.innerHeight);
	renderer.useLegacyLights = false;
	document.body.appendChild(renderer.domElement);
	window.addEventListener("resize", onWindowResize);
}

function onWindowResize() {
	const SCREEN_WIDTH = window.innerWidth;
	const SCREEN_HEIGHT = window.innerHeight;
	renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
	camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
	camera.updateProjectionMatrix();
}
function generateLabelMaterial(text) {
	const canvas = document.createElement("canvas");
	const ctx = canvas.getContext("2d");
	canvas.width = 128;
	canvas.height = 32;
	ctx.fillStyle = "rgba( 0, 0, 0, 0.95 )";
	ctx.fillRect(0, 0, 128, 32);
	ctx.fillStyle = "white";
	ctx.font = "bold 12pt arial";
	ctx.fillText(text, 10, 22);
	const map = new THREE.CanvasTexture(canvas);
	map.colorSpace = THREE.SRGBColorSpace;
	const material = new THREE.MeshBasicMaterial({
		map: map,
		transparent: true,
	});
	return material;
}

function animate() {
	requestAnimationFrame(animate);
	const time = Date.now() * 0.00025;
	const ox = (time * -0.01 * mapBg.repeat.x) % 1;
	const oy = (time * -0.01 * mapBg.repeat.y) % 1;
	// 贴图单次重复中的起始偏移量,分别表示U和V。 一般范围是由0.0到1.0。
	mapBg.offset.set(ox, oy);
	renderer.render(scene, camera);
}

运行结果

左图为右图平铺的底图,采用canvas作为纹理

canvas的效果 混合运行效果

结论:

  1. THREE可以使用Canvas作为纹理转为材质 【CanvasTexture】;

  2. scene.background不为null时,在渲染场景的时候背景总是首先被渲染的。【速度快,第一个被渲染】

  3. mapBg.offset.set(ox, oy); 贴图单次重复中的起始偏移量,分别表示U和V。 一般范围是由0.0到1.0。

  4. mapBg.wrapS = mapBg.wrapT = THREE.RepeatWrapping/THREE.MirroredRepeatWrapping/ClampToEdgeWrapping官网给出很明确的三个意思:整体纹理不停重复(类似复制),整体纹理不停重复(类似复制)然而是镜像重复,整体纹理的最后一个像素延伸到边缘(我去掉了网格,因为网格范围很广例如:模型、几何的边缘等,案例中是延伸到场景的边缘)。

    • 使用RepeatWrapping,纹理将简单地重复到无穷大;
    • 使用MirroredRepeatWrapping, 纹理将重复到无穷大,在每次重复时将进行镜像;
    • 使用ClampToEdgeWrapping是默认值,纹理中的最后一个像素将延伸到网格的边缘;
  5. 混合其实就是一种颜色计算,将材质内部颜色采取指定计算模式。如何实现呢?【案例为例】

    1. MeshBasicMaterial extends Material 通过MeshBasicMaterial 构造函数中的this.setValues( parameters ){this[ key ] = newValue}实现修改this.blending的值;

    2. 回到案例源码let mesh = new THREE.Mesh(geometry, material) Mesh对象其实没干啥事就是绑定内部this上的属性:this.geometry = geometry;this.material = material;

    3. 回到案例源码scene.add(mesh)添加到场景,源码就是对mesh类型判断是否为数组,mesh类型是否是否isobject,是否与当前对象相同(相同就不允许添加),最后this.children.push( object );

    4. 回到源码renderer.render(scene, camera)分析跟踪发现代码,回到了webGL知识或者OpenGL知识,这里讲的篇幅很大,想看往下划吧。【思考:THREE中一个材质很多颜色,我们该怎么知道哪个是源,哪个是目标】

switch (blending) {
	case NormalBlending:
		gl.blendFuncSeparate(
			gl.ONE,
			gl.ONE_MINUS_SRC_ALPHA,
			gl.ONE,
			gl.ONE_MINUS_SRC_ALPHA
		);
		break;

	case AdditiveBlending:
		gl.blendFunc(gl.ONE, gl.ONE);
		break;

	case SubtractiveBlending:
		gl.blendFuncSeparate(gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE);
		break;

	case MultiplyBlending:
		gl.blendFuncSeparate(gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA);
		break;

	default:
		console.error("THREE.WebGLState: Invalid blending: ", blending);
		break;
}
  1. 思考:我认为THREE源属性指Color属性,目标材质属性map。因为我后面修改Color属性、目标删除map改为specularMap等都表现不出来目标,只有源Color属性进行了计算。还有一点大家要知道,WebGl渲染看上去一下子就出来了,然而实际着顶点一点一点或者着变元一点一点,只不过很快而已。

  2. 混合算法

    • THREE.NoBlending
    • THREE.NormalBlending
    • THREE.AdditiveBlending
    • THREE.SubtractiveBlending
    • THREE.MultiplyBlending
    • THREE.CustomBlending【自定义混合】下一节讲

混合

混合颜色就像从五颜六色的窗户看外面的事件,最后看到的每一个都不相同。前端最常见的就是Canvas的globalCompositeOperation

bending.png

**补:blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha) 适合对有透明度的颜色做合成。基于srcRGB, dstRGB对源和目标的rgb颜色做合成;基于srcAlpha, dstAlpha对源和目标的alpha透明度做合成。

例如:gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE)

BlendColor.rgb=(0,0,1)0.5+(1,1,0)(1-0.5)

BlendColor.rgb=(0,0,0.5)+(0.5,0.5,0)

BlendColor.rgb=(0.5,0.5,0.5)

BlendColor.a=0.51+11

BlendColor.a=1.5=1 **