diff --git a/src/lib/terrain/voxelmap/i-voxelmap.ts b/src/lib/terrain/voxelmap/i-voxelmap.ts index 8208f1d3..128f315c 100644 --- a/src/lib/terrain/voxelmap/i-voxelmap.ts +++ b/src/lib/terrain/voxelmap/i-voxelmap.ts @@ -14,6 +14,7 @@ type Color = { interface IVoxelMaterial { readonly color: Color; readonly shininess?: number; + readonly emissiveness?: number; } type VoxelsChunkSize = { diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts index f677eaf9..026e8c6f 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts @@ -232,6 +232,7 @@ float computeNoise() { struct VoxelMaterial { vec3 color; float shininess; + vec3 emissive; }; VoxelMaterial getVoxelMaterial(const vec3 modelNormal) { @@ -240,6 +241,7 @@ VoxelMaterial getVoxelMaterial(const vec3 modelNormal) { if (uDisplayMode == ${EVoxelsDisplayMode.NORMALS}u) { voxelMaterial.color = 0.5 + 0.5 * modelNormal; voxelMaterial.shininess = 0.0; + voxelMaterial.emissive = vec3(0); } else { float noise = 0.0; #ifdef ${cstVoxelNoise} @@ -250,11 +252,16 @@ VoxelMaterial getVoxelMaterial(const vec3 modelNormal) { ivec2 texelCoords = ivec2(voxelMaterialId % ${this.texture.image.width}u, voxelMaterialId / ${this.texture.image.width}u); vec4 fetchedTexel = texelFetch(uTexture, texelCoords, 0); voxelMaterial.color = fetchedTexel.rgb + noise; - voxelMaterial.shininess = uShininessStrength * ${VoxelsRenderableFactoryBase.maxShininess.toFixed(1)} * fetchedTexel.a * (1.0 + 10.0 * noise); + + float emissive = step(0.5, fetchedTexel.a) * (2.0 * fetchedTexel.a - 1.0); + voxelMaterial.shininess = 0.0001 + step(fetchedTexel.a, 0.5) * uShininessStrength * ${VoxelsRenderableFactoryBase.maxShininess.toFixed(1)} * 2.0 * fetchedTexel.a * (1.0 + 10.0 * noise); + voxelMaterial.emissive = emissive * voxelMaterial.color; + voxelMaterial.color *= (1.0 - emissive); } if (uDisplayMode == ${EVoxelsDisplayMode.GREY}u) { voxelMaterial.color = vec3(0.75); + voxelMaterial.emissive = vec3(0); } return voxelMaterial; @@ -290,6 +297,9 @@ void main() { '#include ': ` #include material.specularShininess = voxelMaterial.shininess; + `, + '#include ': ` + totalEmissiveRadiance = voxelMaterial.emissive; `, }); }; diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts index f4a2c55e..916f27ad 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts @@ -1,4 +1,4 @@ -import { nextPowerOfTwo } from '../../../../helpers/math'; +import { clamp, nextPowerOfTwo } from '../../../../helpers/math'; import { vec3ToString } from '../../../../helpers/string'; import { type PackedUintFragment } from '../../../../helpers/uint-packing'; import * as THREE from '../../../../libs/three-usage'; @@ -167,8 +167,25 @@ abstract class VoxelsRenderableFactoryBase { textureData[4 * materialId + 1] = 255 * material.color.g; textureData[4 * materialId + 2] = 255 * material.color.b; const shininess = material.shininess ?? 0; - // shininess cannot be 0 or it creates visual artifacts. Clamp it. - textureData[4 * materialId + 3] = Math.max(1, (255 * shininess) / VoxelsRenderableFactoryBase.maxShininess); + const emissiveness = material.emissiveness ?? 0; + + if (shininess < 0) { + throw new Error(`A material cannot have negative shininess.`); + } + if (emissiveness < 0) { + throw new Error(`A material cannot have negative emissiveness.`); + } + if (emissiveness > 0 && shininess > 0) { + throw new Error(`A material cannot both have shininess and emissiveness`); + } + + if (emissiveness > 0) { + // store emissiveness + textureData[4 * materialId + 3] = 128 + clamp(127 * emissiveness, 0, 127); + } else { + // store shininess + textureData[4 * materialId + 3] = clamp((127 * shininess) / VoxelsRenderableFactoryBase.maxShininess, 0, 127); + } }); const texture = new THREE.DataTexture(textureData, textureWidth, textureHeight); texture.needsUpdate = true; diff --git a/src/test/map/color-mapping.ts b/src/test/map/color-mapping.ts index 09dd896d..cb662e60 100644 --- a/src/test/map/color-mapping.ts +++ b/src/test/map/color-mapping.ts @@ -16,7 +16,13 @@ class ColorMapping { for (leveled.g = 0; leveled.g < this.valuesCountPerChannel; leveled.g++) { for (leveled.r = 0; leveled.r < this.valuesCountPerChannel; leveled.r++) { const color = this.buildColorFromLeveled(leveled); - this.materialsList.push({ color, shininess: 200 * Math.random() }); + + const isEmissive = Math.random() > 0.7; + this.materialsList.push({ + color, + shininess: isEmissive ? 0 : 200 * Math.random(), + emissiveness: isEmissive ? 0.5 : 0, + }); } } }