基于THREEJS场景中模型局部辉光效果

背景

        之前写过一个关于辉光的文章,全场景辉光(传送门),但是有些时候,我们的场景只需要一部分模型辉光,这就是本文的背景。还是基于ThreeJS实现。

原理

        简单来说,就是做两个合成器(Composer),一个用于辉光(glowComposer),一个用于混合辉光和正常渲染(finalComposer)。在创建一个集合对象,用来存储要辉光的模型,主要是用于区分辉光和不辉光的模型,反过来存不辉光的模型也可以,或者用标志来区分也行,宗旨就是将辉光和不辉光的模型能分开识别即可。官网例子使用的是图层layer,因为我觉得图层不利于理解,也比较麻烦,所以本文直接使用数组来代替。

        在场景render的时候,先将不辉光的模型材质设置为黑色,因为辉光一般背景是黑色,模型设置为黑色后,辉光也没有效果。然后先执行辉光合成器的render进行渲染,此时场景渲染辉光,而不需要辉光的模型因为设置了黑色,所以也看不出来,到此,场景中需要辉光的模型就实现了辉光,而不需要辉光的模型目前是看不见的,所以接下来,我们要将材质设置为黑色的模型还原成原本的材质颜色,再执行最终合成器的render进行渲染,因为finalComposer合成器中不包含辉光特效,所以第二次redner后,只渲染了正常不辉光的模型,而第一次渲染辉光的模型也被混合在一起了,所以场景最终就实现了一部分模型辉光,一部分不辉光。

        需要注意的是,将模型设置为黑色后,还原的时候不仅仅是要还原颜色,还要将材质也还原。另外最终合成器使用了一些webGL代码,如果感兴趣可以研究一下,如果单纯实现效果,直接忽略即可。

效果

蓝色六面体为不辉光模型,红色六面体为辉光模型。

          

 

核心代码

// 创建场景封装对象(场景、光源等于本例无关的内容)
const ts = new TS('container')

// 辉光模型集合
const glows = []


// 初始化模型
initModel()

// 初始合成器
const { finalComposer, glowComposer } = initComposer()

// 渲染逻辑
const render = () => {
  // 不辉光的先变黑
  ts.scene.traverse(darkenMaterial)
  // 渲染辉光合成器
  glowComposer.render()
  // 还原不会光的材质
  ts.scene.traverse(restoreMaterial)
  // 渲染最终合成器
  finalComposer.render()
  requestAnimationFrame(this.render)
})

/**
 * 非辉光材质颜色设置黑色
 */
function darkenMaterial (obj) {
  const material = obj.material
  if (material && !glows.includes(obj)) {
    obj.originalMaterial = obj.material
    const Proto = Object.getPrototypeOf(material).constructor
    obj.material = new Proto({ color: ts.scene.background })
  }
}

/**
 * 还原材质
 */
function restoreMaterial (obj) {
  if (!obj.originalMaterial) return
  obj.material = obj.originalMaterial
  delete obj.originalMaterial
}

/**
 * 初始化通道
 */
function initComposer () {
  // 辉光参数
  const params = {
    // 强度
    bloomStrength: 1.5,
    // 阈值
    bloomThreshold: 0,
    // 半径
    bloomRadius: 0
  }
  // 渲染通道
  const renderPass = new RenderPass(ts.scene, ts.camera)
  // 辉光通道
  const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), params.bloomStrength, params.bloomRadius, params.bloomThreshold)

  // 辉光合成器
  const glowComposer = new EffectComposer(ts.renderer)
  glowComposer.renderToScreen = false
  glowComposer.addPass(renderPass)
  glowComposer.addPass(bloomPass)

  // 最终通道
  const finalPass = new ShaderPass(
    new THREE.ShaderMaterial({
      uniforms: {
        baseTexture: { value: null },
        bloomTexture: { value: glowComposer.renderTarget2.texture }
      },
      vertexShader: WebGL.VERTEX_SHADER,
      fragmentShader: WebGL.FRAGMENT_SHADER,
      defines: {}
    }), 'baseTexture'
  )
  finalPass.needsSwap = true
  // 最终合成器
  const finalComposer = new EffectComposer(ts.renderer)
  finalComposer.addPass(renderPass)
  finalComposer.addPass(finalPass)

  // UI调试
  const gui = new GUI()
  window.gui = gui

  gui.add(params, 'bloomThreshold', 0.0, 1.0).step(0.01).name('阈值').onChange(function (value) {
    bloomPass.threshold = Number(value)
  })

  // 强度 在0-10之间可正常看到物体,超过10会因光线过强而看不见物体,步长建议0.01
  gui.add(params, 'bloomStrength', 0, 10).step(0.01).name('强度').onChange(function (value) {
    bloomPass.strength = Number(value)
  })

  gui.add(params, 'bloomRadius', 0.0, 1.0).step(0.01).name('半径').onChange(function (value) {
    bloomPass.radius = Number(value)
  })

  return { finalComposer, glowComposer }
}

