threejs-component.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. import { THREE, RenderPass, ShaderPass, FXAAShader, ACESFilmicToneMappingShader } from './three-defs.js';
  2. import Stats from 'three/examples/jsm/libs/stats.module.js';
  3. import * as entity from "./entity.js";
  4. import * as light_component from './render/light-component.js';
  5. import * as shaders from '../game/render/shaders.js';
  6. const HEMI_UP = new THREE.Color().setHex(0x7CBFFF, THREE.SRGBColorSpace);
  7. const HEMI_DOWN = new THREE.Color().setHex(0xE5BCFF, THREE.SRGBColorSpace);
  8. const HEMI_INTENSITY = 0.25;
  9. const LIGHT_INTENSITY = 0.7;
  10. const LIGHT_COLOUR = new THREE.Color().setRGB(0.52, 0.66, 0.99, THREE.SRGBColorSpace);
  11. const LIGHT_FAR = 1000.0;
  12. const GammaCorrectionShader2 = {
  13. name: 'GammaCorrectionShader2',
  14. uniforms: {
  15. 'tDiffuse': { value: null },
  16. 'exposure': { value: 1.0 },
  17. },
  18. vertexShader: /* glsl */`
  19. varying vec2 vUv;
  20. void main() {
  21. vUv = uv;
  22. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  23. }`,
  24. fragmentShader: /* glsl */`
  25. uniform sampler2D tDiffuse;
  26. varying vec2 vUv;
  27. #define saturate(a) clamp( a, 0.0, 1.0 )
  28. float inverseLerp(float minValue, float maxValue, float v) {
  29. return (v - minValue) / (maxValue - minValue);
  30. }
  31. float remap(float v, float inMin, float inMax, float outMin, float outMax) {
  32. float t = inverseLerp(inMin, inMax, v);
  33. return mix(outMin, outMax, t);
  34. }
  35. uniform float exposure;
  36. vec3 RRTAndODTFit( vec3 v ) {
  37. vec3 a = v * ( v + 0.0245786 ) - 0.000090537;
  38. vec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;
  39. return a / b;
  40. }
  41. vec3 ACESFilmicToneMapping( vec3 color ) {
  42. // sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
  43. const mat3 ACESInputMat = mat3(
  44. vec3( 0.59719, 0.07600, 0.02840 ), // transposed from source
  45. vec3( 0.35458, 0.90834, 0.13383 ),
  46. vec3( 0.04823, 0.01566, 0.83777 )
  47. );
  48. // ODT_SAT => XYZ => D60_2_D65 => sRGB
  49. const mat3 ACESOutputMat = mat3(
  50. vec3( 1.60475, -0.10208, -0.00327 ), // transposed from source
  51. vec3( -0.53108, 1.10813, -0.07276 ),
  52. vec3( -0.07367, -0.00605, 1.07602 )
  53. );
  54. color = ACESInputMat * color;
  55. // Apply RRT and ODT
  56. color = RRTAndODTFit( color );
  57. color = ACESOutputMat * color;
  58. // Clamp to [0, 1]
  59. return saturate( color );
  60. }
  61. vec3 vignette(vec2 uvs) {
  62. float v1 = smoothstep(0.5, 0.3, abs(uvs.x - 0.5));
  63. float v2 = smoothstep(0.5, 0.3, abs(uvs.y - 0.5));
  64. float v = v1 * v2;
  65. v = pow(v, 0.125);
  66. v = remap(v, 0.0, 1.0, 0.4, 1.0);
  67. return vec3(v);
  68. }
  69. void main() {
  70. vec4 tex = texture2D( tDiffuse, vUv );
  71. tex.rgb *= exposure / 0.6; // pre-exposed, outside of the tone mapping function
  72. tex.rgb = ACESFilmicToneMapping( tex.rgb );
  73. tex = LinearTosRGB(tex);
  74. tex.rgb *= vignette(vUv);
  75. gl_FragColor = tex;
  76. }`
  77. };
  78. const Copy2 = {
  79. name: 'Copy2',
  80. uniforms: {
  81. 'tDiffuse': { value: null }
  82. },
  83. vertexShader: /* glsl */`
  84. varying vec2 vUv;
  85. void main() {
  86. vUv = uv;
  87. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  88. }`,
  89. fragmentShader: /* glsl */`
  90. uniform sampler2D tDiffuse;
  91. varying vec2 vUv;
  92. void main() {
  93. vec4 tex = texture2D( tDiffuse, vUv );
  94. gl_FragColor = tex;
  95. }`
  96. };
  97. class FakeCSM {
  98. constructor() {
  99. this.lights = [{
  100. color: new THREE.Color(0xFFFFFF),
  101. lightDirection: new THREE.Vector3(1, 0, 0),
  102. }];
  103. this.lightDirection = new THREE.Vector3(1, 0, 0);
  104. }
  105. setupMaterial() {}
  106. updateFrustums() {}
  107. update() {}
  108. }
  109. export const threejs_component = (() => {
  110. class ThreeJSController extends entity.Component {
  111. static CLASS_NAME = 'ThreeJSController';
  112. get NAME() {
  113. return ThreeJSController.CLASS_NAME;
  114. }
  115. #threejs_;
  116. #csm_;
  117. #ssaoPass_;
  118. #opaqueScene_;
  119. #opaquePass_;
  120. #waterScene_;
  121. #waterPass_;
  122. #opaqueCamera_;
  123. #waterCamera_;
  124. #transparentScene_;
  125. #transparentPass_;
  126. #transparentCamera_;
  127. #waterTexturePass_;
  128. #fxaaPass_;
  129. #acesPass_;
  130. #gammaPass_;
  131. #copyPass_;
  132. #grassTimingAvg_;
  133. constructor() {
  134. super();
  135. this.#threejs_ = null;
  136. this.#ssaoPass_ = null;
  137. this.#opaqueScene_ = null;
  138. this.#opaquePass_ = null;
  139. this.#opaqueCamera_ = null;
  140. this.#waterScene_ = null;
  141. this.#waterCamera_ = null;
  142. this.#waterPass_ = null;
  143. this.#waterScene_ = null;
  144. this.#waterCamera_ = null;
  145. this.#waterPass_ = null;
  146. this.#waterTexturePass_ = null;
  147. this.#fxaaPass_ = null;
  148. this.#acesPass_ = null;
  149. this.#gammaPass_ = null;
  150. this.#copyPass_ = null;
  151. this.#csm_ = null;
  152. this.grassTimingAvg_ = 0;
  153. this.timerQuery = null;
  154. }
  155. InitEntity() {
  156. shaders.SetThreeJS(this);
  157. this.#threejs_ = new THREE.WebGLRenderer({
  158. antialias: false,
  159. powerPreference: 'high-performance',
  160. });
  161. this.#threejs_.shadowMap.enabled = true;
  162. this.#threejs_.shadowMap.type = THREE.PCFSoftShadowMap;
  163. this.#threejs_.setSize(window.innerWidth, window.innerHeight);
  164. this.#threejs_.domElement.id = 'threejs';
  165. this.#threejs_.outputColorSpace = THREE.LinearSRGBColorSpace;
  166. document.getElementById('container').appendChild(this.#threejs_.domElement);
  167. window.addEventListener('resize', () => {
  168. this.onWindowResize_();
  169. }, false);
  170. const fov = 60;
  171. const aspect = 1920 / 1080;
  172. const near = 0.1;
  173. const far = 10000.0;
  174. this.#opaqueCamera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
  175. this.#opaqueCamera_.position.set(20, 5, 15);
  176. this.#waterCamera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
  177. this.#opaqueScene_ = new THREE.Scene();
  178. this.#opaqueScene_.add(this.#opaqueCamera_);
  179. this.#waterScene_ = new THREE.Scene();
  180. this.#waterScene_.add(this.#waterCamera_);
  181. this.#transparentScene_ = new THREE.Scene();
  182. this.#transparentCamera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
  183. this.#transparentScene_.add(this.#transparentCamera_);
  184. this.uiCamera_ = new THREE.OrthographicCamera(
  185. -1, 1, 1, -1, 1, 1000);
  186. this.uiScene_ = new THREE.Scene();
  187. this.#opaqueScene_.fog = new THREE.FogExp2(0xDFE9F3, 0.0001);
  188. this.#opaqueScene_.fog.color.setRGB(0.45, 0.8, 1.0, THREE.SRGBColorSpace);
  189. let light = new THREE.DirectionalLight(0xFFFFFF, LIGHT_INTENSITY);
  190. light.position.set(-20, 20, 20);
  191. light.target.position.set(0, 0, 0);
  192. light.color.copy(LIGHT_COLOUR);
  193. this.#csm_ = new FakeCSM();
  194. // VIDEO HACK
  195. light.castShadow = true;
  196. light.shadow.bias = -0.001;
  197. light.shadow.mapSize.width = 4096;
  198. light.shadow.mapSize.height = 4096;
  199. light.shadow.camera.near = 1.0;
  200. light.shadow.camera.far = 100.0;
  201. light.shadow.camera.left = 32;
  202. light.shadow.camera.right = -32;
  203. light.shadow.camera.top = 32;
  204. light.shadow.camera.bottom = -32;
  205. this.#opaqueScene_.add(light);
  206. this.#opaqueScene_.add(light.target);
  207. const lightDir = light.position.clone();
  208. lightDir.normalize();
  209. lightDir.multiplyScalar(-1);
  210. const csmFar = LIGHT_FAR;
  211. // this.#csm_ = new CSM({
  212. // maxFar: csmFar,
  213. // fade: true,
  214. // cascades: 6,
  215. // mode: 'practical',
  216. // parent: this.#opaqueScene_,
  217. // shadowMapSize: 2048,
  218. // lightIntensity: LIGHT_INTENSITY,
  219. // lightNear: 1.0,
  220. // lightFar: csmFar,
  221. // lightDirection: lightDir,
  222. // camera: this.#opaqueCamera_
  223. // });
  224. // this.#csm_.fade = true;
  225. for (let i = 0; i < this.#csm_.lights.length; i++) {
  226. this.#csm_.lights[i].color.copy(LIGHT_COLOUR);
  227. }
  228. this.#csm_.updateFrustums();
  229. this.sun_ = light;
  230. const waterParams = {
  231. type: THREE.HalfFloatType,
  232. magFilter: THREE.NearestFilter,
  233. minFilter: THREE.NearestFilter,
  234. wrapS: THREE.ClampToEdgeWrapping,
  235. wrapT: THREE.ClampToEdgeWrapping,
  236. generateMipmaps: false,
  237. };
  238. this.waterBuffer_ = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, waterParams);
  239. this.waterBuffer_.stencilBuffer = false;
  240. const bufferParams = {
  241. type: THREE.HalfFloatType,
  242. magFilter: THREE.LinearFilter,
  243. minFilter: THREE.LinearFilter,
  244. wrapS: THREE.ClampToEdgeWrapping,
  245. wrapT: THREE.ClampToEdgeWrapping,
  246. generateMipmaps: false,
  247. };
  248. this.readBuffer_ = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, bufferParams);
  249. this.readBuffer_.stencilBuffer = false;
  250. this.readBuffer_.depthTexture = new THREE.DepthTexture();
  251. this.readBuffer_.depthTexture.format = THREE.DepthStencilFormat;
  252. this.readBuffer_.depthTexture.type = THREE.UnsignedInt248Type;
  253. this.writeBuffer_ = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, bufferParams);
  254. this.writeBuffer_.stencilBuffer = false;
  255. this.writeBuffer_.depthTexture = new THREE.DepthTexture();
  256. this.writeBuffer_.depthTexture.format = THREE.DepthStencilFormat;
  257. this.writeBuffer_.depthTexture.type = THREE.UnsignedInt248Type;
  258. this.#opaquePass_ = new RenderPass(this.#opaqueScene_, this.#opaqueCamera_);
  259. this.#waterPass_ = new RenderPass(this.#waterScene_, this.#opaqueCamera_);
  260. this.#transparentPass_ = new RenderPass(this.#transparentScene_, this.#transparentCamera_);
  261. const f = this.#opaqueCamera_.far;
  262. const n = this.#opaqueCamera_.near;
  263. const shader = new shaders.ShaderMaterial('WATER-TEXTURE', {
  264. uniforms: {
  265. colourTexture: { value: null },
  266. depthTexture: { value: null },
  267. nearFar: { value: new THREE.Vector3(f * n, f - n, f) },
  268. }
  269. });
  270. this.#waterTexturePass_ = new ShaderPass(shader);
  271. this.#fxaaPass_ = new ShaderPass(FXAAShader);
  272. this.#acesPass_ = new ShaderPass(ACESFilmicToneMappingShader);
  273. this.#gammaPass_ = new ShaderPass(GammaCorrectionShader2);
  274. this.#copyPass_ = new ShaderPass(Copy2);
  275. const hemiLight = new entity.Entity();
  276. hemiLight.AddComponent(new light_component.LightComponent({
  277. hemi: {
  278. // upColour: 0x7CBFFF,
  279. // downColour: 0xFFE5BC,
  280. upColour: HEMI_UP,
  281. downColour: HEMI_DOWN,
  282. intensity: HEMI_INTENSITY,
  283. }
  284. }));
  285. hemiLight.SetActive(false);
  286. hemiLight.Init(this.Parent);
  287. this.stats_ = new Stats();
  288. this.grassStats_ = new Stats.Panel('Grass MS', '#0f0', '#020');
  289. this.stats_.addPanel(this.grassStats_);
  290. this.stats_.showPanel(0);
  291. document.body.appendChild(this.stats_.dom);
  292. this.onWindowResize_();
  293. }
  294. get Scene() {
  295. return this.#opaqueScene_;
  296. }
  297. get Camera() {
  298. return this.#opaqueCamera_;
  299. }
  300. get WaterTexture() {
  301. return this.waterBuffer_.texture;
  302. }
  303. get WaterDepthTexture() {
  304. return this.waterBuffer_.texture;
  305. }
  306. getMaxAnisotropy() {
  307. return this.#threejs_.capabilities.getMaxAnisotropy();
  308. }
  309. onWindowResize_() {
  310. const w = window.innerWidth;
  311. const h = window.innerHeight
  312. this.#opaqueCamera_.aspect = w / h;
  313. this.#opaqueCamera_.updateProjectionMatrix();
  314. this.#waterCamera_.aspect = this.#opaqueCamera_.aspect;
  315. this.#waterCamera_.updateProjectionMatrix();
  316. this.#transparentCamera_.aspect = this.#opaqueCamera_.aspect;
  317. this.#transparentCamera_.updateProjectionMatrix();
  318. this.#threejs_.setSize(w, h);
  319. // this.composer_.setSize(window.innerWidth, window.innerHeight);
  320. this.waterBuffer_.setSize(w, h);
  321. this.writeBuffer_.setSize(w, h);
  322. this.readBuffer_.setSize(w, h);
  323. // this.csm_.updateFrustums();
  324. this.#waterTexturePass_.setSize(w, h);
  325. this.#fxaaPass_.material.uniforms['resolution'].value.x = 1 / w;
  326. this.#fxaaPass_.material.uniforms['resolution'].value.y = 1 / h;
  327. this.#csm_.updateFrustums();
  328. }
  329. swapBuffers_() {
  330. const tmp = this.writeBuffer_;
  331. this.writeBuffer_ = this.readBuffer_;
  332. this.readBuffer_ = tmp;
  333. }
  334. SetupMaterial(material) {
  335. this.#csm_.setupMaterial(material);
  336. }
  337. AddSceneObject(obj, params) {
  338. params = params || {};
  339. if (params.pass == 'water') {
  340. this.#waterScene_.add(obj);
  341. } else if (params.pass == 'transparent') {
  342. this.#transparentScene_.add(obj);
  343. } else {
  344. this.#opaqueScene_.add(obj);
  345. }
  346. }
  347. Render(timeElapsedS) {
  348. this.#waterCamera_.position.copy(this.#opaqueCamera_.position);
  349. this.#waterCamera_.quaternion.copy(this.#opaqueCamera_.quaternion);
  350. this.#transparentCamera_.position.copy(this.#opaqueCamera_.position);
  351. this.#transparentCamera_.quaternion.copy(this.#opaqueCamera_.quaternion);
  352. this.stats_.begin();
  353. this.#threejs_.autoClear = true;
  354. this.#threejs_.autoClearColor = true;
  355. this.#threejs_.autoClearDepth = true;
  356. this.#threejs_.autoClearStencil = true;
  357. this.#threejs_.setRenderTarget(this.writeBuffer_);
  358. this.#threejs_.clear();
  359. this.#threejs_.setRenderTarget(this.readBuffer_);
  360. this.#threejs_.clear();
  361. this.#threejs_.setRenderTarget(null);
  362. this.#opaquePass_.renderToScreen = false;
  363. this.#opaquePass_.render(this.#threejs_, null, this.writeBuffer_, timeElapsedS, false);
  364. this.writeBuffer_.ACTIVE_HAS_OPAQUE = true;
  365. this.readBuffer_.ACTIVE_HAS_OPAQUE = false;
  366. this.swapBuffers_();
  367. this.#threejs_.autoClear = false;
  368. this.#threejs_.autoClearColor = false;
  369. this.#threejs_.autoClearDepth = false;
  370. this.#threejs_.autoClearStencil = false;
  371. this.swapBuffers_();
  372. this.#waterTexturePass_.clear = false;
  373. this.#waterTexturePass_.renderToScreen = false;
  374. this.#waterTexturePass_.material.uniforms.colourTexture.value = this.writeBuffer_.texture;
  375. this.#waterTexturePass_.material.uniforms.depthTexture.value = this.writeBuffer_.depthTexture;
  376. this.#waterTexturePass_.render(this.#threejs_, this.waterBuffer_, null, timeElapsedS, false);
  377. this.#waterPass_.clear = false;
  378. this.#waterPass_.render(this.#threejs_, this.null, this.writeBuffer_, timeElapsedS, false);
  379. this.#transparentPass_.renderToScreen = false;
  380. this.#transparentPass_.clear = false;
  381. this.#transparentPass_.render(this.#threejs_, null, this.writeBuffer_, timeElapsedS, false);
  382. this.writeBuffer_.ACTIVE_HAS_WATER = true;
  383. this.readBuffer_.ACTIVE_HAS_WATER = false;
  384. this.swapBuffers_();
  385. this.#gammaPass_.clear = false;
  386. this.#gammaPass_.renderToScreen = true;
  387. this.#gammaPass_.render(this.#threejs_, null, this.readBuffer_, timeElapsedS, false);
  388. this.stats_.end();
  389. }
  390. Update(timeElapsed) {
  391. const player = this.FindEntity('player');
  392. if (!player) {
  393. return;
  394. }
  395. const pos = player.Position;
  396. this.#csm_.update();
  397. this.sun_.position.copy(pos);
  398. this.sun_.position.add(new THREE.Vector3(-10, 40, 10));
  399. this.sun_.target.position.copy(pos);
  400. this.sun_.updateMatrixWorld();
  401. this.sun_.target.updateMatrixWorld();
  402. }
  403. }
  404. return {
  405. ThreeJSController: ThreeJSController,
  406. };
  407. })();