【Three.js基础学习】27.Modified materials

时间:2024-11-21 15:22:56


前言

  补充:\node_modules\three\src\renderers\shaders

        自Three.js第132版以来,位于ShaderLib/文件夹中的着色器现在按材质分组。

        顶点代码和片段代码都在同一个文件中。

    课程

        学习如何改进3DS内置材质

        改进网格标准材质

            两种方法

                1.使用Three.js钩子,让我们调整着色器并注入代码(在编译之前将我们的代码注入到材料中)。 (使用这种方式)

                2.通过重新创建材料,但遵循Three.js代码中所做的工作。

           

        怎么做?

            要挂钩材料,先找到材料

            1.使用onBeforeCompile() 这是一个空函数 ,在材料编译之前,3GS回自动调用此函数

            可以使用着色器,使用此处的任何参数访问其中的内容

            2.地址\node_modules\three\src\renderers\shaders\ShaderLib\  

            中关于材质部分代码 meshphysical.glsl    #include <begin_vertex>

            在字符中注入代码

            3.扭曲模型,根据海拔高度进行不同的旋转

                https://thebookofshaders.com/08/

            4.人物旋转 ,会发现阴影有问题

            5. uniforms制服 (公共数据) 添加uTime旋转

            6.修复阴影

                为什么阴影有问题,因为设置阴影贴图是深度材质(深度材质没有阴影),并没有扭曲

                投射阴影没有变化

                法线没有变化,法线告诉我们什么是外部

                模型阴影没有变化

            7. 需要创建自己的网络深度材质

                MeshDepthMaterial

                之后和设置material一样设置自定义深度材质,注入自己的代码,让阴影扭曲

                投影阴影好了,模型阴影还没

            8. 需要旋转法线

                \node_modules\three\src\renderers\shaders\ShaderChunk

                beginnormal_vertex.glsl.js

                两个变量 objectNormal ,objectTangent

这里注意 node_modules里面的shader文件,对应代码我们通过下面方式实现代码的注入。


一、代码

学习如何改进3DS内置材质,shader ,这里放到一个文件下,也可以和之前的一样分出去。

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import * as dat from 'lil-gui'


/**
 * Base
 */
// Debug
const gui = new dat.GUI()

// Canvas
const canvas = document.querySelector('canvas.webgl')

// Scene
const scene = new THREE.Scene()

/**
 * Loaders
 */
const textureLoader = new THREE.TextureLoader()
const gltfLoader = new GLTFLoader()
const cubeTextureLoader = new THREE.CubeTextureLoader()

/**
 * Update all materials
 */
const updateAllMaterials = () =>
{
    scene.traverse((child) =>
    {
        if(child instanceof THREE.Mesh && child.material instanceof THREE.MeshStandardMaterial)
        {
            child.material.envMapIntensity = 1
            child.material.needsUpdate = true
            child.castShadow = true
            child.receiveShadow = true
        }
    })
}

/**
 * Environment map
 */
const environmentMap = cubeTextureLoader.load([
    '/textures/environmentMaps/0/px.jpg',
    '/textures/environmentMaps/0/nx.jpg',
    '/textures/environmentMaps/0/py.jpg',
    '/textures/environmentMaps/0/ny.jpg',
    '/textures/environmentMaps/0/pz.jpg',
    '/textures/environmentMaps/0/nz.jpg'
])
environmentMap.encoding = THREE.sRGBEncoding

scene.background = environmentMap
scene.environment = environmentMap

/**
 * Material
 */

// Textures
const mapTexture = textureLoader.load('/models/LeePerrySmith/color.jpg')
mapTexture.encoding = THREE.sRGBEncoding

const normalTexture = textureLoader.load('/models/LeePerrySmith/normal.jpg')

// Material
const material = new THREE.MeshStandardMaterial( {
    map: mapTexture,
    normalMap: normalTexture
})

// 创建自己的网络深度材质
const depthMaterial = new THREE.MeshDepthMaterial({
    depthPacking: THREE.RGBADepthPacking //depth packing的编码。
})

const customUniforms = {
    uTime:{value:0},
    uSpeed:{value:0.2},
}

