Faux Vertex Lighting

This is a novel attempt at lighting that is identical to the real thing with a couple of added benefits for lazy people like me. I made it a couple of years ago for a project that I don’t have time for, so I’m publishing it as-is in case someone would find it useful.

Here are some features:

  • Utilizes scene lights
  • Simulates subdivided surfaces for more detailed lighting without the need for more vertices
    • Subdivisions are not constrained to triangle boundaries, so they aren’t strictly accurate
    • Removing this would result in an authentic implementation for better or for worse

This is not at all parameterized, readable, or well-optimized. I would need to rewrite it to alleviate those issues, but I feel it’s better to publish what I have for now in case I can’t.

Shader code
shader_type spatial;
render_mode skip_vertex_transform;

varying vec3 sfcPosVS;
varying vec3 sfcPosLS;
varying vec3 sfcNmlLS;
varying vec3 sfcRtLS;
varying vec3 sfcUpLS;

vec3 cvtSpcPos(vec3 v, mat4 m) {
	return (m * vec4(v, 1.)).xyz;
}

vec3 cvtSpcDir(vec3 v, mat4 m) {
	return normalize((m * vec4(v, 0.0)).xyz);
}

void vertex() {
	sfcPosLS = VERTEX;
	sfcNmlLS = NORMAL;
	sfcRtLS = TANGENT;
	sfcUpLS = -cross(NORMAL,TANGENT);
	
	VERTEX = cvtSpcPos(VERTEX,MODELVIEW_MATRIX);
	NORMAL = cvtSpcDir(NORMAL,MODELVIEW_MATRIX);
	sfcPosVS = VERTEX;
}

void fragment() {
	//
}

void light() {
	vec3 lgtPosVS = (LIGHT / ATTENUATION) + sfcPosVS;
	vec3 lgtPosWS = cvtSpcPos(lgtPosVS,INV_VIEW_MATRIX);
	
	vec3 uv = vec3(
		dot(sfcPosLS,sfcRtLS),
		dot(sfcPosLS,-sfcUpLS),
		dot(sfcPosLS,sfcNmlLS)
	);
	
	vec3 uvQtz = vec3(
		floor(dot(sfcPosLS,sfcRtLS)),
		floor(dot(sfcPosLS,-sfcUpLS)),
		dot(sfcPosLS,sfcNmlLS)
	);
	
	vec3 tstPosLS = cvtSpcPos(lgtPosWS,inverse(MODEL_MATRIX));
	
	vec3 tstPos2D = vec3(
		dot(tstPosLS,sfcRtLS),
		dot(tstPosLS,-sfcUpLS),
		dot(tstPosLS,sfcNmlLS)
	);
	
	vec3 prjPosUV = tstPos2D;
	
	float dst00 = clamp(
		distance(prjPosUV,uvQtz) * .33,
		0., 1.);
	float dst01 = clamp(
		distance(prjPosUV,uvQtz + vec3(0.,1.,0.)) * .33,
		0., 1.);
	float dst10 = clamp(
		distance(prjPosUV,uvQtz + vec3(1.,0.,0.)) * .33,
		0., 1.);
	float dst11 = clamp(
		distance(prjPosUV,uvQtz + vec3(1.,1.,0.)) * .33,
		0., 1.);
	
	vec3 uvFrc = fract(uv);
	float finalf;
	
	if (uvFrc.x > uvFrc.y) {
		finalf = 
			(1. - uvFrc.x) * dst00 + 
			(uvFrc.x - uvFrc.y) * dst10 + 
			uvFrc.y * dst11; }
	else {
		finalf = 
			(1. - uvFrc.y) * dst00 + 
			(uvFrc.y - uvFrc.x) * dst01 + 
			uvFrc.x * dst11;
	}
	
	float sfcAngMlt = clamp(dot(LIGHT,NORMAL),0.,1.);
	float atnMod = clamp(round(2. * ATTENUATION),0.,1.);
	
	DIFFUSE_LIGHT += atnMod * LIGHT_COLOR * clamp(1. - finalf,0.,1.) * sfcAngMlt;
}
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from nildivision

“Omniplanar” Mapping

Related shaders

View-Matcap Based Fake Vertex Lighting

PSX Shader with Vertex Lighting

Ramp Lighting

Subscribe
Notify of
guest

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
bat
bat
3 days ago

is this 4.3 friendly?

bat
bat
2 days ago
Reply to  nildivision

alright but does it still apply the per-vertex lighting look?

Momonyaro
3 hours ago

I can’t quite figure out how to apply it? It doesn’t take in any texture so I’m assuming that you use multiple passes or am I missing something?