| @@ -114,6 +114,7 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| - [CustomBumpMapPlugin](#custombumpmapplugin) - Custom Bump Map material extension for PhysicalMaterial | |||
| - [ClearcoatTintPlugin](#clearcoattintplugin) - Clearcoat Tint material extension for PhysicalMaterial | |||
| - [FragmentClippingExtensionPlugin](#fragmentclippingextensionplugin) - Fragment/SDF Clipping material extension for PhysicalMaterial | |||
| - [ParallaxMappingPlugin](#parallaxmappingplugin) - Relief Parallax Bump Mapping extension for PhysicalMaterial | |||
| - [HDRiGroundPlugin](#hdrigroundplugin) - Add support for ground projected hdri/skybox to the webgl background shader. | |||
| - [VirtualCamerasPlugin](#virtualcamerasplugin) - Add support for rendering virtual cameras before the main one every frame. | |||
| - [EditorViewWidgetPlugin](#editorviewwidgetplugin) - Adds an interactive ViewHelper/AxisHelper that syncs with the main camera. | |||
| @@ -2923,6 +2924,41 @@ material.userData._clearcoatTint.clipEnabled = false | |||
| material.setDirty() | |||
| ``` | |||
| ## ParallaxMappingPlugin | |||
| [//]: # (todo: image) | |||
| [Example](https://threepipe.org/examples/#parallax-mapping-plugin/) — | |||
| [Source Code](./src/plugins/material/ParallaxMappingPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/ParallaxMappingPlugin.html) | |||
| `ParallaxMappingPlugin` adds a material extension to PhysicalMaterial to add support for [parallax relief mapping](https://en.wikipedia.org/wiki/Relief_mapping_(computer_graphics)). The idea is to walk along a ray that has entered the bumpmap's volume, finding the intersection point of the ray with the bumpmap. [Steep parallax mapping](https://en.wikipedia.org/wiki/Parallax_mapping) and [parallax occlusion mapping](https://en.wikipedia.org/wiki/Parallax_occlusion_mapping) are other common names for these techniques. | |||
| To use the plugin, add the plugin to the viewer and use the `bumpMap` in `PhysicalMaterial` normally. The max height is determined by the `bumpScale` in the material. This is assumed to be in world scale. | |||
| ```typescript | |||
| import {ThreeViewer, ParallaxMappingPlugin} from 'threepipe' | |||
| const viewer = new ThreeViewer({...}) | |||
| const parallaxMapping = viewer.addPluginSync(ParallaxMappingPlugin) | |||
| // load or create an object | |||
| // set the bump map | |||
| object.material.bumpMap = await viewer.load<ITexture>(bumps[0]) || null | |||
| // set the bump scale | |||
| object.material.bumpScale = 0.1 | |||
| // setDirty to notify the viewer to update. | |||
| object.material.setDirty() | |||
| ``` | |||
| ### References and related links: | |||
| - WebGL implementation by Rabbid76 - [github.com/Rabbid76/graphics-snippets](https://github.com/Rabbid76/graphics-snippets/blob/master/html/technique/parallax_005_parallax_relief_mapping_derivative_tbn.html) | |||
| - Lesson on Parallax Occlusion Mapping in GLSL - [http://sunandblackcat.com/tipFullView.php?topicid=28](https://web.archive.org/web/20190128023901/http://sunandblackcat.com/tipFullView.php?topicid=28) | |||
| - Learn OpenGL - https://learnopengl.com/Advanced-Lighting/Parallax-Mapping | |||
| ## HDRiGroundPlugin | |||
| [//]: # (todo: image) | |||
| @@ -0,0 +1,36 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Parallax Bump Mapping (Relief)</title> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,94 @@ | |||
| import { | |||
| _testFinish, | |||
| BoxGeometry, | |||
| ITexture, | |||
| Mesh, | |||
| ParallaxMappingPlugin, | |||
| PhysicalMaterial, | |||
| SSAAPlugin, | |||
| ThreeViewer, | |||
| } from 'threepipe' | |||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| plugins: [new SSAAPlugin(4)], | |||
| dropzone: { | |||
| addOptions: { | |||
| disposeSceneObjects: true, | |||
| }, | |||
| }, | |||
| }) | |||
| const parallaxMapping = viewer.addPluginSync(ParallaxMappingPlugin) | |||
| console.log(parallaxMapping) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| ui.setupPluginUi(ParallaxMappingPlugin, {expanded: true}) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| const cube = new Mesh( | |||
| new BoxGeometry(1, 1, 1), | |||
| new PhysicalMaterial({ | |||
| // roughness: 0, | |||
| // metalness: 1, | |||
| })) | |||
| const maps = [ | |||
| 'https://threejs.org/examples/textures/sprite0.png', | |||
| 'https://threejs.org/examples/textures/uv_grid_opengl.jpg', | |||
| 'https://threejs.org/examples/models/svg/style-css-inside-defs.svg', | |||
| 'https://cdn.jsdelivr.net/gh/Rabbid76/graphics-snippets/resource/texture/lookuptable.png', | |||
| 'https://cdn.jsdelivr.net/gh/Rabbid76/graphics-snippets/resource/texture/perlin3_cp.png', | |||
| 'https://cdn.jsdelivr.net/gh/Rabbid76/graphics-snippets/resource/texture/perlin4_cp.png', | |||
| 'https://cdn.jsdelivr.net/gh/Rabbid76/graphics-snippets/resource/texture/ObjectSheet.png', | |||
| 'https://cdn.jsdelivr.net/gh/Rabbid76/graphics-snippets/resource/texture/512x512_Texel_Density_Texture_1.png', | |||
| 'https://cdn.jsdelivr.net/gh/Rabbid76/graphics-snippets/resource/texture/toy_box_normal.png', | |||
| 'https://cdn.jsdelivr.net/gh/Rabbid76/graphics-snippets/resource/texture/example_1_texture.png', | |||
| ] | |||
| const bumps = [ | |||
| maps[0], | |||
| maps[1], | |||
| maps[2], | |||
| maps[3], | |||
| maps[4], | |||
| maps[5], | |||
| maps[6], | |||
| maps[7], | |||
| 'https://cdn.jsdelivr.net/gh/Rabbid76/graphics-snippets/resource/texture/toy_box_disp.png', | |||
| 'https://cdn.jsdelivr.net/gh/Rabbid76/graphics-snippets/resource/texture/example_1_heightmap.png', | |||
| ] | |||
| cube.material.bumpMap = await viewer.load<ITexture>(bumps[0]) || null | |||
| cube.material.map = await viewer.load<ITexture>(maps[0]) || null | |||
| cube.material.bumpScale = 0.1 | |||
| viewer.scene.addObject(cube) | |||
| ui.appendChild({ | |||
| type: 'dropdown', | |||
| value: maps[0], | |||
| label: 'Bump Texture', | |||
| children: ['none', ...maps].map((url: string) => ({ | |||
| label: url.split('/').pop(), | |||
| value: url, | |||
| })), | |||
| onChange: async(ev) => { | |||
| console.log(ev.value) | |||
| const url = ev.value | |||
| const tex = await viewer.load<ITexture>(url) || null | |||
| cube.material.map = tex | |||
| const bumpUrl = bumps[maps.indexOf(url)] | |||
| const bumpTex = await viewer.load<ITexture>(bumpUrl) || null | |||
| cube.material.bumpMap = bumpTex | |||
| cube.material.setDirty() | |||
| }, | |||
| }) | |||
| ui.appendChild(cube.material.uiConfig) | |||
| } | |||
| init().finally(_testFinish) | |||
| @@ -61,6 +61,7 @@ export {CameraViewPlugin, type CameraViewPluginOptions} from './animation/Camera | |||
| export {ClearcoatTintPlugin} from './material/ClearcoatTintPlugin' | |||
| export {NoiseBumpMaterialPlugin} from './material/NoiseBumpMaterialPlugin' | |||
| export {CustomBumpMapPlugin} from './material/CustomBumpMapPlugin' | |||
| export {ParallaxMappingPlugin} from './material/ParallaxMappingPlugin' | |||
| export {FragmentClippingExtensionPlugin, FragmentClippingMode} from './material/FragmentClippingExtensionPlugin' | |||
| // rendering | |||
| @@ -0,0 +1,115 @@ | |||
| import {MaterialExtension, updateMaterialDefines} from '../../materials' | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {onChange, serialize} from 'ts-browser-helpers' | |||
| import {uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js' | |||
| import {shaderReplaceString} from '../../utils' | |||
| import {ShaderChunk} from 'three' | |||
| import {PhysicalMaterial} from '../../core' | |||
| import ParallaxMappingPluginReliefShader from './shaders/ParallaxMappingPlugin.relief.glsl' | |||
| /** | |||
| * Parallax Mapping Plugin | |||
| * Adds a material extension to PhysicalMaterial which parallax mapping to bump map in the material. | |||
| * This is a port of Relief Parallax Mapping from [Rabbid76/graphics-snippets](https://github.com/Rabbid76/graphics-snippets/blob/master/html/technique/parallax_005_parallax_relief_mapping_derivative_tbn.html) | |||
| * @category Plugins | |||
| */ | |||
| @uiFolderContainer('Parallax Mapping') | |||
| export class ParallaxMappingPlugin extends AViewerPluginSync<''> { | |||
| public static PluginType = 'ReliefParallaxMapping' | |||
| @onChange(ParallaxMappingPlugin.prototype._updateExtension) | |||
| @serialize() | |||
| @uiToggle('Enabled') enabled = true | |||
| @uiSlider('Step count', [1, 32], 1) | |||
| @onChange(ParallaxMappingPlugin.prototype._updateExtension) | |||
| @serialize() stepCount = 12 | |||
| @uiSlider('Binary search steps', [1, 8], 1) | |||
| @onChange(ParallaxMappingPlugin.prototype._updateExtension) | |||
| @serialize() binaryStepCount = 3 | |||
| @onChange(ParallaxMappingPlugin.prototype._updateExtension) | |||
| @uiToggle('Debug Normals') debugNormals = false | |||
| @onChange(ParallaxMappingPlugin.prototype._updateExtension) | |||
| @uiToggle('Debug Hit Height') debugHitHeight = false | |||
| private _defines: any = { | |||
| ['PARALLAX_NORMAL_MAP_QUALITY']: 0, | |||
| } | |||
| constructor(enabled = true) { | |||
| super() | |||
| this.enabled = enabled | |||
| this._updateExtension = this._updateExtension.bind(this) | |||
| } | |||
| private _updateExtension() { | |||
| this._bumpMapExtension?.setDirty?.() | |||
| this._viewer?.setDirty() | |||
| } | |||
| private _bumpMapExtension: MaterialExtension = { | |||
| shaderExtender: (shader, material, _renderer) => { | |||
| if (!material.bumpMap || this.isDisabled()) return | |||
| shader.fragmentShader = shader.fragmentShader.replace('#include <normal_fragment_begin>', '') | |||
| shader.fragmentShader = shader.fragmentShader.replace('#include <normal_fragment_maps>', '') | |||
| shader.fragmentShader = shader.fragmentShader.replace('#include <map_fragment>', | |||
| '#include <normal_fragment_begin>\n#include <normal_fragment_maps>\n#include <map_fragment>') | |||
| for (const s of ['map_fragment', 'alphamap_fragment', 'roughnessmap_fragment', 'metalnessmap_fragment', 'emissivemap_fragment', 'transmission_fragment']) { | |||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, `#include <${s}>`, | |||
| (ShaderChunk as any)[s].replace(/\bv\w+Uv\b/g, 'parallaxUv.xy', {replaceAll: true}) | |||
| ) | |||
| } | |||
| if (this.debugNormals || this.debugHitHeight) | |||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, | |||
| // .replace('texture2D( map, parallaxUv.xy )', 'texture2D( map, parallaxUv.xy )') | |||
| 'texture2D( map, parallaxUv.xy )', | |||
| this.debugNormals ? 'vec4(normal, 1.); normal = geometryNormal' : 'vec4(parallaxUv.z,0., 0., 1.)') | |||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#include <normal_fragment_maps>', | |||
| shaderReplaceString( | |||
| shaderReplaceString( | |||
| ShaderChunk.normal_fragment_maps, | |||
| '#elif defined( USE_NORMALMAP_TANGENTSPACE )', '#elif defined( USE_NORMALMAP_TANGENTSPACE ) && !defined( USE_BUMPMAP )'), | |||
| 'normal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );', | |||
| // 'diffuseColor.rgb = vec3(0, dHdxy_fwd());' | |||
| // 'diffuseColor.rgb = CalculateNormal(vUv).rgb;' | |||
| 'vec3 parallaxUv = reliefParallaxPerturbNormal(faceDirection, normal);' | |||
| ) | |||
| ) | |||
| }, | |||
| parsFragmentSnippet: ()=> { | |||
| return this.isDisabled() ? '' : (ParallaxMappingPluginReliefShader + '\n') | |||
| .replaceAll('PARALLAX_MAP_STEPS', this.stepCount.toString()) // replacing here to unroll for loop. | |||
| .replaceAll('PARALLAX_MAP_B_STEPS', this.binaryStepCount.toString()) | |||
| }, | |||
| isCompatible: (material: PhysicalMaterial) => { | |||
| return material.isPhysicalMaterial | |||
| }, | |||
| computeCacheKey: material => { | |||
| return '' + !this.isDisabled() + material.bumpMap?.uuid + this.debugNormals + this.debugHitHeight + this.stepCount.toString() + this.binaryStepCount.toString() | |||
| }, | |||
| onObjectRender: (_object, material, _renderer) => { | |||
| if (this.isDisabled()) return // todo: use extraDefines | |||
| updateMaterialDefines({ | |||
| ...this._defines, | |||
| }, material) | |||
| }, | |||
| } as MaterialExtension | |||
| onAdded(viewer: ThreeViewer) { | |||
| viewer.materialManager.registerMaterialExtension(this._bumpMapExtension) | |||
| return super.onAdded(viewer) | |||
| } | |||
| onRemove(viewer: ThreeViewer) { | |||
| viewer.materialManager.unregisterMaterialExtension(this._bumpMapExtension) | |||
| return super.onRemove(viewer) | |||
| } | |||
| } | |||
| @@ -0,0 +1,157 @@ | |||
| #ifdef USE_BUMPMAP | |||
| mat3 mat3_inverse( mat3 A ) | |||
| { | |||
| mat3 M_t = mat3( | |||
| vec3( A[0][0], A[1][0], A[2][0] ), | |||
| vec3( A[0][1], A[1][1], A[2][1] ), | |||
| vec3( A[0][2], A[1][2], A[2][2] ) ); | |||
| float det = dot( cross( M_t[0], M_t[1] ), M_t[2] ); | |||
| mat3 adjugate = mat3( cross( M_t[1], M_t[2] ), | |||
| cross( M_t[2], M_t[0] ), | |||
| cross( M_t[0], M_t[1] ) ); | |||
| return adjugate / det; | |||
| } | |||
| float CalculateHeight( in vec2 texCoords ) | |||
| { | |||
| float height = texture2D( bumpMap, texCoords ).x; | |||
| return clamp( height, 0.0, 1.0 ); | |||
| } | |||
| const vec2 bumpMapSize = vec2(512, 512); | |||
| // Return normal in tangent space from normal map if available or bump map | |||
| vec3 CalculateNormal( in vec2 texCoords ) | |||
| { | |||
| #if defined( TANGENTSPACE_NORMALMAP ) && 0 //todo: fix. not working properly. | |||
| vec3 mapN = texture2D( normalMap, texCoords ).xyz; | |||
| mapN.xy *= normalScale; | |||
| return normalize( mapN ); | |||
| #else | |||
| vec2 texOffs = 1.0 / bumpMapSize; | |||
| #if PARALLAX_NORMAL_MAP_QUALITY > 0 | |||
| float hx[9]; | |||
| hx[0] = texture2D( bumpMap, texCoords.st + texOffs * vec2(-1.0, -1.0) ).r; | |||
| hx[1] = texture2D( bumpMap, texCoords.st + texOffs * vec2( 0.0, -1.0) ).r; | |||
| hx[2] = texture2D( bumpMap, texCoords.st + texOffs * vec2( 1.0, -1.0) ).r; | |||
| hx[3] = texture2D( bumpMap, texCoords.st + texOffs * vec2(-1.0, 0.0) ).r; | |||
| hx[4] = texture2D( bumpMap, texCoords.st ).r; | |||
| hx[5] = texture2D( bumpMap, texCoords.st + texOffs * vec2( 1.0, 0.0) ).r; | |||
| hx[6] = texture2D( bumpMap, texCoords.st + texOffs * vec2(-1.0, 1.0) ).r; | |||
| hx[7] = texture2D( bumpMap, texCoords.st + texOffs * vec2( 0.0, 1.0) ).r; | |||
| hx[8] = texture2D( bumpMap, texCoords.st + texOffs * vec2( 1.0, 1.0) ).r; | |||
| vec2 deltaH = vec2(hx[0]-hx[2] + 2.0*(hx[3]-hx[5]) + hx[6]-hx[8], hx[0]-hx[6] + 2.0*(hx[1]-hx[7]) + hx[2]-hx[8]); | |||
| #else | |||
| float h_xa = texture2D( bumpMap, texCoords.st + texOffs * vec2(-1.0, 0.0) ).r; | |||
| float h_xb = texture2D( bumpMap, texCoords.st + texOffs * vec2( 1.0, 0.0) ).r; | |||
| float h_ya = texture2D( bumpMap, texCoords.st + texOffs * vec2( 0.0, -1.0) ).r; | |||
| float h_yb = texture2D( bumpMap, texCoords.st + texOffs * vec2( 0.0, 1.0) ).r; | |||
| vec2 deltaH = vec2(h_xa-h_xb, h_ya-h_yb); | |||
| #endif | |||
| return normalize( vec3( deltaH / texOffs, 1.0 ) ); | |||
| #endif | |||
| } | |||
| //https://github.com/Rabbid76/graphics-snippets/blob/master/html/technique/parallax_005_parallax_relief_mapping_derivative_tbn.html | |||
| //https://web.archive.org/web/20190128023901/http://sunandblackcat.com/tipFullView.php?topicid=28 | |||
| vec3 ReliefParallax( in float frontFace, in vec3 texDir3D, in vec2 texCoord ) | |||
| { | |||
| float surf_sign = frontFace; | |||
| float back_face = step(0.0, -surf_sign); | |||
| vec2 texStep = surf_sign * texDir3D.xy / abs(texDir3D.z); // (z is negative) the direction vector points downwards in tangent-space | |||
| vec2 texC = texCoord.st + surf_sign * texStep + back_face * texStep.xy; | |||
| float mapHeight = 1.0; | |||
| float bumpHeightStep = 1.0 / float(PARALLAX_MAP_STEPS); | |||
| float bestBumpHeight = mapHeight+bumpHeightStep; | |||
| #pragma unroll_loop_start | |||
| for ( int i = 0 ; i < PARALLAX_MAP_STEPS ; i ++ ) { | |||
| if ( mapHeight < bestBumpHeight ) | |||
| { | |||
| bestBumpHeight -= bumpHeightStep; | |||
| mapHeight = back_face + surf_sign * CalculateHeight(texC.xy - bestBumpHeight * texStep.xy); | |||
| } | |||
| } | |||
| #pragma unroll_loop_end | |||
| bestBumpHeight += bumpHeightStep; | |||
| #pragma unroll_loop_start | |||
| for ( int i = 0; i < PARALLAX_MAP_B_STEPS ; i ++ ) { | |||
| bumpHeightStep *= 0.5; | |||
| bestBumpHeight -= bumpHeightStep; | |||
| mapHeight = back_face + surf_sign * CalculateHeight( texC.xy - bestBumpHeight * texStep.xy ); | |||
| bestBumpHeight += ( bestBumpHeight < mapHeight ) ? bumpHeightStep : 0.0; | |||
| } | |||
| #pragma unroll_loop_end | |||
| bestBumpHeight -= bumpHeightStep * clamp( ( bestBumpHeight - mapHeight ) / bumpHeightStep, 0.0, 1.0 ); | |||
| mapHeight = bestBumpHeight; | |||
| texC -= mapHeight * texStep; | |||
| return vec3( texC.xy, mapHeight ); | |||
| } | |||
| vec3 reliefParallaxPerturbNormal(in float faceDirection, inout vec3 normal){ | |||
| if(abs(bumpScale) < 0.001) return vec3(vBumpMapUv, 0.); | |||
| // #ifdef DOUBLE_SIDED | |||
| // | |||
| // normal = normal * faceDirection; | |||
| // | |||
| // #endif | |||
| float parallaxHeight; | |||
| vec2 texCoords = vBumpMapUv; | |||
| float face_sign = sign(dot(normal, vViewPosition)); | |||
| // Followup: Normal Mapping Without Precomputed Tangents [http://www.thetenthplanet.de/archives/1180] | |||
| vec3 N = normalize(normal); | |||
| vec3 dp1 = dFdx(-vViewPosition); | |||
| vec3 dp2 = dFdy(-vViewPosition); | |||
| vec2 duv1 = dFdx(vBumpMapUv); | |||
| vec2 duv2 = dFdy(vBumpMapUv); | |||
| vec3 dp2perp = cross(dp2, N); | |||
| vec3 dp1perp = cross(N, dp1); | |||
| vec3 T = dp2perp * duv1.x + dp1perp * duv2.x; | |||
| vec3 B = dp2perp * duv1.y + dp1perp * duv2.y; | |||
| float invmax = inversesqrt(max(dot(T, T), dot(B, B))); | |||
| mat3 tbnMat = mat3(T * invmax, B * invmax, N * bumpScale); | |||
| vec3 tangentPos = normalize(mat3_inverse(tbnMat) * -vViewPosition); | |||
| // vec2 parallaxUv = parallaxMapping(tangentPos, vBumpMapUv, parallaxHeight); | |||
| vec3 parallaxUv = ReliefParallax(face_sign, tangentPos, vBumpMapUv); | |||
| tbnMat[2] = face_sign * N / bumpScale; | |||
| normal = normalize(tbnMat * CalculateNormal(parallaxUv.xy).xyz); | |||
| //todo test this. | |||
| #ifdef FLIP_SIDED | |||
| normal = - normal; | |||
| #endif | |||
| // #ifdef DOUBLE_SIDED | |||
| // | |||
| // normal = normal * faceDirection; | |||
| // | |||
| // #endif | |||
| // normal = geometryNormal; | |||
| // todo: modify geometry.position (vViewPosition) for point, spot and area lights | |||
| return parallaxUv; | |||
| } | |||
| #endif // USE_BUMPMAP | |||
| @@ -43,6 +43,11 @@ export class SSAAPlugin extends AViewerPluginSync<SSAAPluginEventTypes> { | |||
| dependencies = [ProgressivePlugin] | |||
| constructor(rendersPerFrame = 1) { | |||
| super() | |||
| this.rendersPerFrame = rendersPerFrame | |||
| } | |||
| onAdded(viewer: ThreeViewer) { | |||
| super.onAdded(viewer) | |||
| viewer.addEventListener('preRender', this._preRender) | |||