three.js自定义材质 切线空间及阴影
前言
法线贴图中的法线向量定义在切线空间中,在切线空间中,法线永远指着正z方向。切线空间是位于三角形表面之上的空间:法线相对于单个三角形的本地参考框架。它就像法线贴图向量的本地空间;它们都被定义为指向正z方向,无论最终变换到什么方向。使用一个特定的矩阵我们就能将本地/切线空间中的法线向量转成世界或视图空间下,使它们转向到最终的贴图表面的方向。
一、three.js获取切线空间
新版three.js 的BufferGeometryUtils.js 删除了computeTangents方法,以computeMikkTSpaceTangents 替代
computeMikkTSpaceTangents 参数为( BuffGeoMetry , MikkTSpace , negateSign )
- negateSign -- 是否对每个切线的符号分量 (.w) 取反。某些格式的法线贴图约定需要,包括 glTF。
返回切线数据(vec4)
mikktspace 为 three/examples/jsm/libs/mikktspace.module.js (旧版没有,可以npm引入)
使用computeMikkTSpaceTangents前需要等MikkTSpace.ready完成
二、使用步骤
1.初始化initMikkTSpace
代码如下:
import { computeMikkTSpaceTangents } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
import { wasm,isReady,ready,generateTangents } from 'three/examples/jsm/libs/mikktspace.module.js'
async initMikkTSpace(cb){
await ready
cb()
}
const geo01 = new THREE.SphereGeometry(6, 64, 32)
// const geo01 = new THREE.BoxGeometry( 6, 6, 6 );
this.initMikkTSpace(cb=>{
let MikkTSpace = {
wasm:wasm,
isReady:isReady,
generateTangents:generateTangents
}
computeMikkTSpaceTangents(geo01,MikkTSpace)
})
2.编辑材质
getShadowMask()返回的是阴影
主要步骤:1.attribute vec4 tangent;是computeMikkTSpaceTangents直接添加到geo的切线数据
2.
vec3 nowPoint = vec3((modelViewMatrix * vec4( position, 1.0 )).xyz);
lightToPos = myLight - nowPoint;
获取光与点的向量
vNormal = normalize(normalMatrix * normal);//获取法线
vec3 vTangent = normalize( normalMatrix * tangent.xyz );//切线
vec3 vBinormal = normalize(cross( vNormal, vTangent ) * tangent.w);//副切线
tbn = mat3(vTangent, vBinormal, vNormal);//切线空间
代码如下:
initShader(tangent,myLight){
let textureLoader = new THREE.TextureLoader();
let vertexShader = `
varying vec3 vNormal;
varying vec2 vUv;
attribute vec4 tangent;
varying vec4 vtangent;
varying mat3 tbn;
uniform vec3 myLight;
varying vec3 lightToPos;
${THREE.ShaderChunk[ "common" ]}
${THREE.ShaderChunk[ "bsdfs" ]}
${THREE.ShaderChunk[ "shadowmap_pars_vertex" ]}
void main()
{
${THREE.ShaderChunk['beginnormal_vertex']}
${THREE.ShaderChunk['defaultnormal_vertex']}
${THREE.ShaderChunk[ "begin_vertex" ]}
${THREE.ShaderChunk[ "project_vertex" ]}
${THREE.ShaderChunk[ "worldpos_vertex" ]}
${THREE.ShaderChunk[ "shadowmap_vertex" ]}
vec3 nowPoint = vec3((modelViewMatrix * vec4( position, 1.0 )).xyz);
lightToPos = myLight - nowPoint;
vNormal = normalize(normalMatrix * normal);
vec3 vTangent = normalize( normalMatrix * tangent.xyz );
vec3 vBinormal = normalize(cross( vNormal, vTangent ) * tangent.w);
tbn = mat3(vTangent, vBinormal, vNormal);
vUv = uv;
vtangent = tangent;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`
let fragmentShader = `
// uniform vec3 light;
varying vec3 vNormal;
varying vec3 lightToPos;
varying vec2 vUv;
varying vec4 vtangent;
uniform sampler2D u_texture;
uniform sampler2D u_textureNormal;
varying mat3 tbn;
${THREE.ShaderChunk[ "common" ]}
${THREE.ShaderChunk[ "packing" ]}
${THREE.ShaderChunk[ "bsdfs" ]}
${THREE.ShaderChunk[ "lights_pars_begin" ]}
${THREE.ShaderChunk[ "shadowmap_pars_fragment" ]}
${THREE.ShaderChunk[ "shadowmask_pars_fragment" ]}
//获取纹理颜色
vec3 mygetPixelColor(sampler2D mytexture) {
return texture2D(mytexture, vUv).rgb;
}
void main(){
vec3 normalColor = mygetPixelColor(u_textureNormal);
normalColor = normalColor * 2.0 - 1.0;
vec3 anynormalColor = normalize(tbn * normalColor);
//处理光照
float diff = max( dot(anynormalColor , normalize(lightToPos)) , 0.0 );
vec3 diffuse = diff * vec3(1,1,1);//diff * lightColor
vec3 textureColor = mygetPixelColor(u_texture);
vec3 addDiffuse = textureColor + diffuse;
vec3 shadowColor = vec3(0,0,0);
vec3 addShadow = mix( shadowColor , addDiffuse ,getShadowMask());
gl_FragColor = vec4(addShadow,1.0);
}`
//着色器材质
let sm = new THREE.ShaderMaterial({
uniforms: THREE.UniformsUtils.merge( [
THREE.UniformsLib[ "lights" ],
{
opacity: { type: 'f', value: 1.0 },
// tangent: { value: tangent },
myLight: { value: myLight.position},
u_texture:{value:textureLoader.load(require('../../assets/brickwall.jpg'))},
u_textureNormal:{value:textureLoader.load(require('../../assets/brickwall_normal.jpg'))},
}
] ),
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.FrontSide,
lights: true
});
return sm
},
3.阴影处理
renderer.shadowMap.enabled = true;
...
let light = new THREE.DirectionalLight(0xffffff);
light.position.set(42,60,0);
//告诉平行光需要开启阴影投射
light.castShadow = true;
light.shadow.mapSize.width = 1024; // default 512
light.shadow.mapSize.height = 1024; // default 512
//阴影相机范围
light.shadow.camera.near = 0.5; // default 0.5
light.shadow.camera.far = 100; // default 500
light.shadow.camera.left = -30
light.shadow.camera.right = 30
light.shadow.camera.top = 30
light.shadow.camera.bottom = -30
scene.add(light);
效果
材质图片