当前位置:   article > 正文

three.js学习笔记(十八)——调整材质_threejs修改模型材质

threejs修改模型材质

介绍

到现在为止,我们都在创建新的着色器材质,但是如果我们想要修改一个Three.js内置的材质呢?或许我们对MeshStandardMaterial的处理结果感到满意,但是希望往里边添加顶点动画。

如果重写整个MeshStandardMaterial,那么处理灯光、环境贴图、基于物理的渲染、所有类型的纹理等将花费太多时间。

相反,我们将从MeshStandardMaterial开始,并尝试将自己的代码集成到其着色器中。
有两种方法可以做到:

  1. 通过在编译着色器前触发Three.js钩子,可以让我们处理着色器并注入自己的代码。
  2. 重新创建一个全新材质,使用与Three.js写的代码相同的参数,然后再加上我们自己的参数。

我们会用第一个方法,将以一种有趣的方式扭曲模型顶点,但材质的所有基本特征仍在工作,如阴影、纹理、法线贴图等。

设置

我们将使用于three.js学习笔记(十三)——真实渲染一样的设置,只不过将模型替换为一个头部模型,它只有一个网格和真实纹理,可以与我们下边的扭曲动画相处得来。
在这里插入图片描述
在加载模型前已经创建好了带有贴图和法向贴图的标准网格材质MeshStandardMaterial,然后在模型的唯一网格上使用该材质,该网格最终被添加到场景中。

下边的代码大都跟材质相关。

材质钩子

我们有MeshStandardMaterial,但想修改它的着色器。

要想修改一种材质,必须先访问其原始着色器,为此我们可以使用材质的onBeforeCompile方法,为其指定一个函数,该函数会在编译着色器程序前执行回调,此函数使用shader源码作为参数,用于修改内置材质:

material.onBeforeCompile = (shader) =>
{
    console.log(shader)
    console.log(shader.uniforms)
    console.log(shader.vertexShader)
    console.log(shader.fragmentShader);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述
现在我们可以查看vertexShader ,fragmentShaderuniforms 了。

给顶点着色器添加代码

在这里插入图片描述
通过查看顶点着色器代码,每个#include ...都会插入Three.js依赖包里边特定文件夹中的代码,因此我们可以使用js来替换这部分。
进入到/node_modules/three/src/renders/shaders/文件夹中,这是我们能找到的大多数Three.js着色器代码的地方。
include包裹住的部分称为块chunk,可以在ShaderChunk/文件夹中找到它们。
进入某个块js文件中,可以看到begin_vertex通过创建名为transformed的变量来处理位置。
在这里插入图片描述
接下来我们回到js中替换这部分代码:
在y轴上移动模型

material.onBeforeCompile = (shader) =>
{
    shader.vertexShader = shader.vertexShader.replace(
    '#include <begin_vertex>',
    `
        #include <begin_vertex>

        transformed.y += 3.0;
    `
	)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述
可以观察到确实在y轴方向上移动了,但是阴影出现了些问题,待会再修复,现在移除transformed.y += 3.0;

扭曲

我们将创建一个矩阵来对顶点进行扭曲。
(下边的代码都是位于要替换的glsl中)
首先,我们将尝试以相同的角度旋转所有顶点。然后根据顶点的高度偏移该角度,并为其设置动画。
创建具有任意值的角度变量:
(即便还没有移动顶点,依然可以刷新查看后台是否报错)

#include <begin_vertex>

float angle = 0.3;
  • 1
  • 2
  • 3

在学着色器的时候知道,矩阵就像是一个管道,我们可以在这发送像向量vector这样的数据。管道将对该向量应用变换并输出结果。我们可以创建一个矩阵来缩放顶点,一个来旋转,另一个来移动,甚至可以组合起来,这些矩阵可以处理2D变换、3D变换等等。

在本例中,我们会做一个2D变换,只在x和z轴上旋转顶点进行扭曲,不会在y轴上进行旋转。
使用get2dRotateMatrix函数返回二维矩阵(mat2):
通过The Book of Shaders了解更多

mat2 get2dRotateMatrix(float _angle)
{
    return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));
}
  • 1
  • 2
  • 3
  • 4

我们该把这段代码放在glsl哪个位置?如果我们自己写着色器,则会将其放在main函数前,而在这里,main函数外边的有一个块是common,这个块的优点是它位于所有着色器中,替换它:

