Three.js Tri-panner (三面贴图/世界UV贴图) 材质 两种实现方式

请添加图片描述

在这里插入图片描述

介绍

Tri-panner 在babylonjs中有支持 但是three.js目前的基础材质并不支持
需要自己定义shader 或者使用目前还没有什么完善的文档的 NodeMaterial

解析

三面贴图 指的是xyz三个轴方向去采样 不使用uv映射转而使用position
x轴的颜色 就是 texture(tex, position.zy).rgb
y轴的颜色 就是 texture(tex, position.zx).rgb
z轴的颜色 就是 texture(tex, position.xy).rgb
而不管是x还是y或者z都是有值的 那颜色就会出现叠接 无法直接应用
这是就用到了法相 用法相也就是这个点朝向哪个轴 这个轴的比重就大
如果是垂直的 那么有两个方向的法相就为0 自然就只取一个轴的颜色 就不会叠加颜色

这里有一个细节
法相和position的取值
如果只取标准的position和normal 也可以
但这样就无法应用物体的旋转 缩放平移
为了适应物体的变化 position和normal都要应用 modelMatrix
vec3(modelMatrix * vec4(transformed,1.) )
vec3(modelMatrix * vec4(normal, 0.0))
这两项取值要同步 否则就会在物体变化后出现线条拉伸问题 就是位置和法相采样不一致导致的

下面展示两种实现方式

自定义shader
/**
 * @description: 替换三角面贴图  https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/triPlanarMat
 * @param {SingleMaterialMesh} mesh
 * @return {*}
 */
export const useTriplanarMapping = (mesh: SingleMaterialMesh) => {
    const material = mesh.material.clone();
    mesh.material = material;
    material.map!.wrapS = THREE.RepeatWrapping;
    material.map!.wrapT = THREE.RepeatWrapping;
   
    material.onBeforeCompile = (shader) => {
        shader.vertexShader = shader.vertexShader.replace(
            "#include <common>",
            `
            #include <common>
            varying vec3 tripPosition;
            varying vec3 tripNormal;
        `
        );
        shader.vertexShader = shader.vertexShader.replace(
            "#include <fog_vertex>",
            `
            #include <fog_vertex>
            vec4 tripPosition4 = modelMatrix * vec4(position,1.) ;
            tripPosition = tripPosition4.xyz;

            vec3 world_space_normal = vec3(modelMatrix * vec4(normal, 0.0));
            tripNormal = world_space_normal;
        `
        );
        shader.fragmentShader = shader.fragmentShader.replace(
            "#include <common>",
            `
            #include <common>
            varying vec3 tripPosition;
            varying vec3 tripNormal;
            vec3 blendNormal(vec3 normal){
                vec3 blending = abs( normal );
                blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 
                float b = (blending.x + blending.y + blending.z);
                blending /= vec3(b, b, b);
                return blending;
            }
            
            vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position) {
              vec3 normalBlend = blendNormal(normal);
              vec3 xColor = texture(tex, position.yz).rgb;
              vec3 yColor = texture(tex, position.xz).rgb;
              vec3 zColor = texture(tex, position.xy).rgb;
              return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
            }
        `
        );

        shader.fragmentShader = shader.fragmentShader.replace(
            "#include <map_fragment>",
            `
            #include <map_fragment>
            diffuseColor.rgb = vec3(triplanarMapping( map ,tripNormal,tripPosition));
        `
        );
        // shader.fragmentShader = shader.fragmentShader.replace(
        //     "#include <color_fragment>",
        //     `
        //     #include <color_fragment>
        //     diffuseColor.rgb = vec3(triplanar_mapping( map ,tripNormal,tripPosition,1.0));
        // `
        // );
    };
};

NodeMaterial

这是threejs新系统充满未来 目前还没有一个完善的文档 并且不太稳定 r132的时候支持这个材质 r138就被删除了 一些api也都有变化 可以先参考 https://raw.githack.com/sunag/three.js/dev-nodes-doc/docs/index.html#manual/en/introduction/How-to-use-node-material

