three.js의 customDepthMaterial을 사용해보십시오.

Three.js Advent Calendar 2019 5일째의 기사입니다.
Object3DcustomDepthMaterial 을 사용할 필요가 있었지만, 조사해도 정보가 거의 나오지 않기 때문에 시험해 보았습니다.

customDepthMaterial 속성 설명은 다음과 같이 작성됩니다.

Custom depth material to be used when rendering to the depth map. Can only be used in context of meshes. When shadow-casting with a DirectionalLight or SpotLight, if you are (a) modifying vertex positions in the vertex shader, (b) using a displacement map, (c) using an alpha map with alphaTest, or (d) using a transparent texture with alphaTest, you must specify a customDepthMaterial for proper shadows. Default is undefined.

실수로 번역하면 이런 느낌입니까.

Custom depth material은 깊이 맵에 그릴 때 사용됩니다. 메쉬에만 사용할 수 있습니다. 평행 라이트나 스포트라이트로부터 그림자를 떨어뜨릴 때에 만약 (a) 정점 쉐이더로 정점 위치를 변경하는, (b) 변위 맵을 사용하는, (c) 알파 테스트로 알파 맵을 사용하는, (d) 알파 테스트 에서 투명 텍스처를 사용하는 경우 적절한 그림자를 그리기 위해 customDepthMaterial을 지정해야합니다. 기본값은 undefined입니다.

요컨대, 셰이더측에서 메쉬의 표시 위치를 바꾸거나 그릴지 어떨지를 바꾸는 경우에는 쉐도우 매핑으로 사용하는 셰이더에도 같은 변경을 실시하는 것이 필요해, 라고 하는 것입니다. customDepthMaterial 프로퍼티을 undefined 그대로 두고 있는 경우에는 MeshDepthMaterial 가 사용됩니다 (소스 코드적으로는 이 근처 입니다).

그래서 customDepthMaterial을 사용하고 싶습니다. 이번은 (a)頂点シェーダーで頂点位置を変更する 의 케이스로 시험해 봅니다.

three.js 버전은 r109입니다.

우선, 정점 위치를 변경하는 머티리얼을 ShaderMaterial 을 사용해 만듭니다. 이번은 ShaderLib 에서 MeshLambertMaterial 에서 사용되고 있는 코드를 당겨 와서 사용합니다. 정점 셰이더는 소스 코드 에서 복사하여 꼭지점 위치를 변경하도록 개조를 하고 있습니다.
// ShaderLib['lambert'].vertexShaderを改造
// original: https://github.com/mrdoob/three.js/blob/r109/src/renderers/shaders/ShaderLib/meshlambert_vert.glsl.js
const vertexShader = `
#define LAMBERT
varying vec3 vLightFront;
varying vec3 vIndirectFront;
#ifdef DOUBLE_SIDED
    varying vec3 vLightBack;
    varying vec3 vIndirectBack;
#endif
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <envmap_pars_vertex>
#include <bsdfs>
#include <lights_pars_begin>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>

// 追記
// 頂点位置を変更するために使用する関数とuniformを定義
float random(vec3 x){
  return fract(sin(dot(x,vec3(12.9898, 78.233, 39.425))) * 43758.5453);
}
uniform float time;

void main() {
  #include <uv_vertex>
  #include <uv2_vertex>
  #include <color_vertex>
  #include <beginnormal_vertex>
  #include <morphnormal_vertex>
  #include <skinbase_vertex>
  #include <skinnormal_vertex>
  #include <defaultnormal_vertex>
  #include <begin_vertex>

  // 追記
  // 頂点位置を法線方向にsinで移動させる
  // transformedは頂点位置のオブジェクト座標系を表している
  transformed += objectNormal * 0.5 * sin(0.5 * time + 10.0 * random(transformed));

  #include <morphtarget_vertex>
  #include <skinning_vertex>
  #include <project_vertex>
  #include <logdepthbuf_vertex>
  #include <clipping_planes_vertex>
  #include <worldpos_vertex>
  #include <envmap_vertex>
  #include <lights_lambert_vertex>
  #include <shadowmap_vertex>
  #include <fog_vertex>
}
`;

