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作为纹理
结论:
-
THREE可以使用Canvas作为纹理转为材质 【CanvasTexture】;
-
scene.background不为null时,在渲染场景的时候背景总是首先被渲染的。【速度快,第一个被渲染】
-
mapBg.offset.set(ox, oy); 贴图单次重复中的起始偏移量,分别表示U和V。 一般范围是由0.0到1.0。
-
mapBg.wrapS = mapBg.wrapT = THREE.RepeatWrapping/THREE.MirroredRepeatWrapping/ClampToEdgeWrapping官网给出很明确的三个意思:整体纹理不停重复(类似复制),整体纹理不停重复(类似复制)然而是镜像重复,整体纹理的最后一个像素延伸到边缘(我去掉了网格,因为网格范围很广例如:模型、几何的边缘等,案例中是延伸到场景的边缘)。
- 使用RepeatWrapping,纹理将简单地重复到无穷大;
- 使用MirroredRepeatWrapping, 纹理将重复到无穷大,在每次重复时将进行镜像;
- 使用ClampToEdgeWrapping是默认值,纹理中的最后一个像素将延伸到网格的边缘;
-
混合其实就是一种颜色计算,将材质内部颜色采取指定计算模式。如何实现呢?【案例为例】
-
MeshBasicMaterial extends Material 通过MeshBasicMaterial 构造函数中的this.setValues( parameters ){this[ key ] = newValue}实现修改this.blending的值;
-
回到案例源码let mesh = new THREE.Mesh(geometry, material) Mesh对象其实没干啥事就是绑定内部this上的属性:this.geometry = geometry;this.material = material;
-
回到案例源码scene.add(mesh)添加到场景,源码就是对mesh类型判断是否为数组,mesh类型是否是否isobject,是否与当前对象相同(相同就不允许添加),最后this.children.push( object );
-
回到源码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;
}
-
思考:我认为THREE源属性指Color属性,目标材质属性map。因为我后面修改Color属性、目标删除map改为specularMap等都表现不出来目标,只有源Color属性进行了计算。还有一点大家要知道,WebGl渲染看上去一下子就出来了,然而实际着顶点一点一点或者着变元一点一点,只不过很快而已。
-
混合算法
- THREE.NoBlending
- THREE.NormalBlending
- THREE.AdditiveBlending
- THREE.SubtractiveBlending
- THREE.MultiplyBlending
THREE.CustomBlending【自定义混合】下一节讲
混合
混合颜色就像从五颜六色的窗户看外面的事件,最后看到的每一个都不相同。前端最常见的就是Canvas的globalCompositeOperation
**补: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 **