material.onBeforeCompile = (shader) =>{ // 材质加载之前调用
    console.log(shader)

    shader.uniforms.uTime = customUniforms.uTime
    shader.uniforms.uSpeed = customUniforms.uSpeed

    // 这里将node_modules中关于材质部分着色器代码替换
    // 只是现在我们可以添加自己的代码更改转换后的变量

    shader.vertexShader = shader.vertexShader.replace(  // common 中添加了扭曲函数函数
        '#include <common>',
        ` 
            #include <common> 

            uniform float uTime;
            uniform float uSpeed;

            mat2 get2dRotateMatrix(float _angle){
                return mat2(cos(_angle),-sin(_angle),sin(_angle),cos(_angle));
            }
        `
    )

    // normal 法线
    shader.vertexShader = shader.vertexShader.replace(  // common 中添加了扭曲函数函数
        '#include <beginnormal_vertex>',
        ` 
            #include <beginnormal_vertex>
            float angle = sin(position.y + uTime) * uSpeed ;
            mat2 rotateMatrix = get2dRotateMatrix(angle);

            objectNormal.xz = rotateMatrix *  objectNormal.xz;
        `
    )

    // 执行扭曲函数,transformed进行旋转, (阴影有问题)
    // 加入uTime ,跟随时间变化旋转
    // 加入但是没有动画 在tick()中也要加入,但是没办法访问uTime
    // 在外部创建uTime
    shader.vertexShader = shader.vertexShader.replace( 
        '#include <begin_vertex>',
        ` 
            #include <begin_vertex> 
            // float angle = sin(position.y + uTime) * uSpeed ; // 这里注释调,因为在上一步中已经旋转,同样在同一个函数中不能重复定义相同变量
            // mat2 rotateMatrix = get2dRotateMatrix(angle);

            transformed.xz = rotateMatrix *  transformed.xz;
        `
    )

    
}

depthMaterial.onBeforeCompile = (shader) =>{ // 自定义深度材质
    shader.uniforms.uTime = customUniforms.uTime
    shader.uniforms.uSpeed = customUniforms.uSpeed
    shader.vertexShader = shader.vertexShader.replace(  // common 中添加了扭曲函数函数
        '#include <common>',
        ` 
            #include <common> 

            uniform float uTime;
            uniform float uSpeed;

            mat2 get2dRotateMatrix(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(position.y + uTime) * uSpeed ;
            mat2 rotateMatrix = get2dRotateMatrix(angle);

            transformed.xz = rotateMatrix *  transformed.xz;
        `
    )
}

gui.add(customUniforms.uSpeed,'value').min(0).max(1).step(0.01).name('速度')

/**
 * Models
 */
gltfLoader.load(
    '/models/LeePerrySmith/LeePerrySmith.glb',
    (gltf) =>
    {
        // Model
        const mesh = gltf.scene.children[0]
        mesh.rotation.y = Math.PI * 0.5
        mesh.material = material
        mesh.customDepthMaterial = depthMaterial
        scene.add(mesh)

        // Update materials
        updateAllMaterials()
    }
)

/* 
    plan
*/
const plan = new THREE.Mesh(
    new THREE.PlaneBufferGeometry(15,15,15),
    new THREE.MeshStandardMaterial({})
)
plan.rotation.y = Math.PI
plan.position.y = -5
plan.position.z = 5
scene.add(plan)

/**
 * Lights
 */
const directionalLight = new THREE.DirectionalLight('#ffffff', 3)
directionalLight.castShadow = true
directionalLight.shadow.mapSize.set(1024, 1024)
directionalLight.shadow.camera.far = 15
directionalLight.shadow.normalBias = 0.05
directionalLight.position.set(0.25, 2, - 2.25)
scene.add(directionalLight)

/**
 * Sizes
 */
const sizes = {
    width: window.innerWidth,
    height: window.innerHeight
}

window.addEventListener('resize', () =>
{
    // Update sizes
    sizes.width = window.innerWidth
    sizes.height = window.innerHeight

    // Update camera
    camera.aspect = sizes.width / sizes.height
    camera.updateProjectionMatrix()

    // Update renderer
    renderer.setSize(sizes.width, sizes.height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.set(4, 1, - 4)
scene.add(camera)

// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    antialias: true
})
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFShadowMap
renderer.physicallyCorrectLights = true
renderer.outputEncoding = THREE.sRGBEncoding
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

/**
 * Animate
 */
const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // update material
    customUniforms.uTime.value = elapsedTime

    // Update controls
    controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()

二、效果

对应代码可以自己修改,对应视频中的样式,上面代码属于完全版本的,一些实现的难点可以自己去试错

shader- 人物模型 扭曲 旋转

shader- 人物模型 扭曲 旋转阴影问题

shader- 人物模型 扭曲 旋转


总结

我们可以加入一些变量控制gui,实现扭曲的变化!