lamina
lamina copied to clipboard
Please create an example for TS Custom Layers
It took me a few hours to figure it out using the src
// src/layers/CustomLayer/index.tsx
import { forwardRef } from "react";
import { Abstract } from "lamina/vanilla";
import { LayerProps } from "lamina/types";
import { Node, extend } from "@react-three/fiber";
import { getNonUniformArgs } from "../../utils";
import vertexShader from "./vertex.glsl";
import fragmentShader from "./fragment.glsl";
interface CustomLayerProps extends LayerProps {
color?: THREE.ColorRepresentation | THREE.Color;
alpha?: number;
}
class CustomLayer extends Abstract {
// Define stuff as static properties!
// Uniforms: Must begin with prefix "u_".
// Assign them their default value.
// Any uniforms here will automatically be set as properties on the class as setters and getters.
// There are setters and getters will update the underlying uniforms.
static u_color = "red"; // Can be accessed as CustomExampleLayer.color
static u_alpha = 1; // Can be accessed as CustomExampleLayer.alpha
// Define your fragment shader just like you already do!
// Only difference is, you must return the final color of this layer
static fragmentShader = fragmentShader;
// Optionally Define a vertex shader!
// Same rules as fragment shaders, except no blend modes.
// Return a non-transformed vec3 position.
static vertexShader = vertexShader;
constructor(props?: CustomLayerProps) {
super(CustomLayer, {
name: "CustomLayer",
...props,
});
}
}
declare global {
namespace JSX {
interface IntrinsicElements {
customLayer_: Node<CustomLayer, typeof CustomLayer>;
}
}
}
extend({ CustomLayer_: CustomLayer });
const CustomLayerComponent = forwardRef<CustomLayer, CustomLayerProps>((props, ref) => {
return <customLayer_ ref={ref} args={getNonUniformArgs(props)} {...props} />;
}) as React.ForwardRefExoticComponent<
CustomLayerProps & React.RefAttributes<CustomLayer>
>;
export default CustomLayerComponent;
// src/App
import { useRef } from "react";
import { DebugLayerMaterial } from "lamina";
import { Mesh } from "three";
import { Plane } from "@react-three/drei";
import { GroupProps } from "@react-three/fiber";
import CustomLayer, { CustomLayerProps } from "./layers/CustomLayer";
export default function App({
customLayerProps,
...props
}: GroupProps & { customLayerProps?: CustomLayerProps }) {
const ref = useRef<Mesh>(null!);
return (
<group {...props}>
<Plane
ref={ref}
args={[10, 10, 100, 100]}
rotation={[Math.PI / -2, 0, 0]}
>
<DebugLayerMaterial
color={"#ffffff"}
lighting={"physical"} //
transmission={1}
roughness={0.1}
thickness={3}
>
<CustomLayer color="green" alpha="0.5" />
</DebugLayerMaterial>
</Plane>
</group>
);
}
If you notice I import the .glsl directly. You can do this without ejecting by using the @rescripts/cli
library. It gives you better syntax highlighting
// .rescripts.js
const { edit, getPaths } = require("@rescripts/utilities");
const predicate = (valueToTest) => {
return valueToTest.oneOf;
};
const transform = (match) => ({
...match,
oneOf: [
// Need to add as second-to-last to avoid being intercepted by the file-loader in CRA
...match.oneOf.slice(0, -1),
{
test: /\.(glsl|frag|vert)$/,
exclude: [/node_modules/],
use: ["raw-loader", "glslify-loader"],
},
...match.oneOf.slice(-1),
],
});
function rescriptGlslifyPlugin() {
return (config) => {
const matchingPaths = getPaths(predicate, config);
return edit(transform, matchingPaths, config);
};
}
module.exports = [[rescriptGlslifyPlugin]];
and the packlage.json to make the imports work
// package.json
{
...
"dependencies": {
"glslify": "^7.1.1",
"glslify-loader": "^2.0.0",
"lamina": "^1.1.8",
"raw-loader": "^4.0.2",
...
},
"scripts": {
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",
"eject": "rescripts eject"
},
...
"devDependencies": {
"@rescripts/cli": "^0.0.16",
"@rescripts/rescript-env": "^0.0.14",
"@rescripts/utilities": "^0.0.8"
}
}
now you can load your glsl files
// src/layers/CustomLayer/vertexShader.glsl
varying vec3 v_Position;
void main() {
v_Position = position;
return position * 2.;
}
// src/layers/CustomLayer/fragmentShader.glsl
uniform vec3 u_color;
uniform float u_alpha;
// Varyings must be prefixed by "v_"
varying vec3 v_Position;
vec4 main() {
// Local variables must be prefixed by "f_"
vec4 f_color = vec4(u_color, u_alpha);
return f_color;
}
The TS example is a good idea, I will replace the JS examples with TS actually as it’s easy to derive JS from TS but not the other way around.
as for the GLSL import, that’s great that it works but I don’t think it’s needed in the library itself. It’s a user land thing. Besides you can use the /*glsl*/
decorator to get syntax highlighting for inline template strings.
Oh? didn't know about the decorator, i'll give that a try!
Hey, I am quite struggling with implementing your solution as I am not the best with Typescript but still trying to enforce it as it's a good practice. If you don't mind sharing - what exactly does your utility method getNonUniformArgs
do?
Also, how how would I implement useRef
directly on material layer itself - so I can change the props via useFrame?
I tried following but I am not getting anywhere.
const materialRef = useRef<CustomLayerProps>();
useFrame((state) =>
{
const { clock } = state;
materialRef .current!.time = 0.4 * clock.getElapsedTime();
});
return (
<mesh position={[2, 0, 0]}>
<sphereGeometry args={[4.25, 64, 64]} />
<LayerMaterial color="#434" lighting="standard">
<CustomLayer ref={materialRef} intensity={0.15} />
</LayerMaterial>
</mesh>
);