💅🏻

three.js + GLSLでcolorを正規化して動的に扱う

2022/08/14に公開

はじめに

GLSLで使う色を動的に変更して正規化する方法をまとめました。

サンプル

ソースコード

index.vue

index.vue
<script setup lang="ts">
import { Ref } from 'vue'

const container: Ref<HTMLElement | null> = ref(null)
const colorCode: Ref<string> = ref('#ffffff')
const { color } = useColor(colorCode)
const { clickAction } = useThree(color)
onMounted(() => {
  const { init } = useThree(color)
  init()
})
</script>

<template>
  <div>
    <div class="container" ref="container">
      <div class="input">
        <input type="color" v-model="colorCode">
        <button @click="clickAction">Change Color</button>
      </div>
    </div>
  </div>
</template>

<style>
.container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.input {
  position: absolute;
  top: 0;
  left: 0;
  display: grid;
  grid-template-rows: repeat(1fr, 2);
}
</style>

color.ts

colorの状態管理

color.ts
import { Ref } from 'vue'

const color: Ref<number> = ref()

export const useColor = (colorCode: Ref<string>) => {
  watchEffect(() => {
    color.value = '0x' + colorCode.value.slice(1) as unknown as number
  })
  return {
    color: readonly(color)
  }
}

three.ts

3Dオブジェクトの状態管理

three.ts
import {
  Mesh,
  OrthographicCamera,
  PlaneGeometry,
  Scene,
  ShaderMaterial,
  Vector2,
  WebGLRenderer
} from "three"
import { Ref } from 'vue'

// シェーダーマテリアル
const mat = new ShaderMaterial({
  vertexShader:
    `
    void main() {
      gl_Position = vec4( position, 1.0 );
    }
    `,
  fragmentShader:
    `
    uniform vec2 uResolution;
    uniform int uColor;

    void main() {
      // 色を正規化
      float rValue = float(uColor / 256 / 256);
      float gValue = float(uColor / 256 - int(rValue * 256.0));
      float bValue = float(uColor - int(rValue * 256.0 * 256.0) - int(gValue * 256.0));
      gl_FragColor = vec4(rValue / 255.0, gValue / 255.0, bValue / 255.0, 1.0);
    }
    `
})

// ユニフォーム
const uniform = {
  uResolution: {
    value: new Vector2(0, 0)
  },
  uColor: {
    value: 0xffffff
  }
}

// シーン
const scene = new Scene()

// カメラ
const camera = new OrthographicCamera(-1, 1, 1, -1, 0, -1)

// ジオメトリ
const geometry = new PlaneGeometry(2, 2, 10, 10)

// マテリアル
const material = new ShaderMaterial({
  uniforms: uniform,
  vertexShader: mat.vertexShader,
  fragmentShader: mat.fragmentShader,
})

// メッシュ
const mesh = new Mesh(geometry, material)

scene.add(mesh)

export const useThree = (color: Ref<number>) => {
  const renderer = ref()
  const init = (container: Ref<HTMLElement>) => {
    const { clientWidth, clientHeight } = container.value
    uniform.uResolution.value = new Vector2(clientWidth, clientHeight)
    watchEffect(() => {
      renderer.value = new WebGLRenderer()
      renderer.value.setSize(clientWidth, clientHeight)
      renderer.value.setPixelRatio(clientWidth / clientHeight)
    })
    container.value.appendChild(renderer.value.domElement)
    tick()
  }
  const tick = () => {
    renderer.value.render(scene, camera)
    requestAnimationFrame(tick)
  }
  // clickしたらuniformに渡すcolorを変更する
  const clickAction = () => {
    uniform.uColor.value = color.value
  }
  return {
    init,
    clickAction
  }
}

さいごに

GLSLへ渡す色を動的に変更する方法をまとめました。
上記のコードはあくまでサンプルなのでただ背景色を変えているだけになっていますが、使い道によっては面白いものが作れそうです。

Discussion