tsl文档

import {
    MeshBasicNodeMaterial,
    texture,
    triplanarTexture,
} from "three/examples/jsm/nodes/Nodes.js";
import { nodeFrame } from "three/examples/jsm/renderers/webgl/nodes/WebGLNodes.js";

const skyMat = new MeshBasicNodeMaterial();

skyMat.colorNode = triplanarTexture(
    texture(
        this.helper.loadTexture(
            "/public/textures/coral_stone_wall_diff_1k.jpg",
            (map) => {
                map.colorSpace = THREE.SRGBColorSpace;
                map.wrapS = THREE.RepeatWrapping;
                map.wrapT = THREE.RepeatWrapping;
            }
        )
    )
);
skyMat.side = THREE.DoubleSide;

const sky = new THREE.Mesh(new THREE.SphereGeometry(2, 32, 15), skyMat);
scene.add(sky);


animation() {
    nodeFrame.update();
}

要注意每一次render 同时调用 nodeFrame.update(); 否则报错

修复:骨骼材质特殊处理

这个问题需要根据three版本进行区别处理

r160版本 使用的是 position
r155版本使用的是 nodeUniform2 * vec4( 忘了叫什么了, 1.0 )
总之每个版本可能不一样 因为 节点系统正在开发 需要对应版本对应处理

r160版本写法如下

material.onBeforeCompile = (shader) => {
    material.vertexShader = shader.vertexShader.replace(
        "#include <skinning_vertex>",
        `
        #include <skinning_vertex>
        nodeVarying2 = (modelMatrix * vec4(transformed,1.0)).xyz;
        `
    );
};

r155版本写法如下

material.onBeforeCompile = (shader) => {
   material.vertexShader = shader.vertexShader.replace(
         "#include <skinning_vertex>",
         `
         #include <skinning_vertex>
         nodeVarying2 = ( nodeUniform2 * vec4( transformed, 1.0 ) );
     `
     );
 };
修复:使用法相贴图时整体变色

这个问题nodeMaterial 没找到如何解决 下面给出自定义材质的解决方案

export const useTriplanarMapping = (mesh) => {
    const material = mesh.material.clone();
    mesh.material = material;
    material.map.colorSpace = THREE.SRGBColorSpace;
    material.map.wrapS = THREE.RepeatWrapping;
    material.map.wrapT = THREE.RepeatWrapping;
    if (material.normalMap) {
        material.normalMap.colorSpace = THREE.SRGBColorSpace;
        material.normalMap.wrapS = THREE.RepeatWrapping;
        material.normalMap.wrapT = THREE.RepeatWrapping;
    }

    material.onBeforeCompile = (shader) => {
        shader.vertexShader = shader.vertexShader.replace(
            "#include <common>",
            `
            #include <common>
            varying vec3 tripPosition;
            varying vec3 tripNormal;
        `
        );
        shader.vertexShader = shader.vertexShader.replace(
            "#include <skinning_vertex>",
            `
            #include <skinning_vertex>
            vec4 tripPosition4 = modelMatrix * vec4(transformed,1.) ;
            tripPosition = tripPosition4.xyz;
            
            vec3 world_space_normal = vec3(modelMatrix * vec4(normal, 0.0));
            tripNormal = world_space_normal;
        `
        );
        shader.fragmentShader = shader.fragmentShader.replace(
            "#include <common>",
            `
            #include <common>
            varying vec3 tripPosition;
            varying vec3 tripNormal;
            vec3 blendNormal(vec3 normal){
                vec3 blending = abs( normal );
                blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 
                float b = (blending.x + blending.y + blending.z);
                blending /= vec3(b, b, b);
                return blending;
            }
            
            vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position) {
              vec3 normalBlend = blendNormal(normal);
              vec3 xColor = texture(tex, position.yz).rgb;
              vec3 yColor = texture(tex, position.xz).rgb;
              vec3 zColor = texture(tex, position.xy).rgb;
              return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
            }
        `
        );

        shader.fragmentShader = shader.fragmentShader.replace(
            "#include <map_fragment>",
            `
            #include <map_fragment>
            diffuseColor.rgb = vec3(triplanarMapping( map ,tripNormal,tripPosition));
        `
        );
        shader.fragmentShader = shader.fragmentShader.replace(
            "#include <normal_fragment_maps>",
            `
            #include <normal_fragment_maps>
            normal = vec3(triplanarMapping( normalMap ,tripNormal,tripPosition));
            normal = normalize( tbn * normal );
        `
        );
    };
};

