fix(render): restore WebGL2 voxel instancing and shadow shaders

This commit is contained in:
霜冻公爵
2026-10-11 06:36:26 +08:00
parent 898d891885
commit b112be90ec
4 changed files with 72 additions and 22 deletions
+5 -1
View File
@@ -27,7 +27,11 @@ export class Renderer {
if (!this.renderer) {
throw new Error('Renderer not yet initialized');
}
return !!this.renderer.extensions.get('ANGLE_instanced_arrays');
const context = this.renderer.getContext();
// Instancing is core in WebGL2; the WebGL1 extension may be absent.
return 'drawElementsInstanced' in context && 'vertexAttribDivisor' in context &&
typeof context.drawElementsInstanced === 'function' &&
typeof context.vertexAttribDivisor === 'function';
}
initStats(container: HTMLElement): void {
if (!this.stats) {
+15 -19
View File
@@ -1,23 +1,12 @@
import * as THREE from 'three';
const depthMaterial = new THREE.MeshDepthMaterial();
depthMaterial.depthPacking = THREE.RGBADepthPacking;
(depthMaterial as any).clipping = true;
(depthMaterial as any).defines = { INSTANCE_TRANSFORM: "" };
const distanceShader = THREE.ShaderLib.distance;
const distanceUniforms = THREE.UniformsUtils.clone(distanceShader.uniforms);
const distanceDefines = { USE_SHADOWMAP: "", INSTANCE_TRANSFORM: "" };
const distanceMaterial = new THREE.ShaderMaterial({
defines: distanceDefines,
uniforms: distanceUniforms,
vertexShader: distanceShader.vertexShader,
fragmentShader: distanceShader.fragmentShader,
clipping: true,
});
import { installInstancingShader } from './instancingShader';
export class InstancedMesh extends THREE.Mesh {
public maxInstances: number;
public uniformScale: boolean;
public useInstanceColor: boolean;
private instanceMatrixAttributes: THREE.InstancedBufferAttribute[];
private inverseWorldMatrix = new THREE.Matrix4();
private localInstanceMatrix = new THREE.Matrix4();
constructor(geometry: THREE.BufferGeometry, material: THREE.Material, maxInstances: number, uniformScale: boolean, useInstanceColor: boolean = false) {
const instancedGeometry = new THREE.InstancedBufferGeometry();
(instancedGeometry as any).copy(geometry);
@@ -26,10 +15,10 @@ export class InstancedMesh extends THREE.Mesh {
this.uniformScale = uniformScale;
this.useInstanceColor = useInstanceColor;
this.initAttributes(this.geometry as THREE.InstancedBufferGeometry);
this.material = this.decorateMaterial(material.clone());
this.material = this.decorateMaterial(material.clone(), !!(material as any).palette);
this.frustumCulled = false;
this.customDepthMaterial = depthMaterial;
this.customDistanceMaterial = distanceMaterial;
this.customDepthMaterial = this.decorateMaterial(new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }));
this.customDistanceMaterial = this.decorateMaterial(new THREE.MeshDistanceMaterial());
}
private initAttributes(geometry: THREE.InstancedBufferGeometry): void {
const attributes: Array<{
@@ -69,7 +58,7 @@ export class InstancedMesh extends THREE.Mesh {
.fill(0)
.map((_, i) => geometry.getAttribute("instanceMatrix" + i) as THREE.InstancedBufferAttribute);
}
private decorateMaterial(material: THREE.Material): THREE.Material {
private decorateMaterial(material: THREE.Material, palette = false): THREE.Material {
const mat = material as any;
if (!mat.defines) {
mat.defines = {};
@@ -88,6 +77,7 @@ export class InstancedMesh extends THREE.Mesh {
delete mat.defines.INSTANCE_COLOR;
}
mat.defines.INSTANCE_OPACITY = "";
installInstancingShader(material, palette);
return material;
}
public setRenderCount(count: number): void {
@@ -110,9 +100,13 @@ export class InstancedMesh extends THREE.Mesh {
const opacityAttr = attributes.instanceOpacity as THREE.InstancedBufferAttribute;
const paletteOffsetAttr = attributes.instancePaletteOffset as THREE.InstancedBufferAttribute;
const extraLightAttr = attributes.instanceExtraLight as THREE.InstancedBufferAttribute;
// Source matrices are in world space; the batch can have a transformed parent.
this.updateWorldMatrix(true, false);
this.inverseWorldMatrix.copy(this.matrixWorld).invert();
for (let i = 0, len = meshes.length; i < len; i++) {
const mesh = meshes[i];
this.setMatrixAt(i, mesh.matrixWorld);
this.localInstanceMatrix.multiplyMatrices(this.inverseWorldMatrix, mesh.matrixWorld);
this.setMatrixAt(i, this.localInstanceMatrix);
const opacity = mesh.getOpacity();
if (opacityAttr.getX(i) !== opacity) {
opacityAttr.setX(i, opacity);
@@ -142,5 +136,7 @@ export class InstancedMesh extends THREE.Mesh {
public dispose(): void {
this.geometry.dispose();
(this.material as THREE.Material).dispose();
this.customDepthMaterial?.dispose();
this.customDistanceMaterial?.dispose();
}
}
+8 -2
View File
@@ -35,7 +35,12 @@ export class MeshInstancingBatch {
set clippingPlanes(value: THREE.Plane[]) {
this._clippingPlanes = value;
if (this.instancedMesh) {
(this.instancedMesh.material as any).clippingPlanes = value;
this.applyClippingPlanes(this.instancedMesh);
}
}
private applyClippingPlanes(mesh: InstancedMesh): void {
for (const material of [mesh.material as THREE.Material, mesh.customDepthMaterial, mesh.customDistanceMaterial]) {
if (material) material.clippingPlanes = this._clippingPlanes;
}
}
get renderOrder(): number {
@@ -69,8 +74,9 @@ export class MeshInstancingBatch {
if (!this.instancedMesh) {
this.instancedMesh = new InstancedMesh(meshes[0].geometry, meshes[0].material, this.maxInstances, true);
this.instancedMesh.castShadow = this._castShadow;
this.instancedMesh.receiveShadow = this._receiveShadow;
this.instancedMesh.renderOrder = this._renderOrder;
(this.instancedMesh.material as any).clippingPlanes = this._clippingPlanes;
this.applyClippingPlanes(this.instancedMesh);
if (hasPalette) {
const geometry = this.instancedMesh.geometry as THREE.InstancedBufferGeometry;
geometry.setAttribute('instancePaletteOffset', new THREE.InstancedBufferAttribute(new Float32Array(this.maxInstances), 1));
+44
View File
@@ -0,0 +1,44 @@
import * as THREE from 'three';
/** Use the same instance transform in the color and both shadow passes. */
export function installInstancingShader(material: THREE.Material, palette = false): void {
const beforeCompile = material.onBeforeCompile;
const cacheKey = material.customProgramCacheKey();
material.onBeforeCompile = (shader, renderer) => {
beforeCompile.call(material, shader, renderer);
shader.vertexShader = shader.vertexShader
.replace('#include <common>', `#include <common>
attribute vec4 instanceMatrix0;
attribute vec4 instanceMatrix1;
attribute vec4 instanceMatrix2;
attribute vec4 instanceMatrix3;
attribute float instanceOpacity;
varying float vInstanceOpacity;
mat4 getInstanceTransform() {
return mat4(instanceMatrix0, instanceMatrix1, instanceMatrix2, instanceMatrix3);
}`)
.replace('void main() {', `void main() {
vInstanceOpacity = instanceOpacity;`)
.replace('#include <beginnormal_vertex>', `#include <beginnormal_vertex>
// As with Three.js instancing, rotation and nonuniform scale are supported;
// shear transforms are not used by voxel builders.
mat3 instanceNormal = mat3(getInstanceTransform());
objectNormal /= vec3(dot(instanceNormal[0], instanceNormal[0]),
dot(instanceNormal[1], instanceNormal[1]),
dot(instanceNormal[2], instanceNormal[2]));
objectNormal = instanceNormal * objectNormal;
#ifdef USE_TANGENT
objectTangent = instanceNormal * objectTangent;
#endif`)
.replace('#include <begin_vertex>', `#include <begin_vertex>
transformed = (getInstanceTransform() * vec4(transformed, 1.0)).xyz;`);
shader.fragmentShader = shader.fragmentShader.replace('#include <common>',
'#include <common>\nvarying float vInstanceOpacity;');
// Palette shaders already apply instanceOpacity when resolving the color.
if (!palette) {
shader.fragmentShader = shader.fragmentShader.replace('#include <alphatest_fragment>',
'diffuseColor.a *= vInstanceOpacity;\n#include <alphatest_fragment>');
}
};
material.customProgramCacheKey = () => `${cacheKey}|instance-transform-opacity-v1|${Number(palette)}`;
}