This mission demonstrates a customized jitter shader impressed by the visible fashion of PS1-era video games. The shader replicates the nostalgic ‘jitter’ impact, including a retro aesthetic to 3D fashions—excellent for builders wanting so as to add a retro really feel to low-poly tasks.
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. Nevertheless, whereas growing the parts, I seen a shortage of sources for net builders aiming to recreate this fashion. It’s time to deliver 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 attention-grabbing 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}
digital camera={{ 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 primarily based on the MeshStandardMaterial
. The important thing right here is making certain it really works with mannequin animations and correctly handles shadows.
A very powerful 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 particular 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 generally be too vibrant, so this may be helpful when adjusting the shadow settings. Lowering coloration brightness with diffuseColor.rgb *= 0.8; is important for attaining a extra genuine retro look, because it helps mimic the restricted coloration palette and lighting of older consoles. Moreover, the colour settings could 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}
`.exchange(
`#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.exchange(
`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 are going to course of these textures by way of the shader. The best possibility for exporting textures from the mannequin is the glTF Report instrument.
The Crash Bandicoot mannequin I selected for this demo consists of a number of elements. That is significantly 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 instrument, we will see that the mannequin makes use of skinnedMesh
as a result of it accommodates animations.
<skinnedMesh
title="Material2"
geometry={nodes.Material2.geometry}
materials={supplies['CrashBack.003']}
skeleton={nodes.Material2.skeleton}
/>
<skinnedMesh
title="Material2001"
geometry={nodes.Material2001.geometry}
materials={supplies['material_1.003']}
skeleton={nodes.Material2001.skeleton}
/>
<skinnedMesh
title="Material2002"
geometry={nodes.Material2002.geometry}
materials={supplies['CrashShoes.003']}
skeleton={nodes.Material2002.skeleton}
/>
<skinnedMesh
title="Material2003"
geometry={nodes.Material2003.geometry}
materials={supplies['material.003']}
skeleton={nodes.Material2003.skeleton}
/>
After exporting the three texture pictures 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 dimension. This optimization helps to keep away from processing the textures twice. You may delete the textures utilizing the glTF Report instrument.
For skinnedMesh supplies, we’ll now apply the customized shader perform 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 should 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, attaining the nostalgic aesthetic of PS1-era video games!
Thanks for studying!