mirror of
https://github.com/huangkaoya/redalert2.git
synced 2026-10-29 14:09:06 +08:00
fix(render): restore WebGL2 voxel instancing and shadow shaders
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user