back..

Custom Shader Prototype For Torpor Games

As part of my internship at Torpor Games, I created a shader prototype as a proof of concept for an original art style that I researched and developed. The core idea was to have the shader interoplate visual states based on the camera distance. The prototype was built in Unity Editor 2020.3.48.f1. The demo is available under this link and can be launched by running the CustomShader executable.

All objects in the scene use the same shader. The shader supports the use of a tiled texture as a grayscale mask for interpolating between two main color pairs - one set for the “far” state and another for the “close” state (as seen in the wallpaper and carpet). The second texture input is for a noise texture, used to blend states in a similar manner - for example, to simulate stains on the carpet. The carpet itself is composed of three planes, each using a different material setup to demonstrate how multiple configurations of the same shader can be combined to create artistic effects.

The shader includes an optional shadow edge effect, with configurable width and color. Global shadows are optional as well. An additional setting enables “fake” shading, which uses a user-defined light direction and a pair of colors for the lit and shaded areas. This is particularly useful for giving depth to otherwise flat-shaded objects. The “fake” shading effect can be observed on the window frame, couch, and small table in the scene.

Code snippet that produces the shadow edge mask.
float prerAxisDisplacement = _EdgeWidth * 0.5f;
float3 posWS = i.worldCoord.xyz;

float4 shadowCoordShiftedRight = TransformWorldToShadowCoord( posWS + i.tangentWS * prerAxisDisplacement + i.bitangentWS * prerAxisDisplacement);
light = GetMainLight(shadowCoordShiftedRight);
float attenuationShiftedRight = light.shadowAttenuation;

float4 shadowCoordShiftedLeft = TransformWorldToShadowCoord( posWS - i.tangentWS * prerAxisDisplacement - i.bitangentWS * prerAxisDisplacement);
light = GetMainLight(shadowCoordShiftedLeft);
float attenuationShiftedLeft = light.shadowAttenuation;

float lightMask = saturate(attenuation*10);
float lightMaskShiftedRight = saturate(attenuationShiftedRight*10);
float lightMaskShiftedLeft = saturate(attenuationShiftedLeft*10);

shadowCoordShiftedRight = TransformWorldToShadowCoord( posWS + i.tangentWS * prerAxisDisplacement - i.bitangentWS * prerAxisDisplacement);
light = GetMainLight(shadowCoordShiftedRight);
attenuationShiftedRight = light.shadowAttenuation;

shadowCoordShiftedLeft = TransformWorldToShadowCoord( posWS - i.tangentWS * prerAxisDisplacement + i.bitangentWS * prerAxisDisplacement);
light = GetMainLight(shadowCoordShiftedLeft);
attenuationShiftedLeft = light.shadowAttenuation;

lightMask = saturate(attenuation*10);
float lightMaskShiftedRight2 = saturate(attenuationShiftedRight*10);
float lightMaskShiftedLeft2 = saturate(attenuationShiftedLeft*10);

diffractionMask = saturate (saturate(lightMask - lightMaskShiftedRight) + saturate(lightMask - lightMaskShiftedLeft) + saturate(lightMask - lightMaskShiftedLeft2) + saturate(lightMask - lightMaskShiftedRight2));
© 2025  •  Based on Moonwalk by abhinavs