精品学习网->精美文摘

上一篇    全部文章
three.js自定义材质 切线空间及阴影

three.js自定义材质 切线空间及阴影


前言

法线贴图中的法线向量定义在切线空间中,在切线空间中,法线永远指着正z方向。切线空间是位于三角形表面之上的空间:法线相对于单个三角形的本地参考框架。它就像法线贴图向量的本地空间;它们都被定义为指向正z方向,无论最终变换到什么方向。使用一个特定的矩阵我们就能将本地/切线空间中的法线向量转成世界或视图空间下,使它们转向到最终的贴图表面的方向。

摘自法线贴图 - LearnOpenGL CN

一、three.js获取切线空间

新版three.js 的BufferGeometryUtils.js 删除了computeTangents方法,以computeMikkTSpaceTangents 替代
function computeTangents(){
    throw new Error('BufferGeometryUtils:computeTangents renamed to computeMikkTSpaceTangents."):
}

function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign = true ){
   if !MikkTSpace ||!MikkTSpace.isReady ) { ... 
}
if(!geometry.hasAttribute('position')|!!geometry.hasAttribute('normal")!!!geometry.hasAttribute( 'uv'))
}

function getAttributeArray( attribute){
}

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);

效果







     返回顶部
threejs自定义材质切线空间及阴影