👻

Three.jsでShaderMaterialでのグラデーション

2022/09/28に公開

Three.jsでのShaderMaterialを使ったグラデーションの方法のまとめです。

デモ

円周だけのgeometory

Three.jsにはCircleGeometryという円を描画するための関数があり、materialにwireframeを設定することができます。
しかしその場合下の画像のように中心から放射状に線が出るようになってしまいます。
そこで円周だけを表示するようにするため、円周上に点を配置するという方法にしています。

const radius = 5;
const segmentCount = 600;
const segmentPoints = [];

// 円周上に点を配置
const SEG_RADS = (2 * Math.PI) / segmentCount;
for (let c = 0; c < segmentCount; c++) {
const x = radius * Math.cos(c * SEG_RADS);
const y = radius * Math.sin(c * SEG_RADS);
segmentPoints.push(new THREE.Vector3(x, y, 0));
}
// 円のgeometry作成
const stemCurve = new THREE.CatmullRomCurve3(segmentPoints, true);
const splinePoints = stemCurve.getPoints(segmentCount);
const geometry = new THREE.BufferGeometry().setFromPoints(splinePoints);

const material = ...
const circle = new THREE.Line(geometry, material);

http://www.wayneparrott.com/how-to-create-three-js-gradient-colored-lines-and-circlelines-part-2/

ShaderMaterialを使ったグラデーション

material部分

geometry.computeBoundingBox() でgeometoryのbounding boxを計算しています。これにより物体の境界を取得できるようになります。
bounding boxというのは物体を取り囲む最小の四角形で、パワポなどで図形を選択したら出てくるやつです。
ShaderMaterialのuniformsではGLSLに色とboundingBoxの最小、最大値を渡しています。

https://en.wikipedia.org/wiki/Minimum_bounding_box

geometry.computeBoundingBox();

const material = new THREE.ShaderMaterial({
  uniforms: {
    color1: {
      value: new THREE.Color(color1)
    },
    color2: {
      value: new THREE.Color(color2)
    },
    boundingBoxMin: {
      value: geometry.boundingBox.min
    },
    boundingBoxMax: {
      value: geometry.boundingBox.max
    }
  },
  vertexShader,
  fragmentShader,
  opacity: 1
});
const circle = new THREE.Line(geometry, material);

GLSL部分

vertexShaderではy座標に関しての正規化をしています。positionについては
fragmentShaderでは2色の色を、vertexShaderから渡されたuvのy座標で線形補完してグラデーションを実現しています。
今回はy座標でのグラデーションですが、使う座標を変更すればグラデーションの方向を変えることもできます。

fragment shaderデモ
https://glslsandbox.com/e#91884.0

// グラデーションのためのvertex shader
const vertexShader = `
  uniform vec3 boundingBoxMin;
  uniform vec3 boundingBoxMax;
  varying vec2 vUv;
  void main() {
    vUv.y = (position.y - boundingBoxMin.y) / (boundingBoxMax.y - boundingBoxMin.y);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
  }
`;

// グラデーションのためのfragment shader
const fragmentShader = `
  uniform vec3 color1;
  uniform vec3 color2;
  varying vec2 vUv;
  void main() {
    gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
  }
`;

https://stackoverflow.com/questions/52614371/apply-color-gradient-to-material-on-mesh-three-js/52615186#52615186
https://sbcode.net/threejs/geometry-to-buffergeometry/
http://www.wayneparrott.com/how-to-create-three-js-gradient-colored-lines-and-circlelines-part-2/

Discussion