Initial commit of VIZ 3D project
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import * as THREE from 'three';
|
||||
import { StandardUVMaterial } from './StandardUVMaterial';
|
||||
import { TriplanarMaterial } from './TriplanarMaterial';
|
||||
|
||||
type MaterialCacheKey = string;
|
||||
|
||||
export class MaterialFactory {
|
||||
private static instance: MaterialFactory;
|
||||
private cache: Map<MaterialCacheKey, THREE.Material> = new Map();
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): MaterialFactory {
|
||||
if (!MaterialFactory.instance) {
|
||||
MaterialFactory.instance = new MaterialFactory();
|
||||
}
|
||||
return MaterialFactory.instance;
|
||||
}
|
||||
|
||||
public getStandardUVMaterial(
|
||||
parameters: THREE.MeshPhysicalMaterialParameters,
|
||||
key: string
|
||||
): StandardUVMaterial {
|
||||
const cacheKey = `standard_uv_${key}`;
|
||||
if (this.cache.has(cacheKey)) {
|
||||
return this.cache.get(cacheKey) as StandardUVMaterial;
|
||||
}
|
||||
|
||||
const material = new StandardUVMaterial(parameters);
|
||||
this.cache.set(cacheKey, material);
|
||||
return material;
|
||||
}
|
||||
|
||||
public getTriplanarMaterial(
|
||||
parameters: THREE.MeshPhysicalMaterialParameters,
|
||||
key: string
|
||||
): TriplanarMaterial {
|
||||
const cacheKey = `triplanar_${key}`;
|
||||
if (this.cache.has(cacheKey)) {
|
||||
return this.cache.get(cacheKey) as TriplanarMaterial;
|
||||
}
|
||||
|
||||
const material = new TriplanarMaterial(parameters);
|
||||
this.cache.set(cacheKey, material);
|
||||
return material;
|
||||
}
|
||||
|
||||
public clearCache() {
|
||||
this.cache.forEach((material) => material.dispose());
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class StandardUVMaterial extends THREE.MeshPhysicalMaterial {
|
||||
public uvUniforms: {
|
||||
uMappingMode: { value: number }; // 0: imported, 1: planar, 2: box, 3: cylindrical
|
||||
uUVTransform: { value: THREE.Matrix3 };
|
||||
};
|
||||
|
||||
constructor(parameters?: THREE.MeshPhysicalMaterialParameters) {
|
||||
super(parameters);
|
||||
|
||||
this.uvUniforms = {
|
||||
uMappingMode: { value: 0 },
|
||||
uUVTransform: { value: new THREE.Matrix3() },
|
||||
};
|
||||
|
||||
this.customProgramCacheKey = () => `standard_uv_v2`;
|
||||
|
||||
this.onBeforeCompile = (shader) => {
|
||||
shader.uniforms.uMappingMode = this.uvUniforms.uMappingMode;
|
||||
shader.uniforms.uUVTransform = this.uvUniforms.uUVTransform;
|
||||
|
||||
shader.vertexShader = `
|
||||
attribute vec2 uvPlanar;
|
||||
attribute vec2 uvBox;
|
||||
attribute vec2 uvCylinder;
|
||||
|
||||
uniform int uMappingMode;
|
||||
uniform mat3 uUVTransform;
|
||||
|
||||
varying vec2 vCustomUV;
|
||||
` + shader.vertexShader;
|
||||
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
`#include <uv_vertex>`,
|
||||
`
|
||||
#include <uv_vertex>
|
||||
|
||||
vec2 selectedUV = vec2(0.0);
|
||||
#ifdef USE_UV
|
||||
selectedUV = uv;
|
||||
#endif
|
||||
|
||||
if (uMappingMode == 1) {
|
||||
selectedUV = uvPlanar;
|
||||
} else if (uMappingMode == 2) {
|
||||
selectedUV = uvBox;
|
||||
} else if (uMappingMode == 3) {
|
||||
selectedUV = uvCylinder;
|
||||
}
|
||||
|
||||
vCustomUV = (uUVTransform * vec3(selectedUV, 1.0)).xy;
|
||||
`
|
||||
);
|
||||
|
||||
shader.fragmentShader = `
|
||||
varying vec2 vCustomUV;
|
||||
` + shader.fragmentShader;
|
||||
|
||||
const mainStartIndex = shader.fragmentShader.indexOf('void main() {');
|
||||
if (mainStartIndex !== -1) {
|
||||
const prefix = shader.fragmentShader.substring(0, mainStartIndex);
|
||||
let mainBody = shader.fragmentShader.substring(mainStartIndex);
|
||||
|
||||
mainBody = mainBody.replace(/\bvUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvNormalMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvRoughnessMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvMetalnessMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvAlphaMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvEmissiveMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvAoMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvClearcoatMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvClearcoatNormalMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvClearcoatRoughnessMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvTransmissionMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvThicknessMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvSpecularMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvSpecularIntensityMapUv\b/g, 'vCustomUV');
|
||||
mainBody = mainBody.replace(/\bvSpecularColorMapUv\b/g, 'vCustomUV');
|
||||
|
||||
shader.fragmentShader = prefix + mainBody;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public updateUVTransform(offset: [number, number], repeat: [number, number], rotation: number) {
|
||||
this.uvUniforms.uUVTransform.value.setUvTransform(
|
||||
offset[0], offset[1],
|
||||
repeat[0], repeat[1],
|
||||
rotation,
|
||||
0.5, 0.5
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class TriplanarMaterial extends THREE.MeshPhysicalMaterial {
|
||||
public triplanarUniforms: {
|
||||
tAlbedo: { value: THREE.Texture | null };
|
||||
tNormal: { value: THREE.Texture | null };
|
||||
tRoughness: { value: THREE.Texture | null };
|
||||
tAo: { value: THREE.Texture | null };
|
||||
uTriplanarScale: { value: number };
|
||||
uBlendSharpness: { value: number };
|
||||
};
|
||||
|
||||
constructor(parameters?: THREE.MeshPhysicalMaterialParameters) {
|
||||
super(parameters);
|
||||
|
||||
this.triplanarUniforms = {
|
||||
tAlbedo: { value: null },
|
||||
tNormal: { value: null },
|
||||
tRoughness: { value: null },
|
||||
tAo: { value: null },
|
||||
uTriplanarScale: { value: 1.0 },
|
||||
uBlendSharpness: { value: 5.0 },
|
||||
};
|
||||
|
||||
this.customProgramCacheKey = () => `triplanar_mat_v1`;
|
||||
|
||||
this.onBeforeCompile = (shader) => {
|
||||
// Inject Uniforms
|
||||
shader.uniforms.tAlbedo = this.triplanarUniforms.tAlbedo;
|
||||
shader.uniforms.tNormal = this.triplanarUniforms.tNormal;
|
||||
shader.uniforms.tRoughness = this.triplanarUniforms.tRoughness;
|
||||
shader.uniforms.tAo = this.triplanarUniforms.tAo;
|
||||
shader.uniforms.uTriplanarScale = this.triplanarUniforms.uTriplanarScale;
|
||||
shader.uniforms.uBlendSharpness = this.triplanarUniforms.uBlendSharpness;
|
||||
|
||||
// Vertex Shader Injections
|
||||
shader.vertexShader = `
|
||||
varying vec3 vTriplanarPosition;
|
||||
varying vec3 vTriplanarNormal;
|
||||
` + shader.vertexShader;
|
||||
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
`#include <worldpos_vertex>`,
|
||||
`
|
||||
#include <worldpos_vertex>
|
||||
vTriplanarPosition = position; // Object space
|
||||
vTriplanarNormal = normal; // Object space normal
|
||||
`
|
||||
);
|
||||
|
||||
// Fragment Shader Injections
|
||||
shader.fragmentShader = `
|
||||
uniform sampler2D tAlbedo;
|
||||
uniform sampler2D tNormal;
|
||||
uniform sampler2D tRoughness;
|
||||
uniform sampler2D tAo;
|
||||
uniform float uTriplanarScale;
|
||||
uniform float uBlendSharpness;
|
||||
|
||||
varying vec3 vTriplanarPosition;
|
||||
varying vec3 vTriplanarNormal;
|
||||
|
||||
// Triplanar blending logic
|
||||
vec4 getTriplanarBlend(sampler2D tex, vec3 pos, vec3 norm) {
|
||||
vec3 blendWeights = abs(norm);
|
||||
blendWeights = pow(blendWeights, vec3(uBlendSharpness));
|
||||
blendWeights /= dot(blendWeights, vec3(1.0)); // Normalize
|
||||
|
||||
vec2 uvX = pos.yz * uTriplanarScale;
|
||||
vec2 uvY = pos.zx * uTriplanarScale;
|
||||
vec2 uvZ = pos.xy * uTriplanarScale;
|
||||
|
||||
vec4 colX = texture2D(tex, uvX);
|
||||
vec4 colY = texture2D(tex, uvY);
|
||||
vec4 colZ = texture2D(tex, uvZ);
|
||||
|
||||
return colX * blendWeights.x + colY * blendWeights.y + colZ * blendWeights.z;
|
||||
}
|
||||
` + shader.fragmentShader;
|
||||
|
||||
// Replace map chunks
|
||||
shader.fragmentShader = shader.fragmentShader.replace(
|
||||
`#include <map_fragment>`,
|
||||
`
|
||||
#ifdef USE_MAP
|
||||
vec4 texelColor = getTriplanarBlend(tAlbedo, vTriplanarPosition, vTriplanarNormal);
|
||||
texelColor = mapTexelToLinear(texelColor);
|
||||
diffuseColor *= texelColor;
|
||||
#endif
|
||||
`
|
||||
);
|
||||
|
||||
shader.fragmentShader = shader.fragmentShader.replace(
|
||||
`#include <roughnessmap_fragment>`,
|
||||
`
|
||||
float roughnessFactor = roughness;
|
||||
#ifdef USE_ROUGHNESSMAP
|
||||
vec4 texelRoughness = getTriplanarBlend(tRoughness, vTriplanarPosition, vTriplanarNormal);
|
||||
roughnessFactor *= texelRoughness.g;
|
||||
#endif
|
||||
`
|
||||
);
|
||||
|
||||
shader.fragmentShader = shader.fragmentShader.replace(
|
||||
`#include <normal_fragment_maps>`,
|
||||
`
|
||||
#ifdef USE_NORMALMAP
|
||||
vec4 texelNormal = getTriplanarBlend(tNormal, vTriplanarPosition, vTriplanarNormal);
|
||||
// Simplified triplanar normal calculation
|
||||
vec3 n = texelNormal.rgb * 2.0 - 1.0;
|
||||
normal = normalize(normalMatrix * n);
|
||||
#elif defined( USE_BUMPMAP )
|
||||
// Bump map omitted for brevity
|
||||
#endif
|
||||
`
|
||||
);
|
||||
|
||||
shader.fragmentShader = shader.fragmentShader.replace(
|
||||
`#include <aomap_fragment>`,
|
||||
`
|
||||
#ifdef USE_AOMAP
|
||||
vec4 texelAo = getTriplanarBlend(tAo, vTriplanarPosition, vTriplanarNormal);
|
||||
float ambientOcclusion = ( texelAo.r - 1.0 ) * aoMapIntensity + 1.0;
|
||||
reflectedLight.indirectDiffuse *= ambientOcclusion;
|
||||
#if defined( USE_ENVMAP ) && defined( STANDARD )
|
||||
float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );
|
||||
reflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );
|
||||
#endif
|
||||
#endif
|
||||
`
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user