更新:

export class TriplanarMaterial extends THREE.MeshStandardMaterial {
    constructor(params) {
        super(params);
		
        // if (!params.option.xyRepeat) {
        //     params.option.xyRepeat = { value: new THREE.Vector2(1, 1) };
        // }
        if (params && params.base) {
            this.onBeforeCompile = shader => {
                Object.assign(
                    shader.uniforms,
                    params.option || {
                        iScale: { value: 3 },
                        iRotate: { value: 0 },
                        xyRepeat: { value: new THREE.Vector2(1, 1) },
                    }
                );
                this.setScale = scale => {
                    shader.uniforms.iScale.value = scale;
                };

                shader.vertexShader = shader.vertexShader.replace(
                    "#include <common>",
                    `
            #include <common>
            varying vec3 tripPosition;
            varying vec3 tripNormal;
            uniform float iScale;
            uniform float iRotate;
            vec3 blendNormal(vec3 normal){
                vec3 blending = abs( normal );
                blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 
                float b = (blending.x + blending.y + blending.z);
                blending /= vec3(b, b, b);
                return blending;
            }
            
            vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position) {
              vec3 normalBlend = blendNormal(normal);
              vec3 xColor = texture(tex, position.zy * iScale).rgb;
              vec3 yColor = texture(tex, position.zx * iScale).rgb;
              vec3 zColor = texture(tex, position.xy * iScale).rgb;
              return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
            }
        `
                );
                shader.vertexShader = shader.vertexShader.replace(
                    "#include <skinning_vertex>",
                    `
            #include <skinning_vertex>
            vec4 tripPosition4 = modelMatrix * vec4(transformed,1.) ;
            tripPosition = tripPosition4.xyz;
            tripNormal = normal * normalMatrix;
            vec3 world_space_normal = vec3(modelMatrix * vec4(objectNormal, 0.0));
            tripNormal = normal;
        `
                );
                // displacement
                shader.vertexShader = shader.vertexShader.replace(
                    "#include <displacementmap_vertex>",
                    `
            #ifdef USE_DISPLACEMENTMAP

                // transformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );
                transformed += normalize( objectNormal ) * ( triplanarMapping( displacementMap ,tripNormal,tripPosition).x * displacementScale + displacementBias );

            #endif
        `
                );

                shader.fragmentShader = shader.fragmentShader.replace(
                    "#include <common>",
                    `
            #include <common>
            varying vec3 tripPosition;
            varying vec3 tripNormal;
            uniform float iScale;
            uniform float iRotate;
            uniform vec2 xyRepeat;
            vec3 blendNormal(vec3 normal){
                vec3 blending = abs( normal );
                blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 
                float b = (blending.x + blending.y + blending.z);
                blending /= vec3(b, b, b);
                return blending;
            }
            // vMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;
            vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position,out float opacity) {
                vec3 normalBlend = blendNormal(normal);
                vec4 xColor = texture(tex, position.zy * iScale * xyRepeat );
                vec4 yColor = texture(tex, position.zx* iScale * xyRepeat );
                vec4 zColor = texture(tex, position.xy* iScale * xyRepeat );
                opacity = (xColor.a * normalBlend.x + yColor.a * normalBlend.y + zColor.a * normalBlend.z);
                return (xColor.rgb * normalBlend.x + yColor.rgb * normalBlend.y + zColor.rgb * normalBlend.z);
                  // return normalBlend;
              }
            vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position) {
                vec3 normalBlend = blendNormal(normal);
                vec3 xColor = texture(tex, position.zy * iScale * xyRepeat).rgb;
                vec3 yColor = texture(tex, position.zx* iScale * xyRepeat).rgb;
                vec3 zColor = texture(tex, position.xy* iScale * xyRepeat).rgb;
                return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
                    // return normalBlend;
            }
        `
                );

                shader.fragmentShader = shader.fragmentShader.replace(
                    "#include <dithering_fragment>",
                    `
                #include <dithering_fragment>
                float triOpacity = 0.;
                gl_FragColor.rgb = diffuse * vec3(triplanarMapping( map ,tripNormal,tripPosition,triOpacity));
                gl_FragColor.a = triOpacity;
            `
                );
                // 不受光照影响 base
                // console.log(
                //     " 不受光照影响 base 不受光照影响 base 不受光照影响 base 不受光照影响 base"
                // );
            };
        } else {
			if(params){
				this.onBeforeCompile = shader => {
					Object.assign(
						shader.uniforms,
						params.option || {
							iScale: { value: 3 },
							iRotate: { value: 0 },
							xyRepeat: { value: new THREE.Vector2(1, 1) },
						}
					);
					this.setScale = scale => {
						shader.uniforms.iScale.value = scale;
					};
					
					
					shader.vertexShader = shader.vertexShader.replace(
						"#include <common>",
						`
							#include <common>
							varying vec3 tripPosition;
							varying vec3 tripNormal;
							uniform float iScale;
							uniform float iRotate;
							uniform vec2 xyRepeat;
							vec3 blendNormal(vec3 normal){
								vec3 blending = abs( normal );
								blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 
								float b = (blending.x + blending.y + blending.z);
								blending /= vec3(b, b, b);
								return blending;
							}
							
							vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position) {
								vec3 normalBlend = blendNormal(normal);
								vec3 xColor = texture(tex, position.zy * iScale * xyRepeat).rgb;
								vec3 yColor = texture(tex, position.zx * iScale * xyRepeat).rgb;
								vec3 zColor = texture(tex, position.xy * iScale * xyRepeat).rgb;
								return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
								  // return normalBlend;
							  }
						`
					);
					
					
					shader.vertexShader = shader.vertexShader.replace(
						"#include <skinning_vertex>",
						`
							#include <skinning_vertex>
							vec4 tripPosition4 = modelMatrix * vec4(transformed,1.) ;
							// vec4 tripPosition4 =  vec4(transformed,1.) ;
							tripPosition = tripPosition4.xyz;
							tripNormal = normal * normalMatrix;
							vec3 world_space_normal = vec3(modelMatrix * vec4(objectNormal, 0.0));
							tripNormal = world_space_normal;
						`
					);
					
					
					// displacement
					shader.vertexShader = shader.vertexShader.replace(
						"#include <displacementmap_vertex>",
						`
							#ifdef USE_DISPLACEMENTMAP

								// transformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );
								transformed += normalize( objectNormal ) * ( triplanarMapping( displacementMap ,tripNormal,tripPosition).x * displacementScale + displacementBias );

							#endif
						`
					);
					
					
					shader.fragmentShader = shader.fragmentShader.replace(
						"#include <common>",
						`
							#include <common>
							varying vec3 tripPosition;
							varying vec3 tripNormal;
							uniform float iScale;
							uniform vec2 xyRepeat;
							uniform float iRotate;
							vec3 blendNormal(vec3 normal){
								vec3 blending = abs( normal );
								blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 
								float b = (blending.x + blending.y + blending.z);
								blending /= vec3(b, b, b);
								return blending;
							}
							// vMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;
							vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position,out float opacity) {
							  vec3 normalBlend = blendNormal(normal);
							  vec4 xColor = texture(tex, position.zy * iScale * xyRepeat );
							  vec4 yColor = texture(tex, position.zx* iScale * xyRepeat );
							  vec4 zColor = texture(tex, position.xy* iScale * xyRepeat );
							  opacity = (xColor.a * normalBlend.x + yColor.a * normalBlend.y + zColor.a * normalBlend.z);
							  return (xColor.rgb * normalBlend.x + yColor.rgb * normalBlend.y + zColor.rgb * normalBlend.z);
								// return normalBlend;
							}
                            vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position) {
                                vec3 normalBlend = blendNormal(normal);
                                vec3 xColor = texture(tex, position.zy * iScale * xyRepeat).rgb;
                                vec3 yColor = texture(tex, position.zx* iScale * xyRepeat).rgb;
                                vec3 zColor = texture(tex, position.xy* iScale * xyRepeat).rgb;
                                return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
                                  // return normalBlend;
                              }
						`
					);

					
					{
						// map
						if(params.map){
							shader.fragmentShader = shader.fragmentShader.replace(
								"#include <map_fragment>",
								`
									#include <map_fragment>
									float triOpacity = 0.;
									diffuseColor.rgb = diffuse * vec3(triplanarMapping( map ,tripNormal,tripPosition,triOpacity));
									diffuseColor.a = triOpacity;
								`
							);
						}
						// normal
						shader.fragmentShader = shader.fragmentShader.replace(
							"#include <normal_fragment_maps>",
							`
								#include <normal_fragment_maps>
								#ifdef USE_NORMALMAP
									normal = vec3(triplanarMapping( normalMap ,tripNormal,tripPosition) * 2. - 1.);
									normal = normalize( tbn * normal );
								#endif
							`
						);
						// roughness
						shader.fragmentShader = shader.fragmentShader.replace(
							"#include <roughnessmap_fragment>",
							`
								float roughnessFactor = roughness;

								#ifdef USE_ROUGHNESSMAP

									// vec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );
									vec4 texelRoughness = vec4(triplanarMapping( roughnessMap ,tripNormal,tripPosition),1.0);

									roughnessFactor *= texelRoughness.g;

								#endif
							`
						);
						// metalness
						shader.fragmentShader = shader.fragmentShader.replace(
							"#include <metalnessmap_fragment>",
							`
								float metalnessFactor = metalness;

								#ifdef USE_METALNESSMAP

									// vec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );
									vec4 texelMetalness = vec4(triplanarMapping( metalnessMap ,tripNormal,tripPosition),1.0);

									metalnessFactor *= texelMetalness.b;

								#endif
							`
						);
					}
					
				};
				
			}
			
        }
    }
}

