webgl_raymarching_reflect.html 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgl - raymarching - reflect</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  7. <style type="text/css">
  8. html, body {
  9. height: 100%;
  10. }
  11. body {
  12. background-color: black;
  13. margin: 0;
  14. padding: 0;
  15. }
  16. a { color: skyblue }
  17. #container {
  18. width: 100%;
  19. height: 100%;
  20. display: flex;
  21. align-items: center;
  22. justify-content: center;
  23. }
  24. #info {
  25. position: absolute;
  26. color: white;
  27. font-size: 13px;
  28. bottom: 10px;
  29. width: 100%;
  30. text-align: center;
  31. z-index: 100;
  32. }
  33. </style>
  34. </head>
  35. <body>
  36. <div id="info">
  37. <a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl raymarching example -
  38. reflect by <a href="https://github.com/gam0022" target="_blank" rel="noopener">gam0022</a> (<a href="http://qiita.com/gam0022/items/03699a07e4a4b5f2d41f" target="_blank" rel="noopener">article</a>)
  39. </div>
  40. <div id="container">
  41. <canvas id="canvas"></canvas>
  42. </div>
  43. <script id="fragment_shader" type="x-shader/x-fragment">
  44. precision highp float;
  45. uniform vec2 resolution;
  46. uniform mat4 viewMatrix;
  47. uniform vec3 cameraPosition;
  48. uniform mat4 cameraWorldMatrix;
  49. uniform mat4 cameraProjectionMatrixInverse;
  50. const float EPS = 0.01;
  51. const float OFFSET = EPS * 100.0;
  52. const vec3 lightDir = vec3( -0.48666426339228763, 0.8111071056538127, -0.3244428422615251 );
  53. // distance functions
  54. vec3 opRep( vec3 p, float interval ) {
  55. vec2 q = mod( p.xz, interval ) - interval * 0.5;
  56. return vec3( q.x, p.y, q.y );
  57. }
  58. float sphereDist( vec3 p, float r ) {
  59. return length( opRep( p, 3.0 ) ) - r;
  60. }
  61. float floorDist( vec3 p ){
  62. return dot(p, vec3( 0.0, 1.0, 0.0 ) ) + 1.0;
  63. }
  64. vec4 minVec4( vec4 a, vec4 b ) {
  65. return ( a.a < b.a ) ? a : b;
  66. }
  67. float checkeredPattern( vec3 p ) {
  68. float u = 1.0 - floor( mod( p.x, 2.0 ) );
  69. float v = 1.0 - floor( mod( p.z, 2.0 ) );
  70. if ( ( u == 1.0 && v < 1.0 ) || ( u < 1.0 && v == 1.0 ) ) {
  71. return 0.2;
  72. } else {
  73. return 1.0;
  74. }
  75. }
  76. vec3 hsv2rgb( vec3 c ) {
  77. vec4 K = vec4( 1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0 );
  78. vec3 p = abs( fract( c.xxx + K.xyz ) * 6.0 - K.www );
  79. return c.z * mix( K.xxx, clamp( p - K.xxx, 0.0, 1.0 ), c.y );
  80. }
  81. float sceneDist( vec3 p ) {
  82. return min(
  83. sphereDist( p, 1.0 ),
  84. floorDist( p )
  85. );
  86. }
  87. vec4 sceneColor( vec3 p ) {
  88. return minVec4(
  89. // 3 * 6 / 2 = 9
  90. vec4( hsv2rgb(vec3( ( p.z + p.x ) / 9.0, 1.0, 1.0 ) ), sphereDist( p, 1.0 ) ),
  91. vec4( vec3( 0.5 ) * checkeredPattern( p ), floorDist( p ) )
  92. );
  93. }
  94. vec3 getNormal( vec3 p ) {
  95. return normalize(vec3(
  96. sceneDist(p + vec3( EPS, 0.0, 0.0 ) ) - sceneDist(p + vec3( -EPS, 0.0, 0.0 ) ),
  97. sceneDist(p + vec3( 0.0, EPS, 0.0 ) ) - sceneDist(p + vec3( 0.0, -EPS, 0.0 ) ),
  98. sceneDist(p + vec3( 0.0, 0.0, EPS ) ) - sceneDist(p + vec3( 0.0, 0.0, -EPS ) )
  99. ));
  100. }
  101. float getShadow( vec3 ro, vec3 rd ) {
  102. float h = 0.0;
  103. float c = 0.0;
  104. float r = 1.0;
  105. float shadowCoef = 0.5;
  106. for ( float t = 0.0; t < 50.0; t++ ) {
  107. h = sceneDist( ro + rd * c );
  108. if ( h < EPS ) return shadowCoef;
  109. r = min( r, h * 16.0 / c );
  110. c += h;
  111. }
  112. return 1.0 - shadowCoef + r * shadowCoef;
  113. }
  114. vec3 getRayColor( vec3 origin, vec3 ray, out vec3 pos, out vec3 normal, out bool hit ) {
  115. // marching loop
  116. float dist;
  117. float depth = 0.0;
  118. pos = origin;
  119. for ( int i = 0; i < 64; i++ ){
  120. dist = sceneDist( pos );
  121. depth += dist;
  122. pos = origin + depth * ray;
  123. if ( abs(dist) < EPS ) break;
  124. }
  125. // hit check and calc color
  126. vec3 color;
  127. if ( abs(dist) < EPS ) {
  128. normal = getNormal( pos );
  129. float diffuse = clamp( dot( lightDir, normal ), 0.1, 1.0 );
  130. float specular = pow( clamp( dot( reflect( lightDir, normal ), ray ), 0.0, 1.0 ), 10.0 );
  131. float shadow = getShadow( pos + normal * OFFSET, lightDir );
  132. color = ( sceneColor( pos ).rgb * diffuse + vec3( 0.8 ) * specular ) * max( 0.5, shadow );
  133. hit = true;
  134. } else {
  135. color = vec3( 0.0 );
  136. }
  137. return color - pow( clamp( 0.05 * depth, 0.0, 0.6 ), 2.0 );
  138. }
  139. void main(void) {
  140. // screen position
  141. vec2 screenPos = ( gl_FragCoord.xy * 2.0 - resolution ) / resolution;
  142. // ray direction in normalized device coordinate
  143. vec4 ndcRay = vec4( screenPos.xy, 1.0, 1.0 );
  144. // convert ray direction from normalized device coordinate to world coordinate
  145. vec3 ray = ( cameraWorldMatrix * cameraProjectionMatrixInverse * ndcRay ).xyz;
  146. ray = normalize( ray );
  147. // camera position
  148. vec3 cPos = cameraPosition;
  149. // cast ray
  150. vec3 color = vec3( 0.0 );
  151. vec3 pos, normal;
  152. bool hit;
  153. float alpha = 1.0;
  154. for ( int i = 0; i < 3; i++ ) {
  155. color += alpha * getRayColor( cPos, ray, pos, normal, hit );
  156. alpha *= 0.3;
  157. ray = normalize( reflect( ray, normal ) );
  158. cPos = pos + normal * OFFSET;
  159. if ( !hit ) break;
  160. }
  161. gl_FragColor = vec4( color, 1.0 );
  162. }
  163. </script>
  164. <script id="vertex_shader" type="x-shader/x-vertex">
  165. attribute vec3 position;
  166. void main(void) {
  167. gl_Position = vec4(position, 1.0);
  168. }
  169. </script>
  170. <script src="../build/three.js"></script>
  171. <script src="js/controls/OrbitControls.js"></script>
  172. <script src="js/libs/stats.min.js"></script>
  173. <script src="js/libs/dat.gui.min.js"></script>
  174. <script>
  175. var dolly, camera, scene, renderer;
  176. var geometry, material, mesh;
  177. var stats;
  178. var canvas = document.querySelector( '#canvas' );
  179. var config = {
  180. saveImage: function () {
  181. renderer.render( scene, camera );
  182. window.open( canvas.toDataURL() );
  183. },
  184. resolution: '512'
  185. };
  186. init();
  187. render();
  188. function init() {
  189. renderer = new THREE.WebGLRenderer( { canvas: canvas } );
  190. renderer.setPixelRatio( window.devicePixelRatio );
  191. renderer.setSize( config.resolution, config.resolution );
  192. window.addEventListener( 'resize', onWindowResize );
  193. // Scene
  194. scene = new THREE.Scene();
  195. dolly = new THREE.Group();
  196. scene.add( dolly );
  197. camera = new THREE.PerspectiveCamera( 60, canvas.width / canvas.height, 1, 2000 );
  198. camera.position.z = 4;
  199. dolly.add( camera );
  200. geometry = new THREE.PlaneBufferGeometry( 2.0, 2.0 );
  201. material = new THREE.RawShaderMaterial( {
  202. uniforms: {
  203. resolution: { value: new THREE.Vector2( canvas.width, canvas.height ) },
  204. cameraWorldMatrix: { value: camera.matrixWorld },
  205. cameraProjectionMatrixInverse: { value: new THREE.Matrix4().getInverse( camera.projectionMatrix ) }
  206. },
  207. vertexShader: document.getElementById( 'vertex_shader' ).textContent,
  208. fragmentShader: document.getElementById( 'fragment_shader' ).textContent
  209. } );
  210. mesh = new THREE.Mesh( geometry, material );
  211. mesh.frustumCulled = false;
  212. scene.add( mesh );
  213. // Controls
  214. var controls = new THREE.OrbitControls( camera, canvas );
  215. // GUI
  216. var gui = new dat.GUI();
  217. gui.add( config, 'saveImage' ).name( 'Save Image' );
  218. gui.add( config, 'resolution', [ '256', '512', '800', 'full' ] ).name( 'Resolution' ).onChange( onWindowResize );
  219. stats = new Stats();
  220. document.body.appendChild( stats.dom );
  221. }
  222. function onWindowResize() {
  223. if ( config.resolution === 'full' ) {
  224. renderer.setSize( window.innerWidth, window.innerHeight );
  225. } else {
  226. renderer.setSize( config.resolution, config.resolution );
  227. }
  228. camera.aspect = canvas.width / canvas.height;
  229. camera.updateProjectionMatrix();
  230. material.uniforms.resolution.value.set( canvas.width, canvas.height );
  231. material.uniforms.cameraProjectionMatrixInverse.value.getInverse( camera.projectionMatrix );
  232. }
  233. function render( time ) {
  234. stats.begin();
  235. dolly.position.z = - time / 1000;
  236. renderer.render( scene, camera );
  237. stats.end();
  238. requestAnimationFrame( render );
  239. }
  240. </script>
  241. </body>
  242. </html>