目 录CONTENT

文章目录

如何修改材质顶点!自定义顶点的正确阴影?自定义顶点法向?

Hello!你好!我是村望~!
2022-08-14 / 0 评论 / 0 点赞 / 392 阅读 / 2,129 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

准备工作,加入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
    })
})

我们这里加载的是一个小美女,暂时先用着平面的材质!
image-1660382694131
效果!
1-1660382829153

给模型加一点贴图,法线贴图+普通贴图

这里我们用的是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
        }
    })
})

效果
1-1660384996626
可以看到人物在场景中真实了许多!那么到这里 准备工作就做好了!
这里多加点阴影在里面!
image-1660467452434

那么第一步,我们就先去修改其定点着色器!让模型发生一些形变!

这里我们自定义了一个 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

效果
1-1660469383827
可以看到顶点已经修改成功!但是阴影投射和光线计算其实是不对的!首先,阴影没有跟随头部的转动而变化,光线没有根据转动而变化,因为此时只是修改了顶点,而法向和阴影的计算还是按照之前的顶点去计算的!所以下面的工作就是这两部分!这里光线不明显!我把灯光调亮一点,这样和后面做对比就能看出不同了!
1-1660469740401

修改阴影!customDepthMaterial

使用 DirectionalLightSpotLight 进行阴影投射时,如果要修改顶点着色器中的顶点位置,则必须指定 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
}

1-1660473757244
这里就可以看到效果了!自定义顶点的阴影也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;
        `
)

1-1660487529444
可以看到这个光线就比较真实了!
完整代码

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

评论区