material.onBeforeCompile = (shader) =>
{
    // 替换common
    shader.vertexShader = shader.vertexShader.replace(
        '#include <common>',
        `
            #include <common>
            mat2 get2dRotateMatrix(float _angle)
            {
                return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));
            }
        `
    )
    
    // 替换begin_vertex
    // ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

使用get2dRotateMatrix函数创建rotateMatrix矩阵变量,然后旋转该矩阵:

#include <begin_vertex>
    
float angle = 0.3;
mat2 rotateMatrix = get2dRotateMatrix(angle);
transformed.xz = rotateMatrix * transformed.xz;
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述
根据顶点高度改变角度:

float angle = position.y * 0.9;
  • 1

在这里插入图片描述

动画

在函数外部声明一个变量:

const customUniforms = {
  uTime: { value: 0 }
}
  • 1
  • 2
  • 3

跟以往一样,将名为uTimeuniform发送到着色器:

material.onBeforeCompile = function(shader)
{
    shader.uniforms.uTime = customUniforms.uTime

    // ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

common块中检索uTime

#include <common>

uniform float uTime;

mat2 get2dRotateMatrix(float _angle)
{
    return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

回到动画函数中更新uTime的值

const clock = new THREE.Clock()

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

    // Update material
    customUniforms.uTime.value = elapsedTime

    // ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述

修复阴影

正如阴影那节课所说的,在灯光渲染下,网格材质将被深度网格材质MeshDepthMaterial所替代,而我们并没有修改MeshDepthMaterial。
在后边加一个平面可以将看清楚阴影:
在这里插入图片描述
由于阴影使用的是深度网格材质,我们可以在网格上使用customDepthMaterial属性覆盖该材质以便让Three.js使用自定义材质。
首先,创建一个深度网格材质并设置其 depthPacking属性值为THREE.RGBADepthPacking

const depthMaterial = new THREE.MeshDepthMaterial({
    depthPacking: THREE.RGBADepthPacking
})
  • 1
  • 2
  • 3

加载模型时要使用自定义的深度网格材质depthMaterial

gltfLoader.load(
    '/models/LeePerrySmith/LeePerrySmith.glb',
    (gltf) =>
    {
        // ...

        mesh.material = material // Update the material
        mesh.customDepthMaterial = depthMaterial // Update the depth material

        // ...
    }
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

然后跟前面的材质同理:

depthMaterial.onBeforeCompile = (shader) =>
{
    shader.uniforms.uTime = customUniforms.uTime
    shader.vertexShader = shader.vertexShader.replace(
        '#include <common>',
        `
            #include <common>

            uniform float uTime;

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

            transformed.xz = rotateMatrix * transformed.xz;
        `
    )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

现在看平面上的阴影也可以看出在进行扭曲,但是模型上的阴影其实是错误的,看起来阴影在随着顶点旋转,这与法线相关。
在这里插入图片描述

修复法线

当我们旋转顶点时,我们只旋转了位置,但没有旋转法线,因此需要修改处理法线的块。
处理法线的块称为beginnormal_vertex。让我们将其替换为material,记住不是depthMaterial,因为这阴影材质不需要法线:

material.onBeforeCompile = (shader) =>
{
    // ...

    shader.vertexShader = shader.vertexShader.replace(
        '#include <beginnormal_vertex>',
        `
            #include <beginnormal_vertex>
        `
    )

    // ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

如果你去查看
/node_modules/three/src/renderers/shaders/ShaderChunks/beginnormal_vertex.glsl.js,会看到法线变量名为objectNormal,因此我们会对其进行扭曲旋转的相同操作:
(记得移除begin_vertex中的anglerotateMatrix以避免重复声明)

material.onBeforeCompile = function(shader)
{
    // ...

    shader.vertexShader = shader.vertexShader.replace(
        '#include <beginnormal_vertex>',
        `
            #include <beginnormal_vertex>

            float angle = (position.y + uTime) * 0.9;
            mat2 rotateMatrix = get2dRotateMatrix(angle);

            objectNormal.xz = objectNormal.xz * rotateMatrix;
        `
    )
    shader.vertexShader = shader.vertexShader.replace(
        '#include <begin_vertex>',
        `
            #include <begin_vertex>

            transformed.xz = rotateMatrix * transformed.xz;
        `
    )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在这里插入图片描述

源代码

import './style.css'
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 'dat.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 = 5
      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')
const customUniforms = {
  uTime: { value: 0 }
}
// Material
const depthMaterial = new THREE.MeshDepthMaterial({
  depthPacking: THREE.RGBADepthPacking
})
const material = new THREE.MeshStandardMaterial({
  map: mapTexture,
  normalMap: normalTexture
})
material.onBeforeCompile = (shader) => {
  shader.uniforms.uTime = customUniforms.uTime
  shader.vertexShader = shader.vertexShader.replace(
    '#include <common>',
    `
            #include <common>
            uniform float uTime;
            mat2 get2dRotateMatrix(float _angle)
            {
                return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));
            }
        `
  )
  shader.vertexShader = shader.vertexShader.replace(
    '#include <beginnormal_vertex>',
    `
            #include <beginnormal_vertex>
            
            float angle = (position.y + uTime) * 0.9;
            mat2 rotateMatrix = get2dRotateMatrix(angle);

            objectNormal.xz = objectNormal.xz * rotateMatrix;
        `
  )
  shader.vertexShader = shader.vertexShader.replace(
    '#include <begin_vertex>',
    `
            #include <begin_vertex>

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

depthMaterial.onBeforeCompile = (shader) => {
  shader.uniforms.uTime = customUniforms.uTime
  shader.vertexShader = shader.vertexShader.replace(
    '#include <common>',
    `
            #include <common>

            uniform float uTime;

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

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

/**
 * 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 // Update the material
  mesh.customDepthMaterial = depthMaterial // Update the depth material
  scene.add(mesh)

  // Update materials
  updateAllMaterials()
})

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

/**
 * 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()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/861642
推荐阅读
相关标签
  

闽ICP备14008679号