@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
335 lines (314 loc) • 12.7 kB
text/typescript
import {BaseNodeType} from './../../_Base';
import {NodeContext} from './../../../poly/NodeContext';
import {TypeAssert} from './../../../poly/Assert';
import {RayMarchingUniforms, RAYMARCHING_UNIFORMS} from './../../gl/gl/raymarching/uniforms';
import {Constructor} from '../../../../types/GlobalTypes';
import {NodeParamsConfig, ParamConfig} from '../../utils/params/ParamsConfig';
import {PrimitiveMatNode} from '../_Base';
import {Material, Texture} from 'three';
import {ShaderMaterialWithCustomMaterials} from '../../../../core/geometry/Material';
import {isBooleanTrue} from '../../../../core/Type';
import {CustomMaterialRayMarchingParamConfig} from './customMaterials/CustomMaterialRayMarching';
import {ThreeToGl} from '../../../../core/ThreeToGl';
enum RayMarchingDebugMode {
STEPS_COUNT = 'Steps Count',
DEPTH = 'Depth',
}
const RAYMARCHING_DEBUG_MODES: RayMarchingDebugMode[] = [RayMarchingDebugMode.STEPS_COUNT, RayMarchingDebugMode.DEPTH];
const DEBUG_STEPS_COUNT = RAYMARCHING_DEBUG_MODES.indexOf(RayMarchingDebugMode.STEPS_COUNT);
// const DEBUG_DEPTH = RAYMARCHING_DEBUG_MODES.indexOf(RayMarchingDebugMode.DEPTH);
interface EnvMapParams {
envMapCubeUVHeight: number;
}
interface EnvMapData {
texelWidth: number;
texelHeight: number;
maxMip: number;
}
interface EnvMapDataWithRotation extends EnvMapData {
tEnvMapRotate: boolean;
}
// from three.js WebGLProgram.js
function generateCubeUVSize(parameters: EnvMapParams): EnvMapData | null {
const imageHeight = parameters.envMapCubeUVHeight;
if (imageHeight === null) return null;
const maxMip = Math.log2(imageHeight) - 2;
const texelHeight = 1.0 / imageHeight;
const texelWidth = 1.0 / (3 * Math.max(Math.pow(2, maxMip), 7 * 16));
return {texelWidth, texelHeight, maxMip};
}
function setDefines(shaderMaterial: ShaderMaterialWithCustomMaterials, props?: EnvMapDataWithRotation | null) {
shaderMaterial.defines['ENVMAP_TYPE_CUBE_UV'] = props ? 1 : 0;
shaderMaterial.defines['CUBEUV_TEXEL_WIDTH'] = props ? props.texelWidth : ThreeToGl.float(0.1);
shaderMaterial.defines['CUBEUV_TEXEL_HEIGHT'] = props ? props.texelHeight : ThreeToGl.float(0.1);
shaderMaterial.defines['CUBEUV_MAX_MIP'] = props ? ThreeToGl.float(props.maxMip) : ThreeToGl.float(1);
shaderMaterial.defines['ROTATE_ENV_MAP_Y'] = props ? props.tEnvMapRotate : 0;
}
export function RayMarchingMainParamConfig<TBase extends Constructor>(Base: TBase) {
return class Mixin extends Base {
/** @param maximum number of steps the raymarcher will run */
maxSteps = ParamConfig.INTEGER(RAYMARCHING_UNIFORMS.MAX_STEPS.value, {
range: [1, 128],
rangeLocked: [true, false],
});
/** @param maximum distance the raymarcher will step through */
maxDist = ParamConfig.FLOAT(RAYMARCHING_UNIFORMS.MAX_DIST.value, {
range: [1, 100],
rangeLocked: [true, false],
});
/** @param when the ray reaches this distance from a surface it will stop marching. You can lower this value to increase the precision of the raymarcher */
surfDist = ParamConfig.FLOAT(RAYMARCHING_UNIFORMS.SURF_DIST.value, {
range: [0, 0.1],
rangeLocked: [true, false],
step: 0.0000001,
});
/** @param precision for normals computation */
normalsBias = ParamConfig.FLOAT(RAYMARCHING_UNIFORMS.NORMALS_BIAS.value, {
range: [0, 0.1],
rangeLocked: [true, false],
step: 0.0000001,
});
/** @param precision for shadows computation */
shadowBias = ParamConfig.FLOAT(RAYMARCHING_UNIFORMS.SHADOW_BIAS.value, {
range: [-0.1, 0.1],
rangeLocked: [false, false],
step: 0.0000001,
});
};
}
export function RayMarchingEnvMapParamConfig<TBase extends Constructor>(Base: TBase) {
return class Mixin extends Base {
/** @param toggle if you want to use an environment map */
useEnvMap = ParamConfig.BOOLEAN(0, {
separatorBefore: true,
// ...BooleanParamOptions(TextureEnvMapController),
});
/** @param specify the environment map COP node */
envMap = ParamConfig.NODE_PATH('', {
visibleIf: {useEnvMap: 1},
nodeSelection: {context: NodeContext.COP},
});
/** @param environment intensity */
envMapIntensity = ParamConfig.FLOAT(1, {
visibleIf: {useEnvMap: 1},
cook: false,
callback: (node: BaseNodeType) =>
RayMarchingController.updateUniformEnvMapIntensity(node as RayMarchingMatNode),
});
/** @param environment roughness */
envMapRoughness = ParamConfig.FLOAT(1, {
visibleIf: {useEnvMap: 1},
cook: false,
callback: (node: BaseNodeType) =>
RayMarchingController.updateUniformEnvMapRoughness(node as RayMarchingMatNode),
});
/** @param allow env map rotation */
tEnvMapRotate = ParamConfig.BOOLEAN(0, {
visibleIf: {useEnvMap: 1},
});
/** @param env map rotation */
envMapRotation = ParamConfig.FLOAT(0, {
range: [-Math.PI, Math.PI],
rangeLocked: [false, false],
step: 0.0001,
visibleIf: {useEnvMap: 1, tEnvMapRotate: 1},
cook: false,
callback: (node: BaseNodeType) =>
RayMarchingController.updateUniformEnvMapRotate(node as RayMarchingMatNode),
});
};
}
export function RayMarchingDebugParamConfig<TBase extends Constructor>(Base: TBase) {
return class Mixin extends Base {
/** @param debug mode */
debug = ParamConfig.BOOLEAN(0, {
separatorBefore: true,
});
/** @param outputs color showing the number of steps required to solve the raymarching */
debugMode = ParamConfig.INTEGER(DEBUG_STEPS_COUNT, {
menu: {entries: RAYMARCHING_DEBUG_MODES.map((name, value) => ({name, value}))},
visibleIf: {debug: true},
});
/** @param min steps count */
debugMinSteps = ParamConfig.INTEGER(RAYMARCHING_UNIFORMS.debugMinSteps.value, {
range: [0, 128],
rangeLocked: [true, false],
step: 1,
// visibleIf: {debug: true, debugMode: DEBUG_STEPS_COUNT},
});
/** @param max steps count */
debugMaxSteps = ParamConfig.INTEGER(RAYMARCHING_UNIFORMS.debugMaxSteps.value, {
range: [0, 128],
rangeLocked: [true, false],
step: 1,
// visibleIf: {debug: true, debugMode: DEBUG_STEPS_COUNT},
});
/** @param min depth */
debugMinDepth = ParamConfig.FLOAT(RAYMARCHING_UNIFORMS.debugMinDepth.value, {
range: [0, 128],
rangeLocked: [true, false],
step: 1,
// visibleIf: {debug: true, debugMode: DEBUG_DEPTH},
});
/** @param max depth */
debugMaxDepth = ParamConfig.FLOAT(RAYMARCHING_UNIFORMS.debugMaxDepth.value, {
range: [0, 128],
rangeLocked: [true, false],
step: 1,
// visibleIf: {debug: true, debugMode: DEBUG_DEPTH},
});
};
}
class RayMarchingMaterial extends Material {}
class RayMarchingParamsConfig extends CustomMaterialRayMarchingParamConfig(
RayMarchingDebugParamConfig(RayMarchingEnvMapParamConfig(RayMarchingMainParamConfig(NodeParamsConfig)))
) {}
abstract class RayMarchingMatNode extends PrimitiveMatNode<RayMarchingMaterial, RayMarchingParamsConfig> {}
// const worldPos = new Vector3();
export class RayMarchingController {
constructor(protected node: RayMarchingMatNode) {}
async updateUniformsFromParams(shaderMaterial: ShaderMaterialWithCustomMaterials) {
const uniforms = shaderMaterial.uniforms as unknown as RayMarchingUniforms | undefined;
if (!uniforms) {
return;
}
const pv = this.node.pv;
uniforms.MAX_STEPS.value = pv.maxSteps;
uniforms.MAX_DIST.value = pv.maxDist;
uniforms.SURF_DIST.value = pv.surfDist;
uniforms.NORMALS_BIAS.value = pv.normalsBias;
uniforms.SHADOW_BIAS.value = pv.shadowBias;
uniforms.shadowDepthMin.value = pv.shadowDepthMin;
uniforms.shadowDepthMax.value = pv.shadowDepthMax;
uniforms.shadowDistanceMin.value = pv.shadowDistanceMin;
uniforms.shadowDistanceMax.value = pv.shadowDistanceMax;
this._updateUniforms(shaderMaterial);
this._updateDebug(shaderMaterial, uniforms);
await this._updateEnvMap(shaderMaterial, uniforms);
}
private _updateDebug(shaderMaterial: ShaderMaterialWithCustomMaterials, uniforms: RayMarchingUniforms) {
const pv = this.node.pv;
if (isBooleanTrue(pv.debug)) {
function updateDebugMode(uniforms: RayMarchingUniforms) {
const debugMode = RAYMARCHING_DEBUG_MODES[pv.debugMode];
switch (debugMode) {
case RayMarchingDebugMode.STEPS_COUNT: {
uniforms.debugMinSteps.value = pv.debugMinSteps;
uniforms.debugMaxSteps.value = pv.debugMaxSteps;
shaderMaterial.defines['DEBUG_STEPS_COUNT'] = 1;
delete shaderMaterial.defines['DEBUG_DEPTH'];
shaderMaterial.needsUpdate = true;
return;
}
case RayMarchingDebugMode.DEPTH: {
uniforms.debugMinDepth.value = pv.debugMinDepth;
uniforms.debugMaxDepth.value = pv.debugMaxDepth;
shaderMaterial.defines['DEBUG_DEPTH'] = 1;
delete shaderMaterial.defines['DEBUG_STEPS_COUNT'];
shaderMaterial.needsUpdate = true;
return;
}
}
TypeAssert.unreachable(debugMode);
}
updateDebugMode(uniforms);
} else {
if (shaderMaterial.defines['DEBUG_STEPS_COUNT'] != null) {
delete shaderMaterial.defines['DEBUG_STEPS_COUNT'];
shaderMaterial.needsUpdate = true;
}
if (shaderMaterial.defines['DEBUG_DEPTH'] != null) {
delete shaderMaterial.defines['DEBUG_DEPTH'];
shaderMaterial.needsUpdate = true;
}
}
}
private async _updateEnvMap(shaderMaterial: ShaderMaterialWithCustomMaterials, uniforms: RayMarchingUniforms) {
const pv = this.node.pv;
setDefines(shaderMaterial, null);
const currentDefine = shaderMaterial.defines['ENVMAP_TYPE_CUBE_UV'];
const _fetchTexture = async () => {
const pathParam = this.node.p.envMap;
if (pathParam.isDirty()) {
await pathParam.compute();
}
const textureNode = pathParam.value.nodeWithContext(NodeContext.COP);
if (textureNode) {
const container = await textureNode.compute();
const texture = container.texture();
return texture;
}
};
const _applyTexture = (texture: Texture) => {
(uniforms as any)['envMap'].value = texture;
const props = generateCubeUVSize({envMapCubeUVHeight: texture.image.height});
setDefines(shaderMaterial, props ? {...props, tEnvMapRotate: pv.tEnvMapRotate} : null);
};
const _removeTexture = () => {
(uniforms as any)['envMap'].value = null;
setDefines(shaderMaterial, null);
};
const _updateNeedsUpdateIfRequired = () => {
if (currentDefine != shaderMaterial.defines['ENVMAP_TYPE_CUBE_UV']) {
shaderMaterial.needsUpdate = true;
}
};
if (isBooleanTrue(pv.useEnvMap)) {
const texture = await _fetchTexture();
if (texture) {
_applyTexture(texture);
} else {
_removeTexture();
}
} else {
_removeTexture();
}
_updateNeedsUpdateIfRequired();
}
/**
*
* uniforms
*
*/
private _updateUniforms(shaderMaterial: ShaderMaterialWithCustomMaterials) {
RayMarchingController._updateUniforms(this.node, shaderMaterial);
}
private static _updateUniforms(node: RayMarchingMatNode, shaderMaterial: ShaderMaterialWithCustomMaterials) {
this._updateUniformEnvMapIntensity(node, shaderMaterial);
this._updateUniformEnvMapRoughness(node, shaderMaterial);
this._updateUniformEnvMapRotate(node, shaderMaterial);
}
static async updateUniformEnvMapIntensity(node: RayMarchingMatNode) {
this._updateUniformEnvMapIntensity(node, (await node.material()) as ShaderMaterialWithCustomMaterials);
}
static _updateUniformEnvMapIntensity(node: RayMarchingMatNode, shaderMaterial: ShaderMaterialWithCustomMaterials) {
// const shaderMaterial = node.materialSync() as ShaderMaterialWithCustomMaterials;
const uniforms = shaderMaterial.uniforms as unknown as RayMarchingUniforms | undefined;
if (!uniforms) {
return;
}
(uniforms as any)['envMapIntensity'].value = node.pv.envMapIntensity;
}
static async updateUniformEnvMapRoughness(node: RayMarchingMatNode) {
this._updateUniformEnvMapRoughness(node, (await node.material()) as ShaderMaterialWithCustomMaterials);
}
static _updateUniformEnvMapRoughness(node: RayMarchingMatNode, shaderMaterial: ShaderMaterialWithCustomMaterials) {
// const shaderMaterial = node.materialSync() as ShaderMaterialWithCustomMaterials;
const uniforms = shaderMaterial.uniforms as unknown as RayMarchingUniforms | undefined;
if (!uniforms) {
return;
}
(uniforms as any)['roughness'].value = node.pv.envMapRoughness;
}
static async updateUniformEnvMapRotate(node: RayMarchingMatNode) {
this._updateUniformEnvMapRotate(node, (await node.material()) as ShaderMaterialWithCustomMaterials);
}
static _updateUniformEnvMapRotate(node: RayMarchingMatNode, shaderMaterial: ShaderMaterialWithCustomMaterials) {
// const shaderMaterial = node.materialSync() as ShaderMaterialWithCustomMaterials;
const uniforms = shaderMaterial.uniforms as unknown as RayMarchingUniforms | undefined;
if (!uniforms) {
return;
}
(uniforms as any)['envMapRotationY'].value = node.pv.envMapRotation;
}
}