GroundProjectedSkybox.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import { Mesh, IcosahedronGeometry, ShaderMaterial, DoubleSide } from 'three';
  2. /**
  3. * Ground projected env map adapted from @react-three/drei.
  4. * https://github.com/pmndrs/drei/blob/master/src/core/Environment.tsx
  5. */
  6. class GroundProjectedSkybox extends Mesh {
  7. constructor( texture, options = {} ) {
  8. const isCubeMap = texture.isCubeTexture;
  9. const defines = [
  10. isCubeMap ? '#define ENVMAP_TYPE_CUBE' : ''
  11. ];
  12. const vertexShader = /* glsl */ `
  13. varying vec3 vWorldPosition;
  14. void main() {
  15. vec4 worldPosition = ( modelMatrix * vec4( position, 1.0 ) );
  16. vWorldPosition = worldPosition.xyz;
  17. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  18. }
  19. `;
  20. const fragmentShader = defines.join( '\n' ) + /* glsl */ `
  21. varying vec3 vWorldPosition;
  22. uniform float radius;
  23. uniform float height;
  24. uniform float angle;
  25. #ifdef ENVMAP_TYPE_CUBE
  26. uniform samplerCube map;
  27. #else
  28. uniform sampler2D map;
  29. #endif
  30. // From: https://www.shadertoy.com/view/4tsBD7
  31. float diskIntersectWithBackFaceCulling( vec3 ro, vec3 rd, vec3 c, vec3 n, float r )
  32. {
  33. float d = dot ( rd, n );
  34. if( d > 0.0 ) { return 1e6; }
  35. vec3 o = ro - c;
  36. float t = - dot( n, o ) / d;
  37. vec3 q = o + rd * t;
  38. return ( dot( q, q ) < r * r ) ? t : 1e6;
  39. }
  40. // From: https://www.iquilezles.org/www/articles/intersectors/intersectors.htm
  41. float sphereIntersect( vec3 ro, vec3 rd, vec3 ce, float ra ) {
  42. vec3 oc = ro - ce;
  43. float b = dot( oc, rd );
  44. float c = dot( oc, oc ) - ra * ra;
  45. float h = b * b - c;
  46. if( h < 0.0 ) { return -1.0; }
  47. h = sqrt( h );
  48. return - b + h;
  49. }
  50. vec3 project() {
  51. vec3 p = normalize( vWorldPosition );
  52. vec3 camPos = cameraPosition;
  53. camPos.y -= height;
  54. float intersection = sphereIntersect( camPos, p, vec3( 0.0 ), radius );
  55. if( intersection > 0.0 ) {
  56. vec3 h = vec3( 0.0, - height, 0.0 );
  57. float intersection2 = diskIntersectWithBackFaceCulling( camPos, p, h, vec3( 0.0, 1.0, 0.0 ), radius );
  58. p = ( camPos + min( intersection, intersection2 ) * p ) / radius;
  59. } else {
  60. p = vec3( 0.0, 1.0, 0.0 );
  61. }
  62. return p;
  63. }
  64. #include <common>
  65. void main() {
  66. vec3 projectedWorldPosition = project();
  67. #ifdef ENVMAP_TYPE_CUBE
  68. vec3 outcolor = textureCube( map, projectedWorldPosition ).rgb;
  69. #else
  70. vec3 direction = normalize( projectedWorldPosition );
  71. vec2 uv = equirectUv( direction );
  72. vec3 outcolor = texture2D( map, uv ).rgb;
  73. #endif
  74. gl_FragColor = vec4( outcolor, 1.0 );
  75. #include <tonemapping_fragment>
  76. #include <colorspace_fragment>
  77. }
  78. `;
  79. const uniforms = {
  80. map: { value: texture },
  81. height: { value: options.height || 15 },
  82. radius: { value: options.radius || 100 },
  83. };
  84. const geometry = new IcosahedronGeometry( 1, 16 );
  85. const material = new ShaderMaterial( {
  86. uniforms,
  87. fragmentShader,
  88. vertexShader,
  89. side: DoubleSide,
  90. } );
  91. super( geometry, material );
  92. }
  93. set radius( radius ) {
  94. this.material.uniforms.radius.value = radius;
  95. }
  96. get radius() {
  97. return this.material.uniforms.radius.value;
  98. }
  99. set height( height ) {
  100. this.material.uniforms.height.value = height;
  101. }
  102. get height() {
  103. return this.material.uniforms.height.value;
  104. }
  105. }
  106. export { GroundProjectedSkybox };