Skip to content

Commit

Permalink
feeat: base code for texture customization
Browse files Browse the repository at this point in the history
  • Loading branch information
piellardj authored and Sceat committed Oct 2, 2024
1 parent 89ecc65 commit 4e8695a
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 22 deletions.
7 changes: 2 additions & 5 deletions src/lib/effects/billboard/gpu/gpu-textures-state.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createFullscreenQuad } from "../../../helpers/fullscreen-quad";
import * as THREE from '../../../libs/three-usage';

type UniformType = 'sampler2D' | 'float' | 'vec2' | 'vec3' | 'vec4';
Expand Down Expand Up @@ -38,11 +39,7 @@ class GpuTexturesState {

this.textureNames = params.textureNames;

const fullscreenQuadGeometry = new THREE.BufferGeometry();
fullscreenQuadGeometry.setAttribute('aPosition', new THREE.Float32BufferAttribute([0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1], 2));
fullscreenQuadGeometry.setDrawRange(0, 6);
this.fullscreenQuad = new THREE.Mesh(fullscreenQuadGeometry);
this.fullscreenQuad.frustumCulled = false;
this.fullscreenQuad = createFullscreenQuad("aPosition");

const vertexShader = `
in vec2 aPosition;
Expand Down
143 changes: 143 additions & 0 deletions src/lib/helpers/customizable-texture.ts
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
};

14 changes: 14 additions & 0 deletions src/lib/helpers/fullscreen-quad.ts
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,
};
8 changes: 5 additions & 3 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ export {
type ILocalMapData,
type IVoxelMap,
type IVoxelMaterial,
type VoxelsChunkSize,
type VoxelsChunkSize
} from './terrain/voxelmap/i-voxelmap';
export {
VoxelmapViewerAutonomous,
type VoxelmapViewerAutonomousOptions,
type VoxelmapViewerAutonomousOptions
} from './terrain/voxelmap/viewer/autonomous/voxelmap-viewer-autonomous';
export {
EComputationMethod,
VoxelmapViewer,
type ComputationOptions,
type ComputationStatus,
type VoxelmapViewerOptions,
type VoxelsChunkData,
type VoxelsChunkData
} from './terrain/voxelmap/viewer/simple/voxelmap-viewer';
export { VoxelmapVisibilityComputer } from './terrain/voxelmap/voxelmap-visibility-computer';
export { type CheckerboardType } from './terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base';
Expand All @@ -35,3 +35,5 @@ export { type Spritesheet } from './effects/spritesheet';
export { Rain } from './effects/weather/rain';
export { Snow } from './effects/weather/snow';
export { GpuInstancedBillboard } from './effects/weather/weather-particles-base';

export { CustomizableTexture } from "./helpers/customizable-texture";
2 changes: 2 additions & 0 deletions src/test/libs/three-usage-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ export {
} from 'three';
export { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
export { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
export { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
export { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
export * from '../../lib/libs/three-usage';
export { Stats };
41 changes: 27 additions & 14 deletions src/test/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,40 @@ import { VoxelMap } from './map/voxel-map';
import { type TestBase } from './test-base';
import { TestTerrain } from './test-terrain';
import { TestTerrainAutonomous } from './test-terrain-autonomous';
import { TestTextureCustomization } from './test-texture-customization';
import { TestWeather } from './test-weather';

setVerbosity(ELogLevel.WARN);

const mapScaleXZ = 800;
const mapScaleY = 200;
const mapSeed = 'fixed_seed';
const includeTreesInLod = false;
function createVoxelMap(): VoxelMap {
const mapScaleXZ = 800;
const mapScaleY = 200;
const mapSeed = 'fixed_seed';
const includeTreesInLod = false;

return new VoxelMap(mapScaleXZ, mapScaleY, mapSeed, includeTreesInLod);
}

enum ETest {
TERRAIN,
TERRAIN_OLD,
WEATHER,
TEXTURE_CUSTOMIZATION,
}

const voxelMap = new VoxelMap(mapScaleXZ, mapScaleY, mapSeed, includeTreesInLod);
const test = ETest.TEXTURE_CUSTOMIZATION as ETest;

let testScene: TestBase;
const testTerrain = false;
if (testTerrain) {
const testNewTerrain = true;
if (testNewTerrain) {
testScene = new TestTerrain(voxelMap);
} else {
testScene = new TestTerrainAutonomous(voxelMap);
}
} else {
if (test === ETest.TERRAIN) {
testScene = new TestTerrain(createVoxelMap());
} else if (test === ETest.TERRAIN_OLD) {
testScene = new TestTerrainAutonomous(createVoxelMap());
} else if (test === ETest.WEATHER) {
testScene = new TestWeather();
} else if (test === ETest.TEXTURE_CUSTOMIZATION) {
testScene = new TestTextureCustomization();
} else {
throw new Error(`Unknown test "${test}".`);
}

testScene.start();
85 changes: 85 additions & 0 deletions src/test/test-texture-customization.ts
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 added test/resources/character/primemachin.glb
Binary file not shown.

0 comments on commit 4e8695a

Please sign in to comment.