Add reflection support to water material
This commit is contained in:
@@ -1,6 +1,20 @@
|
||||
import { DoubleSide, Euler, MeshBasicMaterial, Quaternion, ShaderMaterial, Vector2, Vector3, Vector4 } from "three";
|
||||
import {
|
||||
DoubleSide,
|
||||
Euler,
|
||||
Matrix4,
|
||||
MeshBasicMaterial,
|
||||
Quaternion,
|
||||
ShaderMaterial,
|
||||
Texture,
|
||||
UniformsLib,
|
||||
UniformsUtils,
|
||||
Vector2,
|
||||
Vector3,
|
||||
Vector4
|
||||
} from "three";
|
||||
|
||||
import type { Vec3 } from "../core/vector";
|
||||
import { MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT } from "../document/brushes";
|
||||
|
||||
export interface WaterContactBounds {
|
||||
min: Vec3;
|
||||
@@ -46,6 +60,15 @@ export interface WaterMaterialResult {
|
||||
contactPatchesUniform: { value: Vector4[] } | null;
|
||||
contactPatchAxesUniform: { value: Vector2[] } | null;
|
||||
contactPatchShapesUniform: { value: number[] } | null;
|
||||
reflectionTextureUniform: { value: Texture | null } | null;
|
||||
reflectionMatrixUniform: { value: Matrix4 } | null;
|
||||
reflectionEnabledUniform: { value: number } | null;
|
||||
}
|
||||
|
||||
interface WaterMaterialReflectionOptions {
|
||||
texture: Texture | null;
|
||||
enabled: boolean;
|
||||
strength?: number;
|
||||
}
|
||||
|
||||
interface WaterMaterialOptions {
|
||||
@@ -62,6 +85,7 @@ interface WaterMaterialOptions {
|
||||
z: number;
|
||||
};
|
||||
contactPatches?: WaterContactPatch[];
|
||||
reflection?: WaterMaterialReflectionOptions;
|
||||
}
|
||||
|
||||
interface OrientedWaterVolume {
|
||||
@@ -75,7 +99,7 @@ interface TriangleMeshSegmentSample {
|
||||
normal: Vector3;
|
||||
}
|
||||
|
||||
const MAX_WATER_CONTACT_PATCHES = 6;
|
||||
const MAX_WATER_CONTACT_PATCHES = MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT;
|
||||
const WATER_CONTACT_EPSILON = 1e-4;
|
||||
|
||||
function createBoundsCorners(bounds: WaterContactBounds) {
|
||||
@@ -759,7 +783,7 @@ function appendTriangleMeshContactPatches(
|
||||
patches.push(...mergeTriangleMeshContactPatches(rawPatches, bandMinimumThickness, source.mergeProfile));
|
||||
}
|
||||
|
||||
export function collectWaterContactPatches(volume: OrientedWaterVolume, contactBounds: WaterContactSource[]): WaterContactPatch[] {
|
||||
export function collectWaterContactPatches(volume: OrientedWaterVolume, contactBounds: WaterContactSource[], patchLimit = MAX_WATER_CONTACT_PATCHES): WaterContactPatch[] {
|
||||
const inverseRotation = createInverseVolumeRotation(volume.rotationDegrees);
|
||||
const halfX = Math.max(volume.size.x * 0.5, WATER_CONTACT_EPSILON);
|
||||
const halfY = Math.max(volume.size.y * 0.5, WATER_CONTACT_EPSILON);
|
||||
@@ -851,9 +875,11 @@ export function collectWaterContactPatches(volume: OrientedWaterVolume, contactB
|
||||
}
|
||||
}
|
||||
|
||||
const clampedPatchLimit = Math.max(1, Math.min(patchLimit, MAX_WATER_CONTACT_PATCHES));
|
||||
|
||||
return patches
|
||||
.sort((left, right) => right.halfWidth * right.halfDepth - left.halfWidth * left.halfDepth)
|
||||
.slice(0, MAX_WATER_CONTACT_PATCHES);
|
||||
.slice(0, clampedPatchLimit);
|
||||
}
|
||||
|
||||
export function createWaterContactPatchUniformValue(contactPatches?: WaterContactPatch[]): Vector4[] {
|
||||
@@ -890,7 +916,10 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
animationUniform: null,
|
||||
contactPatchesUniform: null,
|
||||
contactPatchAxesUniform: null,
|
||||
contactPatchShapesUniform: null
|
||||
contactPatchShapesUniform: null,
|
||||
reflectionTextureUniform: null,
|
||||
reflectionMatrixUniform: null,
|
||||
reflectionEnabledUniform: null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -905,7 +934,10 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
animationUniform: null,
|
||||
contactPatchesUniform: null,
|
||||
contactPatchAxesUniform: null,
|
||||
contactPatchShapesUniform: null
|
||||
contactPatchShapesUniform: null,
|
||||
reflectionTextureUniform: null,
|
||||
reflectionMatrixUniform: null,
|
||||
reflectionEnabledUniform: null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -914,8 +946,12 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
const contactPatchesUniform = { value: createWaterContactPatchUniformValue(options.contactPatches) };
|
||||
const contactPatchAxesUniform = { value: createWaterContactPatchAxisUniformValue(options.contactPatches) };
|
||||
const contactPatchShapesUniform = { value: createWaterContactPatchShapeUniformValue(options.contactPatches) };
|
||||
const reflectionTextureUniform = { value: options.reflection?.texture ?? null };
|
||||
const reflectionMatrixUniform = { value: new Matrix4() };
|
||||
const reflectionEnabledUniform = {
|
||||
value: options.reflection?.enabled === true ? Math.max(0, Math.min(1, options.reflection?.strength ?? 0.36)) : 0
|
||||
};
|
||||
const waveStrength = Math.max(0, options.waveStrength);
|
||||
const waveAmplitude = 0.016 + Math.min(0.12, waveStrength * 0.06);
|
||||
const clampedOpacity = Math.max(0.14, Math.min(1, options.opacity));
|
||||
const topFaceFlag = options.isTopFace ? 1 : 0;
|
||||
const hex = options.colorHex.replace("#", "");
|
||||
@@ -926,19 +962,21 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
const vertexShader = /* glsl */ `
|
||||
uniform float time;
|
||||
uniform float waveStrength;
|
||||
uniform float waveAmplitude;
|
||||
uniform float isTopFace;
|
||||
uniform mat4 reflectionMatrix;
|
||||
|
||||
varying vec2 vLocalSurfaceUv;
|
||||
varying vec3 vWaveNormal;
|
||||
varying vec3 vWorldPos;
|
||||
varying vec3 vViewDir;
|
||||
varying vec4 vReflectionCoord;
|
||||
#include <fog_pars_vertex>
|
||||
|
||||
void main() {
|
||||
vec3 transformedPosition = position;
|
||||
vLocalSurfaceUv = position.xz;
|
||||
vWaveNormal = vec3(0.0, 1.0, 0.0);
|
||||
vReflectionCoord = vec4(0.0);
|
||||
|
||||
if (isTopFace > 0.5) {
|
||||
vec2 dirA = normalize(vec2(0.92, 0.38));
|
||||
@@ -951,8 +989,6 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
float waveB = sin(phaseB) * 0.30;
|
||||
float waveC = sin(phaseC) * 0.15;
|
||||
|
||||
transformedPosition.y += (waveA + waveB + waveC) * waveAmplitude;
|
||||
|
||||
vec2 slope =
|
||||
dirA * (cos(phaseA) / 2.3) * 0.55 +
|
||||
dirB * (cos(phaseB) / 1.45) * 0.30 +
|
||||
@@ -964,6 +1000,7 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
vec4 mvPosition = viewMatrix * worldPos;
|
||||
vWorldPos = worldPos.xyz;
|
||||
vViewDir = normalize(cameraPosition - worldPos.xyz);
|
||||
vReflectionCoord = reflectionMatrix * worldPos;
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
#include <fog_vertex>
|
||||
}
|
||||
@@ -981,11 +1018,14 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
uniform vec4 contactPatches[${MAX_WATER_CONTACT_PATCHES}];
|
||||
uniform vec2 contactPatchAxes[${MAX_WATER_CONTACT_PATCHES}];
|
||||
uniform float contactPatchShapes[${MAX_WATER_CONTACT_PATCHES}];
|
||||
uniform sampler2D reflectionTexture;
|
||||
uniform float reflectionEnabled;
|
||||
|
||||
varying vec2 vLocalSurfaceUv;
|
||||
varying vec3 vWaveNormal;
|
||||
varying vec3 vWorldPos;
|
||||
varying vec3 vViewDir;
|
||||
varying vec4 vReflectionCoord;
|
||||
#include <fog_pars_fragment>
|
||||
|
||||
float hash(vec2 p) {
|
||||
@@ -1049,6 +1089,8 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
float contactFoam = 0.0;
|
||||
float contactRipple = 0.0;
|
||||
float contactSheen = 0.0;
|
||||
float reflectionMask = 0.0;
|
||||
vec3 reflectionColor = vec3(0.0);
|
||||
|
||||
float edgeDistance = min(halfSize.x - abs(vLocalSurfaceUv.x), halfSize.y - abs(vLocalSurfaceUv.y));
|
||||
float edgeBand = max(0.22, min(halfSize.x, halfSize.y) * 0.12);
|
||||
@@ -1099,9 +1141,21 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
float refraction = (largeWave - 0.5) * 0.18 + (mediumWave - 0.5) * 0.14 + (microWave - 0.5) * 0.08 + contactRipple * 0.06;
|
||||
float glints = smoothstep(0.78, 0.97, fbm(vLocalSurfaceUv * 4.8 + normal.xz * 2.2 + vec2(time * 0.38, -time * 0.31))) * (0.14 + fresnel * 0.28);
|
||||
vec3 color = mix(deepTint, shallowTint, clamp(0.46 + refraction + fresnel * 0.24 + caustics * 0.08, 0.05, 0.98));
|
||||
|
||||
if (isTopFace > 0.5 && reflectionEnabled > 0.0 && vReflectionCoord.w > 0.0) {
|
||||
vec2 reflectionUv = vReflectionCoord.xy / vReflectionCoord.w;
|
||||
reflectionUv += normal.xz * (0.01 + waveStrength * 0.012) + vec2((microWave - 0.5) * 0.018, (mediumWave - 0.5) * 0.015);
|
||||
if (reflectionUv.x >= 0.0 && reflectionUv.x <= 1.0 && reflectionUv.y >= 0.0 && reflectionUv.y <= 1.0) {
|
||||
reflectionColor = texture2D(reflectionTexture, clamp(reflectionUv, vec2(0.001), vec2(0.999))).rgb;
|
||||
reflectionColor = mix(reflectionColor, shallowTint, 0.16);
|
||||
reflectionMask = reflectionEnabled * clamp(0.12 + fresnel * 0.92 + glints * 0.28, 0.0, 0.92);
|
||||
}
|
||||
}
|
||||
|
||||
float foam = clamp(max(edgeFoam * 0.48, contactFoam) * (0.52 + waveStrength * 0.8) + caustics * 0.08 + glints * 0.06, 0.0, 0.84);
|
||||
vec3 specular = vec3(pow(max(0.0, dot(reflect(-viewDir, normal), normalize(vec3(0.25, 0.88, 0.35)))), 18.0)) * (0.14 + fresnel * 0.56 + caustics * 0.14 + contactSheen * 0.12);
|
||||
|
||||
color = mix(color, reflectionColor, reflectionMask);
|
||||
color = mix(color, vec3(0.97, 0.99, 1.0), foam);
|
||||
color += specular;
|
||||
color += vec3(0.05, 0.08, 0.12) * fresnel;
|
||||
@@ -1116,21 +1170,28 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
}
|
||||
`;
|
||||
|
||||
const material = new ShaderMaterial({
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
uniforms: {
|
||||
const uniforms = UniformsUtils.merge([
|
||||
UniformsLib.fog,
|
||||
{
|
||||
time: animationUniform,
|
||||
waterColor: { value: [cr, cg, cb] },
|
||||
surfaceOpacity: { value: clampedOpacity },
|
||||
waveStrength: { value: waveStrength },
|
||||
waveAmplitude: { value: waveAmplitude },
|
||||
isTopFace: { value: topFaceFlag },
|
||||
halfSize: { value: halfSize },
|
||||
contactPatches: contactPatchesUniform,
|
||||
contactPatchAxes: contactPatchAxesUniform,
|
||||
contactPatchShapes: contactPatchShapesUniform
|
||||
},
|
||||
contactPatchShapes: contactPatchShapesUniform,
|
||||
reflectionTexture: reflectionTextureUniform,
|
||||
reflectionMatrix: reflectionMatrixUniform,
|
||||
reflectionEnabled: reflectionEnabledUniform
|
||||
}
|
||||
]);
|
||||
|
||||
const material = new ShaderMaterial({
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
uniforms,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
fog: true,
|
||||
@@ -1142,6 +1203,9 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
animationUniform,
|
||||
contactPatchesUniform,
|
||||
contactPatchAxesUniform,
|
||||
contactPatchShapesUniform
|
||||
contactPatchShapesUniform,
|
||||
reflectionTextureUniform,
|
||||
reflectionMatrixUniform,
|
||||
reflectionEnabledUniform
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user