新版NodeMaterial实现 @r174

在这里插入图片描述

import * as THREE from "three/webgpu";
import { uv, texture, color, float, vec2, vec3, triplanarTexture, bumpMap } from "three/tsl";

const planeBack = new THREE.Mesh(
  planeGeo,
    new THREE.MeshStandardNodeMaterial({
        roughness: 0.5,
        colorNode: triplanarTexture(
            texture(
                textureLoader.load("/public/textures/pavement_02_diff_1k.jpg", t => {
                    t.wrapS = t.wrapT = THREE.RepeatWrapping;
                    t.colorSpace = THREE.SRGBColorSpace;
                })
            ),
            null,
            null,
            vec2(1, 1)
        ),
        normalNode: normalMap(
            triplanarTexture(
                texture(
                    textureLoader.load("/public/textures/pavement_02_nor_gl_1k.jpg", t => {
                        t.wrapS = t.wrapT = THREE.RepeatWrapping;
                    })
                ),
                null,
                null,
                vec2(1, 1)
            )
        ),
    })
);

or

normalNode: bumpMap(
            triplanarTexture(
                texture(
                    textureLoader.load("/public/textures/pavement_02_nor_gl_1k.jpg", t => {
                        t.wrapS = t.wrapT = THREE.RepeatWrapping;
                    })
                ),
                null,
                null,
                vec2(1, 1)
            )
        ),
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值