-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feeat: base code for texture customization
- Loading branch information
Showing
8 changed files
with
278 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import * as THREE from "../libs/three-usage"; | ||
|
||
import { createFullscreenQuad } from "./fullscreen-quad"; | ||
|
||
type Parameters = { | ||
readonly width: number; | ||
readonly height: number; | ||
readonly baseTexture: THREE.Texture; | ||
readonly additionalTextures: ReadonlyMap<string, THREE.Texture>; | ||
}; | ||
|
||
type TextureLayer = { | ||
readonly texture: THREE.Texture; | ||
readonly color: THREE.Color; | ||
}; | ||
|
||
class CustomizableTexture { | ||
public readonly texture: THREE.Texture; | ||
|
||
private readonly baseTexture: THREE.Texture; | ||
private readonly layers: ReadonlyMap<string, TextureLayer>; | ||
|
||
private readonly renderTarget: THREE.WebGLRenderTarget; | ||
private readonly fakeCamera = new THREE.PerspectiveCamera(); | ||
private readonly fullscreenQuad = createFullscreenQuad("aPosition"); | ||
private readonly applyLayer: { | ||
readonly shader: THREE.RawShaderMaterial; | ||
readonly uniforms: { | ||
readonly layer: THREE.IUniform<THREE.Texture | null>; | ||
readonly color: THREE.IUniform<THREE.Color>; | ||
}; | ||
}; | ||
|
||
public constructor(params: Parameters) { | ||
this.baseTexture = params.baseTexture; | ||
|
||
const layers = new Map<string, TextureLayer>(); | ||
for (const [name, texture] of params.additionalTextures.entries()) { | ||
layers.set(name, { texture, color: new THREE.Color(0xFFFFFF) }); | ||
} | ||
this.layers = layers; | ||
|
||
this.renderTarget = new THREE.WebGLRenderTarget(params.width, params.height, { | ||
depthBuffer: false, | ||
}); | ||
const texture = this.renderTarget.textures[0]; | ||
if (!texture) { | ||
throw new Error(`Cannot get texture from rendertarget`); | ||
} | ||
this.texture = texture; | ||
|
||
const uniforms = { | ||
layer: { value: null }, | ||
color: { value: new THREE.Color(0xFFFFFF) }, | ||
}; | ||
|
||
const shader = new THREE.RawShaderMaterial({ | ||
glslVersion: "300 es", | ||
uniforms: { | ||
uLayerTexture: uniforms.layer, | ||
uLayerColor: uniforms.color, | ||
}, | ||
vertexShader: ` | ||
in vec2 aPosition; | ||
out vec2 vUv; | ||
void main() { | ||
vUv = aPosition; | ||
gl_Position = vec4(2.0 * aPosition - 1.0, 0, 1); | ||
}`, | ||
fragmentShader: ` | ||
precision mediump float; | ||
uniform sampler2D uLayerTexture; | ||
uniform vec3 uLayerColor; | ||
in vec2 vUv; | ||
(layout location = 0) out vec4 fragColor; | ||
void main() { | ||
vec4 sample = texture(uLayerTexture, vUv); | ||
// sample.rgb *= uLayerColor; | ||
fragColor = sample + vec4(0, 1, 0, 1); | ||
} | ||
`, | ||
}); | ||
|
||
this.applyLayer = { shader, uniforms }; | ||
} | ||
|
||
public setLayerColor(layerName: string, color: THREE.Color): void { | ||
const layer = this.layers.get(layerName); | ||
if (!layer) { | ||
const layerNames = Array.from(this.layers.keys()); | ||
throw new Error(`Unknown layer name "${layerName}". Layer names are: ${layerNames.join("; ")}.`); | ||
} | ||
|
||
if (layer.color.equals(color)) { | ||
return; // nothing to do | ||
} | ||
|
||
layer.color.set(color); | ||
} | ||
|
||
public update(renderer: THREE.WebGLRenderer): void { | ||
const previousState = { | ||
renderTarget: renderer.getRenderTarget(), | ||
clearColor: renderer.getClearColor(new THREE.Color()), | ||
clearAlpha: renderer.getClearAlpha(), | ||
autoClear: renderer.autoClear, | ||
autoClearColor: renderer.autoClearColor, | ||
}; | ||
|
||
renderer.setRenderTarget(this.renderTarget); | ||
renderer.setClearColor(0xFF0000, 0); | ||
renderer.autoClear = false; | ||
renderer.autoClearColor = false; | ||
renderer.clear(true); | ||
|
||
this.applyLayer.uniforms.layer.value = this.baseTexture; | ||
this.applyLayer.uniforms.color.value = new THREE.Color(0xFFFFFF); | ||
renderer.render(this.fullscreenQuad, this.fakeCamera); | ||
|
||
// for (const layer of this.layers.values()) { | ||
// this.applyLayer.uniforms.layer.value = layer.texture; | ||
// this.applyLayer.uniforms.color.value = layer.color; | ||
// renderer.render(this.fullscreenQuad, this.fakeCamera); | ||
// } | ||
|
||
renderer.setRenderTarget(previousState.renderTarget); | ||
renderer.setClearColor(previousState.clearColor); | ||
renderer.setClearAlpha(previousState.clearAlpha); | ||
renderer.autoClear = previousState.autoClear; | ||
renderer.autoClearColor = previousState.autoClearColor; | ||
} | ||
} | ||
|
||
export { | ||
CustomizableTexture | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import * as THREE from "../libs/three-usage"; | ||
|
||
function createFullscreenQuad(attributeName: string): THREE.Mesh { | ||
const fullscreenQuadGeometry = new THREE.BufferGeometry(); | ||
fullscreenQuadGeometry.setAttribute(attributeName, new THREE.Float32BufferAttribute([0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1], 2)); | ||
fullscreenQuadGeometry.setDrawRange(0, 6); | ||
const fullscreenQuad = new THREE.Mesh(fullscreenQuadGeometry); | ||
fullscreenQuad.frustumCulled = false; | ||
return fullscreenQuad; | ||
} | ||
|
||
export { | ||
createFullscreenQuad, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { GUI } from 'lil-gui'; | ||
import * as THREE from 'three-usage-test'; | ||
|
||
import { CustomizableTexture } from '../lib'; | ||
|
||
import { TestBase } from './test-base'; | ||
|
||
class TestTextureCustomization extends TestBase { | ||
private readonly gui: GUI; | ||
|
||
private readonly parameters = { | ||
color1: 0xff0000, | ||
color2: 0x00ff00, | ||
color3: 0x0000ff, | ||
}; | ||
|
||
private customizableTexture: CustomizableTexture | null = null; | ||
|
||
public constructor() { | ||
super(); | ||
|
||
this.camera.position.set(2, 2, 4); | ||
this.cameraControl.target.set(0, this.camera.position.y - 1.5, 0); | ||
|
||
const gridHelper = new THREE.GridHelper(1000, 100); | ||
gridHelper.position.setY(-0.01); | ||
this.scene.add(gridHelper); | ||
|
||
const ambientLight = new THREE.AmbientLight(0xCCCCCC); | ||
this.scene.add(ambientLight); | ||
|
||
const enforceColors = () => { this.enforceColors(); }; | ||
this.gui = new GUI(); | ||
this.gui.addColor(this.parameters, "color1").onChange(enforceColors); | ||
this.gui.addColor(this.parameters, "color2").onChange(enforceColors); | ||
this.gui.addColor(this.parameters, "color3").onChange(enforceColors); | ||
enforceColors(); | ||
|
||
const gltfLoader = new THREE.GLTFLoader(); | ||
const dracoLoader = new THREE.DRACOLoader(); | ||
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/'); | ||
dracoLoader.setDecoderConfig({ type: 'js' }); | ||
gltfLoader.setDRACOLoader(dracoLoader); | ||
|
||
gltfLoader.load("/resources/character/primemachin.glb", gltf => { | ||
this.scene.add(gltf.scene); | ||
|
||
gltf.scene.traverse(child => { | ||
if ((child as any).isMesh) { | ||
const childMesh = child as THREE.Mesh; | ||
const childMaterial = childMesh.material as THREE.MeshPhongMaterial; | ||
|
||
setTimeout(() => { | ||
const childTexture = childMaterial.map; | ||
if (!childTexture) { | ||
throw new Error("No base texture"); | ||
} | ||
this.customizableTexture = new CustomizableTexture({ | ||
width: 100, | ||
height: 100, | ||
baseTexture: childTexture, | ||
additionalTextures: new Map<string, THREE.Texture>(), | ||
}); | ||
this.customizableTexture.update(this.renderer); | ||
childMaterial.map = this.customizableTexture.texture; | ||
}, 2000); | ||
} | ||
}) | ||
}); | ||
} | ||
|
||
protected override update(): void { | ||
} | ||
|
||
private enforceColors(): void { | ||
if (this.customizableTexture) { | ||
// this.customizableTexture.setLayerColor("color1", new THREE.Color(this.parameters.color1)); | ||
// this.customizableTexture.setLayerColor("color2", new THREE.Color(this.parameters.color2)); | ||
// this.customizableTexture.setLayerColor("color3", new THREE.Color(this.parameters.color3)); | ||
this.customizableTexture.update(this.renderer); | ||
} | ||
} | ||
} | ||
|
||
export { TestTextureCustomization }; |
Binary file not shown.