/**
 * 初始化模型
 */
function initModel () {
  const boxGeometry = new THREE.BoxGeometry(100, 100, 100)
  // 创建地面材质
  const boxMaterial = new THREE.MeshPhongMaterial({
    color: '#68a5f1',
    side: 0
  })

  // 场景随机创建20个盒子
  for (let i = 0; i < 20; i++) {
    const cubeMesh = new THREE.Mesh(boxGeometry.clone(), boxMaterial.clone())
    cubeMesh.position.setY(50)
    cubeMesh.castShadow = true
    cubeMesh.receiveShadow = true
    cubeMesh.position.set(random(-500, 400), random(-0, 500), random(-500, 400))
    // 偶数的为红色,放入辉光容器,奇数为蓝色,不辉光
    if (i % 2 === 0) {
      glows.push(cubeMesh)
      cubeMesh.material.color = new THREE.Color('#fc2d5d')
    }
    ts.scene.add(cubeMesh)
  }
}

webGL代码:

class WebGL {
  static get VERTEX_SHADER () {
    return '\t\t\tvarying vec2 vUv;\n' +
            '\n' +
            '\t\t\tvoid main() {\n' +
            '\n' +
            '\t\t\t\tvUv = uv;\n' +
            '\n' +
            '\t\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n' +
            '\n' +
            '\t\t\t}'
  }

  static get FRAGMENT_SHADER () {
    return '\t\t\tuniform sampler2D baseTexture;\n' +
            '\t\t\tuniform sampler2D bloomTexture;\n' +
            '\n' +
            '\t\t\tvarying vec2 vUv;\n' +
            '\n' +
            '\t\t\tvoid main() {\n' +
            '\n' +
            '\t\t\t\tgl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );\n' +
            '\n' +
            '\t\t\t}'
  }
}

export { WebGL }

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
three.js 可以通过在模型材质添加 `THREE.ShaderMaterial` 来实现局部辉光效果。 具体实现步骤如下: 1. 创建一个 `THREE.ShaderMaterial` 材质,使用自定义的着色器代码。 ```javascript const customMaterial = new THREE.ShaderMaterial({ uniforms: { glowColor: { value: new THREE.Color(0xffffff) }, viewVector: { value: camera.position } }, vertexShader: ` uniform vec3 viewVector; varying float intensity; void main() { vec3 vNormal = normalize(normalMatrix * normal); vec3 vNormel = normalize(normalMatrix * viewVector); intensity = pow(0.4 - dot(vNormal, vNormel), 4.0); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform vec3 glowColor; varying float intensity; void main() { vec3 glow = glowColor * intensity; gl_FragColor = vec4(glow, 1.0); } `, side: THREE.FrontSide, blending: THREE.AdditiveBlending, transparent: true }); ``` 2. 将材质应用到需要添加辉光效果模型,可以使用 `THREE.Mesh` 或者 `THREE.Points`。 ```javascript const model = new THREE.Mesh(geometry, customMaterial); scene.add(model); ``` 3. 控制局部辉光效果的范围,可以通过修改 `viewVector` uniform 变量的值,该变量表示观察者的位置。可以根据摄像机的位置来设置 `viewVector` 的值。 ```javascript customMaterial.uniforms.viewVector.value = camera.position; ``` 以上代码可以实现一个简单的局部辉光效果,你可以根据自己的需求,修改着色器代码和材质属性来实现更加复杂的效果

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值