介绍
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
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)
)
),