const lambert = ShaderLib['lambert'];
const shaderMaterial = new ShaderMaterial({
  vertexShader: vertexShader,
  fragmentShader: lambert.fragmentShader,
  uniforms: Object.assign(
    lambert.uniforms,
    {
      'diffuse': { value: new Color(0x6699ff) },
      'time': { value: 0 },
    },
  ),
});
shaderMaterial.lights = true;

const sphere = new Mesh(
  new SphereBufferGeometry(),
  shaderMaterial
);
sphere.castShadow = true;
scene.add(sphere);

이것을 그리면 다음과 같습니다. 메쉬는 구뇨구뇨가 되어 있지만 지상에 투영되는 그림자는 구대로 남아 있습니다.


다음으로 이 구의 customDepthMaterial 프로퍼티에 세트 하는 그림자 매핑용의 머티리얼을 만듭니다. 기본적으로는 MeshDepthMaterial 그러므로 방금전과 같이 ShaderLib로부터 당겨 와, 정점 쉐이더에 같은 개조를 더합니다.
// ShaderLib['depth'].vertexShaderを改造
// original: https://github.com/mrdoob/three.js/blob/r109/src/renderers/shaders/ShaderLib/depth_vert.glsl.js
const depthVertexShader = `
#include <common>
#include <uv_pars_vertex>
#include <displacementmap_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>

// 追記
// 頂点位置を変更するために使用する関数とuniformを定義
float random(vec3 x){
  return fract(sin(dot(x,vec3(12.9898, 78.233, 39.425))) * 43758.5453);
}
uniform float time;

void main() {
  #include <uv_vertex>
  #include <skinbase_vertex>
  #ifdef USE_DISPLACEMENTMAP
    #include <beginnormal_vertex>
    #include <morphnormal_vertex>
    #include <skinnormal_vertex>
  #endif
  #include <begin_vertex>

  // 追記
  // 先ほどのシェーダーと同じように頂点を移動させている
  transformed += normal * 0.5 * sin(0.5 * time + 10.0 * random(transformed));

  #include <morphtarget_vertex>
  #include <skinning_vertex>
  #include <displacementmap_vertex>
  #include <project_vertex>
  #include <logdepthbuf_vertex>
  #include <clipping_planes_vertex>
}
`;

const depthMaterial = new ShaderMaterial({
  vertexShader: depthVertexShader,
  fragmentShader: ShaderLib['depth'].fragmentShader,
  uniforms: Object.assign(
    ShaderLib['depth'].uniforms,
    {
      'time': { value: 0 },
    },
  ),
  defines: {
    // シャドウマッピングにMeshDepthMaterialを使用するときにはdepthPackingプロパティにRGBADepthPackingを設定しているので、
    // customDepthMaterialでもRGBADepthPackingを使用するようにする
    // ソースコード的にはこのあたり: https://github.com/mrdoob/three.js/blob/r109/src/renderers/webgl/WebGLShadowMap.js#L84
    'DEPTH_PACKING': RGBADepthPacking,
  },
});

const sphere = new Mesh(
  new SphereBufferGeometry(),
  shaderMaterial
);
sphere.castShadow = true;
sphere.customDepthMaterial = depthMaterial; // customDepthMaterialを適用する
scene.add(sphere);

그리면 다음과 같이 그림자도 똑같이 구뇨구뇨가 되어 있네요.


데모와 소스 코드는 GitHub에 둡니다.
데모 : htps : // 오로 b에서 b. 기주 b. 이오/사 mpぇ_Th 에_쿠 s와 m로 pth 마테리아 l/
소스 코드 : htps : // 기주 b. 여기 m / 오 b에서 b / mp ぇ_ Th ree _ s와 m으로 pth 마테리아 l

여담입니다. Object3D에는 customDistanceMaterial이라는 속성이 있으며, 이것은 포인트 라이트로 그림자를 떨어 뜨릴 때 필요합니다. 아무것도 지정하지 않으면 MeshDistanceMaterial이 사용됩니다.

좋은 웹페이지 즐겨찾기