准备工作,加入hdr环境贴图和模型!
首先使用 RGBELoader 来加载 hdr
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader"
const rgbLoader = new RGBELoader()
rgbLoader.loadAsync("/004.hdr").then(texture => {
texture.mapping = THREE.EquirectangularReflectionMapping
scene.background = texture
scene.environment = texture
})
使用GLTFLoader加载模型!
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const loader = new GLTFLoader();
loader.load("/WhipperNude.glb", gltf => {
const girl = gltf.scene.children[0]
scene.add(girl)
girl.children.forEach(v=>{
v.material = planeMaterial
})
})
我们这里加载的是一个小美女,暂时先用着平面的材质!
效果!
给模型加一点贴图,法线贴图+普通贴图
这里我们用的是dds纹理,threejs也给我们写好了相关的loader!,这个模型是分为 头发,身体,手,脚这些组成的,对应的加载法向贴图和普通贴图!
import {
DDSLoader
} from "three/examples/jsm/loaders/DDSLoader"
const ddsLoader = new DDSLoader()
loader.load("/WhipperNude.glb", async gltf => {
let girlBodyMaterial = new THREE.MeshStandardMaterial({})
let girlHeadMaterial = new THREE.MeshStandardMaterial({})
let girlHairMaterial = new THREE.MeshStandardMaterial({})
let girlHandMaterial = new THREE.MeshStandardMaterial({})
let girlFeetMaterial = new THREE.MeshStandardMaterial({})
const body = await ddsLoader.loadAsync("/girl/BODY_WH_BD_N_00.dds")
const bodyNormal = await ddsLoader.loadAsync("/girl/BODY_WH_BD_N_00_NR.dds")
const head = await ddsLoader.loadAsync("/girl/HEAD_WH_FC_N_08.dds")
const feet = await ddsLoader.loadAsync("/girl/FEET_WH_FT_N_00.dds")
const feetNormal = await ddsLoader.loadAsync("/girl/FEET_WH_FT_N_00_NR.dds")
const hairNormal = await ddsLoader.loadAsync("/girl/HAIR_CO_HR_N_50_NR.dds")
const hand = await ddsLoader.loadAsync("/girl/HAND_WH_GL_N_00.dds")
const handNormal = await ddsLoader.loadAsync("/girl/HAND_WH_GL_N_00_NR.dds")
girlBodyMaterial.map = body
girlBodyMaterial.normalMap = bodyNormal
girlHeadMaterial.map = head
girlHairMaterial.normalMap = hairNormal
girlHandMaterial.map = hand
girlHandMaterial.normalMap = handNormal
girlFeetMaterial.map = feet
girlFeetMaterial.normalMap = feetNormal
const girl = gltf.scene.children[0]
scene.add(girl)
girl.children.forEach(v => {
console.log('====================================');
console.log(v.name);
console.log('====================================');
if (v.name === 'WhipperNude_Hair') {
v.material = girlHairMaterial
}
if (v.name === 'WhipperNude_Head') {
v.material = girlHeadMaterial
}
if (v.name === 'WhipperNude_Body') {
v.material = girlBodyMaterial
}
if (v.name === 'WhipperNude_Hands') {
v.material = girlHandMaterial
}
if (v.name === 'WhipperNude_Feet') {
v.material = girlFeetMaterial
}
})
})
效果
可以看到人物在场景中真实了许多!那么到这里 准备工作就做好了!
这里多加点阴影在里面!
那么第一步,我们就先去修改其定点着色器!让模型发生一些形变!
这里我们自定义了一个 shaderTransform 函数,这里我们自己写了一个来回旋转的效果!用于onBeforeCompile函数的回调!
const shaderTransform = ((shader) => {
// 传入 uniform uTime
shader.uniforms.uTime = myCustomUniforms.uTime
shader.vertexShader = shader.vertexShader.replace(
'#include <common>',
`
#include <common>
uniform float uTime;
//引入了一个旋转函数
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
`
)
// 修改顶点!
shader.vertexShader = shader.vertexShader.replace(
'#include <begin_vertex>',
`
#include <begin_vertex>
float angle = sin(uTime)*2.0;
mat2 rotateMat = rotate2d(angle);
transformed.xz = rotateMat*transformed.xz;
`
)
})
然后把这个函数,应用于头部的材质的 onBeforeCompile 上!
girlHeadMaterial.onBeforeCompile = shaderTransform
girlHairMaterial.onBeforeCompile = shaderTransform
效果
可以看到顶点已经修改成功!但是阴影投射和光线计算其实是不对的!首先,阴影没有跟随头部的转动而变化,光线没有根据转动而变化,因为此时只是修改了顶点,而法向和阴影的计算还是按照之前的顶点去计算的!所以下面的工作就是这两部分!这里光线不明显!我把灯光调亮一点,这样和后面做对比就能看出不同了!
修改阴影!customDepthMaterial
使用 DirectionalLight 或 SpotLight 进行阴影投射时,如果要修改顶点着色器中的顶点位置,则必须指定 customDepthMaterial 以获得正确的阴影
因为上面我们确实自定义去修改了顶点!那么我们就需要这个来获取正确的阴影!
既然是需要自定义深度材质,那么我们肯定需要定义一种额外的材质,那就是
MeshDepthMaterial 深度网格材质
const customDepthMaterial = new THREE.MeshDepthMaterial({
depthPacking: THREE.RGBADepthPacking
})
然后同样的自定义旋转
customDepthMaterial.onBeforeCompile = shaderTransform
最后给模型的头部添加 customDepthMaterial
if (v.name === 'WhipperNude_Hair') {
v.material = girlHairMaterial
v.customDepthMaterial = customDepthMaterial
}
if (v.name === 'WhipperNude_Head') {
v.material = girlHeadMaterial
v.customDepthMaterial = customDepthMaterial
}
这里就可以看到效果了!自定义顶点的阴影也OK了!下一步需要计算法向了,来让我们的光线效果正确显示!!
自定义顶点法向修改!beginnormal_vertex
这里我提前做了一个修改!将 MeshDepthMaterial 材质的shader 拦截函数独立出来!(因为,我后面在一起加了法向之后,阴影就没有了,我目前还不知道什么原因)
const depTransform = shader => {
// 传入 uniform uTime
shader.uniforms.uTime = myCustomUniforms.uTime
shader.vertexShader = shader.vertexShader.replace(
'#include <common>',
`
#include <common>
uniform float uTime;
//引入了一个旋转函数
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
`
)
// 修改顶点!
shader.vertexShader = shader.vertexShader.replace(
'#include <begin_vertex>',
`
#include <begin_vertex>
float angle = sin(uTime)*2.0;
mat2 rotateMat = rotate2d(angle);
transformed.xz = rotateMat*transformed.xz;
`
)
}
customDepthMaterial.onBeforeCompile = depTransform
然后我们在继续去修改女孩模型头部的法向,这里要注意变量重复声明的问题(可参考完整版代码)
shader.vertexShader = shader.vertexShader.replace(
'#include <beginnormal_vertex>',
`
#include <beginnormal_vertex>
float angle = sin(uTime)*2.0;
mat2 rotateMat = rotate2d(angle);
objectNormal.xz = rotateMat*objectNormal.xz;
`
)
可以看到这个光线就比较真实了!
完整代码
import * as THREE from "three"
import {
OrbitControls
} from "three/examples/jsm/controls/OrbitControls"
import {
handleResize
} from "@/common/utils"
import {
RGBELoader
} from "three/examples/jsm/loaders/RGBELoader"
import {
GLTFLoader
} from 'three/examples/jsm/loaders/GLTFLoader';
import {
DDSLoader
} from "three/examples/jsm/loaders/DDSLoader"
const rgbLoader = new RGBELoader()
const ddsLoader = new DDSLoader()
rgbLoader.loadAsync("/004.hdr").then(texture => {
texture.mapping = THREE.EquirectangularReflectionMapping
scene.background = texture
scene.environment = texture
})
// 实例化加载器!
const loader = new GLTFLoader();
// 场景
let clock = new THREE.Clock()
const scene = new THREE.Scene()
const spotLight = new THREE.SpotLight(0xffffff, 2)
scene.add(spotLight)
spotLight.position.x = 0
spotLight.position.y = 0
spotLight.position.z = 10
spotLight.shadow.mapSize.set(2048, 2048)
spotLight.castShadow = true
// 相机
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000)
camera.position.set(0, -5, 4)
// 一个平面
const planeGeometry = new THREE.PlaneBufferGeometry(10, 10, 10, 100)
const planeMaterial = new THREE.MeshStandardMaterial({
roughness: 0,
side: THREE.DoubleSide,
color: 0xffffff,
metalness: 0.5,
})
const myCustomUniforms = {
uTime: {
value: 0
}
}
// depTransform 深度网格材质 编译前回调
const depTransform = shader => {
// 传入 uniform uTime
shader.uniforms.uTime = myCustomUniforms.uTime
shader.vertexShader = shader.vertexShader.replace(
'#include <common>',
`
#include <common>
uniform float uTime;
//引入了一个旋转函数
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
`
)
// 修改顶点!
shader.vertexShader = shader.vertexShader.replace(
'#include <begin_vertex>',
`
#include <begin_vertex>
float angle = sin(uTime)*2.0;
mat2 rotateMat = rotate2d(angle);
transformed.xz = rotateMat*transformed.xz;
`
)
}
const shaderTransform = ((shader) => {
// 传入 uniform uTime
shader.uniforms.uTime = myCustomUniforms.uTime
shader.vertexShader = shader.vertexShader.replace(
'#include <common>',
`
#include <common>
uniform float uTime;
//引入了一个旋转函数
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
`
)
// 修改顶点!
shader.vertexShader = shader.vertexShader.replace(
'#include <begin_vertex>',
`
#include <begin_vertex>
transformed.xz = rotateMat*transformed.xz;
`
)
// 修改法向顶点!
shader.vertexShader = shader.vertexShader.replace(
'#include <beginnormal_vertex>',
`
#include <beginnormal_vertex>
float angle = sin(uTime)*2.0;
mat2 rotateMat = rotate2d(angle);
objectNormal.xz = rotateMat*objectNormal.xz;
`
)
})
const customDepthMaterial = new THREE.MeshDepthMaterial({
depthPacking: THREE.RGBADepthPacking
})
customDepthMaterial.onBeforeCompile = depTransform
loader.load("/WhipperNude.glb", async gltf => {
let girlBodyMaterial = new THREE.MeshStandardMaterial({})
let girlHeadMaterial = new THREE.MeshStandardMaterial()
let girlHairMaterial = new THREE.MeshStandardMaterial({})
let girlHandMaterial = new THREE.MeshStandardMaterial({})
let girlFeetMaterial = new THREE.MeshStandardMaterial({})
const body = await ddsLoader.loadAsync("/girl/BODY_WH_BD_N_00.dds")
const bodyNormal = await ddsLoader.loadAsync("/girl/BODY_WH_BD_N_00_NR.dds")
const head = await ddsLoader.loadAsync("/girl/HEAD_WH_FC_N_08.dds")
const feet = await ddsLoader.loadAsync("/girl/FEET_WH_FT_N_00.dds")
const feetNormal = await ddsLoader.loadAsync("/girl/FEET_WH_FT_N_00_NR.dds")
const hairNormal = await ddsLoader.loadAsync("/girl/HAIR_CO_HR_N_50_NR.dds")
const hand = await ddsLoader.loadAsync("/girl/HAND_WH_GL_N_00.dds")
const handNormal = await ddsLoader.loadAsync("/girl/HAND_WH_GL_N_00_NR.dds")
girlBodyMaterial.map = body
girlBodyMaterial.normalMap = bodyNormal
girlHeadMaterial.map = head
girlHairMaterial.map = hairNormal
girlHairMaterial.normalMap = hairNormal
girlHandMaterial.map = hand
girlHandMaterial.normalMap = handNormal
girlFeetMaterial.map = feet
girlFeetMaterial.normalMap = feetNormal
const girl = gltf.scene.children[0]
scene.add(girl)
girl.children.forEach(v => {
if (v.name === 'WhipperNude_Hair') {
v.material = girlHairMaterial
v.customDepthMaterial = customDepthMaterial
}
if (v.name === 'WhipperNude_Head') {
v.material = girlHeadMaterial
v.customDepthMaterial = customDepthMaterial
}
if (v.name === 'WhipperNude_Body') {
v.material = girlBodyMaterial
}
if (v.name === 'WhipperNude_Hands') {
v.material = girlHandMaterial
}
if (v.name === 'WhipperNude_Feet') {
v.material = girlFeetMaterial
}
v.castShadow = true
})
girlHeadMaterial.onBeforeCompile = shaderTransform
girlHairMaterial.onBeforeCompile = shaderTransform
})
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
planeMesh.receiveShadow = true
planeMesh.position.z -= 5
planeMesh.position.y += 5
scene.add(planeMesh)
// 渲染器
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// 控制器
const controller = new OrbitControls(camera, renderer.domElement)
spotLight.castShadow = true
renderer.shadowMap.enabled = true
handleResize(camera, renderer)
// 渲染函数
const handleRender = (time) => {
myCustomUniforms.uTime.value = clock.getElapsedTime()
renderer.render(scene, camera)
controller.update
requestAnimationFrame(handleRender)
}
requestAnimationFrame(handleRender)
评论区