diff --git a/manual/assets/js/src/demos/texture.ts b/manual/assets/js/src/demos/texture.ts index 79bbc0b34..dc542eb63 100644 --- a/manual/assets/js/src/demos/texture.ts +++ b/manual/assets/js/src/demos/texture.ts @@ -14,8 +14,12 @@ import { import { ClearPass, + ColorDodgeBlendFunction, + EffectPass, GeometryPass, - RenderPipeline + RenderPipeline, + TextureEffect, + ToneMappingEffect } from "postprocessing"; import { Pane } from "tweakpane"; @@ -80,10 +84,10 @@ window.addEventListener("load", () => void load().then((assets) => { const controls = new SpatialControls(camera.position, camera.quaternion, renderer.domElement); const settings = controls.settings; settings.rotation.sensitivity = 2.2; - settings.rotation.damping = 0.025; + settings.rotation.damping = 0.05; settings.translation.damping = 0.1; - controls.position.set(0, 1, 2); - controls.lookAt(0, 0, 0); + controls.position.set(0, 1.5, 10); + controls.lookAt(0, 1.35, 0); // Scene, Lights, Objects @@ -96,31 +100,25 @@ window.addEventListener("load", () => void load().then((assets) => { // Post Processing + const effect = new TextureEffect({ texture: assets.get("scratches") }); + effect.blendMode.blendFunction = new ColorDodgeBlendFunction(); + const pipeline = new RenderPipeline(renderer); pipeline.add( new ClearPass(), new GeometryPass(scene, camera, { frameBufferType: HalfFloatType, samples: 4 - }) + }), + new EffectPass(effect, new ToneMappingEffect()) ); - /* - const effect = new TextureEffect({ - texture: assets.get("scratches") - }); - - effect.blendMode.blendFunction = new ColorDodgeBlendFunction(); - pipeline.addPass(new EffectPass(effect, new ToneMappingEffect())); - */ - // Settings const pane = new Pane({ container: container.querySelector(".tp") as HTMLElement }); const fpsGraph = Utils.createFPSGraph(pane); - /* - const texture = effect.texture; + const texture = effect.texture as Texture; const folder = pane.addFolder({ title: "Settings" }); folder.addBinding(texture, "rotation", { min: 0, max: 2 * Math.PI, step: 0.001 }); @@ -135,7 +133,6 @@ window.addEventListener("load", () => void load().then((assets) => { subFolder.addBinding(texture.center, "y", { min: 0, max: 1, step: 0.001 }); Utils.addBlendModeBindings(folder, effect.blendMode); - */ // Resize Handler diff --git a/manual/content/demos/utility/texture.en.md b/manual/content/demos/utility/texture.en.md index b0fef2730..102d9582d 100644 --- a/manual/content/demos/utility/texture.en.md +++ b/manual/content/demos/utility/texture.en.md @@ -2,7 +2,7 @@ layout: single collection: sections title: Texture -draft: true +draft: false menu: demos: parent: utility diff --git a/src/effects/TextureEffect.ts b/src/effects/TextureEffect.ts new file mode 100644 index 000000000..b6a899e5a --- /dev/null +++ b/src/effects/TextureEffect.ts @@ -0,0 +1,141 @@ +import { Texture, Uniform, UnsignedByteType } from "three"; +import { ColorChannel } from "../enums/ColorChannel.js"; +import { Effect } from "./Effect.js"; + +import fragmentShader from "./shaders/texture.frag"; +import vertexShader from "./shaders/texture.vert"; + +/** + * TextureEffect options. + * + * @category Effects + */ + +export interface TextureEffectOptions { + + /** + * A texture. + */ + + texture?: Texture | null; + +} + +/** + * A texture effect. + * + * @category Effects + */ + +export class TextureEffect extends Effect { + + /** + * Constructs a new texture effect. + * + * @param options - The options. + */ + + constructor({ texture = null }: TextureEffectOptions = {}) { + + super("TextureEffect"); + + this.fragmentShader = fragmentShader; + + const defines = this.input.defines; + defines.set("TEXEL", "texel"); + + const uniforms = this.input.uniforms; + uniforms.set("map", new Uniform(null)); + uniforms.set("uvTransform", new Uniform(null)); + + this.texture = texture; + + } + + /** + * The current texture. + */ + + get texture(): Texture | null { + + return this.input.uniforms.get("map")!.value as Texture; + + } + + set texture(value: Texture | null) { + + const prevTexture = this.texture; + const uniforms = this.input.uniforms; + const defines = this.input.defines; + + if(prevTexture !== value) { + + uniforms.get("map")!.value = value; + defines.delete("TEXTURE_PRECISION_HIGH"); + + if(value !== null) { + + if(value.matrixAutoUpdate) { + + defines.set("UV_TRANSFORM", true); + uniforms.get("uvTransform")!.value = value.matrix; + this.vertexShader = vertexShader; + + } else { + + defines.delete("UV_TRANSFORM"); + uniforms.get("uvTransform")!.value = null; + this.vertexShader = null; + + } + + if(value.type !== UnsignedByteType) { + + defines.set("TEXTURE_PRECISION_HIGH", true); + + } + + } + + this.setChanged(); + + } + + } + + /** + * Sets the swizzles that will be applied to the components of a texel before it is written to the output color. + * + * @param r - The swizzle for the `r` component. + * @param g - The swizzle for the `g` component. Defaults to the same value used for `r`. + * @param b - The swizzle for the `b` component. Defaults to the same value used for `r`. + * @param a - The swizzle for the `a` component. Defaults to the same value used for `r`. + */ + + setTextureSwizzleRGBA(r: ColorChannel, g = r, b = r, a = r) { + + const rgba = "rgba"; + let swizzle = ""; + + if(r !== ColorChannel.RED || g !== ColorChannel.GREEN || b !== ColorChannel.BLUE || a !== ColorChannel.ALPHA) { + + swizzle = [".", rgba[r], rgba[g], rgba[b], rgba[a]].join(""); + + } + + this.input.defines.set("TEXEL", "texel" + swizzle); + this.setChanged(); + + } + + override render(): void { + + if(this.texture !== null && this.texture.matrixAutoUpdate) { + + this.texture.updateMatrix(); + + } + + } + +} diff --git a/src/effects/index.ts b/src/effects/index.ts index f25a85e3e..b70fa5cf4 100644 --- a/src/effects/index.ts +++ b/src/effects/index.ts @@ -5,5 +5,6 @@ export * from "./ColorDepthEffect.js"; export * from "./Effect.js"; export * from "./FXAAEffect.js"; export * from "./ScanlineEffect.js"; +export * from "./TextureEffect.js"; export * from "./ToneMappingEffect.js"; export * from "./VignetteEffect.js"; diff --git a/src/effects/shaders/texture.frag b/src/effects/shaders/texture.frag new file mode 100644 index 000000000..b78aca515 --- /dev/null +++ b/src/effects/shaders/texture.frag @@ -0,0 +1,31 @@ +#ifdef TEXTURE_PRECISION_HIGH + + uniform mediump sampler2D map; + +#else + + uniform lowp sampler2D map; + +#endif + +#ifdef UV_TRANSFORM + + in vec2 vUv2; + +#endif + +vec4 mainImage(const in vec4 inputColor, const in vec2 uv, const in GData gData) { + + #ifdef UV_TRANSFORM + + vec4 texel = texture(map, vUv2); + + #else + + vec4 texel = texture(map, uv); + + #endif + + return TEXEL; + +} diff --git a/src/effects/shaders/texture.vert b/src/effects/shaders/texture.vert new file mode 100644 index 000000000..2c9c4476f --- /dev/null +++ b/src/effects/shaders/texture.vert @@ -0,0 +1,9 @@ +uniform mat3 uvTransform; + +out vec2 vUv2; + +void mainSupport(const in vec2 uv) { + + vUv2 = (uvTransform * vec3(uv, 1.0)).xy; + +} diff --git a/test/effects/TextureEffect.js b/test/effects/TextureEffect.js new file mode 100644 index 000000000..a57e2f2ae --- /dev/null +++ b/test/effects/TextureEffect.js @@ -0,0 +1,11 @@ +import test from "ava"; +import { TextureEffect } from "postprocessing"; + +test("can be created and destroyed", t => { + + const object = new TextureEffect(); + object.dispose(); + + t.pass(); + +});