This challenge demonstrates a customized jitter shader impressed by the visible type of PS1-era video games. The shader replicates the nostalgic ‘jitter’ impact, including a retro aesthetic to 3D fashions—good for builders wanting so as to add a retro really feel to low-poly initiatives.
I’ve at all times discovered PS1 video games fascinating. Impressed by their nostalgic 3D environments, I made a decision to create a portfolio that captures this retro aesthetic. Nonetheless, whereas growing the elements, I observed a shortage of assets for net builders aiming to recreate this type. It’s time to convey a contact of nostalgia to the trendy net.
A little bit of background: The PS1 jitter impact outcomes from the console’s restricted precision in vertex calculations, resulting in the attribute “wobbling” of polygons. This was a byproduct of its fixed-point arithmetic and lack of sub-pixel accuracy, which, though seen as a limitation on the time, has now develop into a cherished visible quirk. That is particularly fascinating for Indie video games and digital artwork that intention to evoke nostalgia or discover lo-fi visuals.
Right here’s a video from the demo:
The Setup
Firstly, we create our stage.
import { Canvas } from "@react-three/fiber";
<Canvas
dpr={1}
digicam={{ fov: 30, place: [0, 1, 3.5] }}
shadows // Allow shadows
>
<ambientLight depth={3} />
<directionalLight place={[0, 1, 0.5]} depth={3} castShadow />
</Canvas>
Subsequent, we have to create a customized shader materials based mostly on the MeshStandardMaterial
. The important thing right here is making certain it really works with mannequin animations and correctly handles shadows.
Crucial step is to regulate the default shader code utilizing onBeforeCompile
earlier than compiling the shader.
On this modification course of on the Vertex Shader, the X and Y coordinates of every vertex are scaled by uJitterLevel
and rounded (utilizing ground) on a selected grid. This creates the PS1-style jitter impact. Scaling the X and Y coordinates by uJitterLevel and making use of ground() simulates the jitter impact by snapping vertex positions to a grid.
With the code we added within the Fragment Shader, we make the colours seem a bit extra pale. Rendered colours can typically be too brilliant, so this may be helpful when adjusting the shadow settings. Lowering coloration brightness with diffuseColor.rgb *= 0.8; is crucial for reaching a extra genuine retro look, because it helps mimic the restricted coloration palette and lighting of older consoles. Moreover, the colour settings might be expanded additional if wanted.
const createCustomMaterial = (coloration, jitterLevel, texture) => {
return new THREE.MeshStandardMaterial({
coloration,
map: texture || null,
onBeforeCompile: (shader) => {
shader.uniforms.uJitterLevel = { worth: jitterLevel };
shader.vertexShader = `
uniform float uJitterLevel;
${shader.vertexShader}
`.change(
`#embrace <project_vertex>`,
`
vec4 mvPosition = modelViewMatrix * vec4( remodeled, 1.0 );
gl_Position = projectionMatrix * mvPosition;
gl_Position.xy /= gl_Position.w;
gl_Position.xy = ground(gl_Position.xy * uJitterLevel) / uJitterLevel * gl_Position.w;
`
);
shader.fragmentShader = shader.fragmentShader.change(
`vec4 diffuseColor = vec4( diffuse, opacity );`,
`
vec4 diffuseColor = vec4( diffuse, opacity );
diffuseColor.rgb *= 0.8; // Little darker colours
`
);
},
});
};
Importing the mannequin with textures
We have to choose a mannequin and export its textures. We’ll course of these textures by means of the shader. The best possibility for exporting textures from the mannequin is the glTF Report device.
The Crash Bandicoot mannequin I selected for this demo consists of a number of elements. That is notably related as a result of the fashions I utilized in my portfolio additionally consisted of separate elements for numerous causes, requiring completely different options.
After making the mannequin appropriate with React Three Fiber utilizing the gltfjsx device, we are able to see that the mannequin makes use of skinnedMesh
as a result of it incorporates animations.
<skinnedMesh
identify="Material2"
geometry={nodes.Material2.geometry}
materials={supplies['CrashBack.003']}
skeleton={nodes.Material2.skeleton}
/>
<skinnedMesh
identify="Material2001"
geometry={nodes.Material2001.geometry}
materials={supplies['material_1.003']}
skeleton={nodes.Material2001.skeleton}
/>
<skinnedMesh
identify="Material2002"
geometry={nodes.Material2002.geometry}
materials={supplies['CrashShoes.003']}
skeleton={nodes.Material2002.skeleton}
/>
<skinnedMesh
identify="Material2003"
geometry={nodes.Material2003.geometry}
materials={supplies['material.003']}
skeleton={nodes.Material2003.skeleton}
/>
After exporting the three texture photographs from the mannequin utilizing glTF.report, I eliminated the mannequin’s textures. Though textures don’t considerably have an effect on this mannequin, some textures could also be giant in measurement. This optimization helps to keep away from processing the textures twice. You may delete the textures utilizing the glTF Report device.
For skinnedMesh supplies, we’ll now apply the customized shader operate we mentioned earlier. This permits us to include the textures we exported from the mannequin.
If you’re engaged on a easy mannequin with a single texture, it doesn’t must be created individually. After that, we place our supplies within the skinnedMesh.
const [crashTextureOne, crashTextureTwo, crashTextureThree] = useTexture([
"/textures/texture.png",
"/textures/texture-1.png",
"/textures/texture-2.png",
]);
const crashMaterials = useMemo(() => {
const baseColor = "#ffffff";
const supplies = [
createCustomMaterial(
baseColor,
jitterLevel,
enableTexture ? crashTextureOne : null
),
createCustomMaterial(
baseColor,
jitterLevel,
enableTexture ? crashTextureTwo : null
),
createCustomMaterial(
baseColor,
jitterLevel,
enableTexture ? crashTextureThree : null
),
createCustomMaterial(baseColor, jitterLevel)
];
return supplies;
}, [
jitterLevel,
enableTexture,
]);
By following these steps, we’ve efficiently built-in a customized jitter shader into our 3D mannequin, reaching the nostalgic aesthetic of PS1-era video games!
Thanks for studying!