当从drei库中创建shaderMaterial时,使用ref会导致TypeScript报怨:
类型'RefObject
'不能分配给类型'Ref |未定义"。
我根据它来声明类型写在这个故事书从drei GitHub repo:
type PolygonMat = {
uTime: number
} & JSX.IntrinsicElements['shaderMaterial']
declare global {
namespace JSX {
interface IntrinsicElements {
polygonMaterial: PolygonMat,
polyMat: PolyMat
}
}
}
然后在组件内部使用useRef:
来更新增量时间错误显示在polygonMaterial
组件的ref
属性上。
function PolygonMesh() {
const matRef = useRef<PolygonMat>(null)
useFrame((state, delta) => {
if (matRef.current) {
matRef.current.uTime += delta / 1.5
}
})
return (
<mesh>
<planeGeometry args={[18.0, 8.0, 18.0, 8.0]} />
<polygonMaterial ref={matRef} wireframe />
</mesh>
)
}
除了TypeScript有问题之外,它可以正常工作。
我认为可以改进shaderMaterial
的实现,从提供的uniforms
推断出泛型类型。我有一个在本地运行的版本,以及一个具有等效更改的分支-所以我的下一个端口是将其转化为PR。这似乎有助于完成您所追求的。
使用它,加上TypeScript声明,给ref赋值并修改ref的属性:
const vertexShader = /*glsl*/`
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = /*glsl*/`
uniform float myOpacity;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, myOpacity);
}
`;
const testShaderDefaultProps = {
myOpacity: 0.5,
};
// referring to my modified version of shaderMaterial, which unions the type of 1st argument with existing return type...
export const TestMaterial = shaderMaterial(testShaderDefaultProps, vertexShader, fragmentShader);
// NB: for some reason, I need to do this in the place where I import TestMaterial, not sure why
// - wasn't the case for a `class TestMaterial extends ShaderMaterial` version I experimented with
extend({ TestMaterial });
declare global {
namespace JSX {
interface IntrinsicElements {
testMaterial: ReactThreeFiber.Node<typeof TestMaterial & JSX.IntrinsicElements['shaderMaterial'], typeof TestMaterial>
}
}
}
function PolygonMesh() {
const matRef = useRef<typeof TestMaterial>(null);
useFrame(() => {
if (matRef.current) matRef.current.myOpacity = 0.5 + 0.5*Math.sin(0.01*Date.now());
});
return (
<mesh>
<boxGeometry />
<testMaterial ref={matRef} />
</mesh>
)
}
因此,declare
语句与您的语句不同;ReactThreeFibre.Node<>
,我不确定我现在是否能清楚地解释这一切的逻辑……
但是无论如何,我对shaderMaterial
所做的改变是:
// this is only used in one place, but seems cleaner to move it out here than inside the generic.
type U = {
[name: string]:
| THREE.CubeTexture
| THREE.Texture
| Int32Array
| Float32Array
| THREE.Matrix4
| THREE.Matrix3
| THREE.Quaternion
| THREE.Vector4
| THREE.Vector3
| THREE.Vector2
| THREE.Color
| number
| boolean
| Array<any>
| null
};
export function shaderMaterial<T extends U>(
uniforms: T,
vertexShader: string,
fragmentShader: string,
onInit?: (material?: THREE.ShaderMaterial) => void
) {
const material = class extends THREE.ShaderMaterial {
public key: string = ''
constructor(parameters = {}) {
const entries = Object.entries(uniforms)
// Create unforms and shaders
super({
uniforms: entries.reduce((acc, [name, value]) => {
const uniform = THREE.UniformsUtils.clone({ [name]: { value } })
return {
...acc,
...uniform,
}
}, {}),
vertexShader,
fragmentShader,
})
// Create getter/setters
entries.forEach(([name]) =>
Object.defineProperty(this, name, {
get: () => this.uniforms[name].value,
set: (v) => (this.uniforms[name].value = v),
})
)
// Assign parameters, this might include uniforms
Object.assign(this, parameters)
// Call onInit
if (onInit) onInit(this)
}
} as unknown as typeof THREE.ShaderMaterial & { key: string } & T
material.key = THREE.MathUtils.generateUUID()
return material
}