oren-nayer lighting (godot 3.x)

done for practice

oren-nayer lighting model, credit goes to these sites for helping me out (especially Fabrizio Spindola of the gdshader bible)

https://www.linkedin.com/in/fabrizioespindola/
https://www.jordanstevenstechart.com/lighting-models

https://developer.download.nvidia.com/cg/saturate.html

 

ElSuicio corrected me, you can see the corrected code here
https://godotshaders.com/shader/oren-nayer-lighting/#comment-4048

Shader code
shader_type spatial;
render_mode ambient_light_disabled;
render_mode skip_vertex_transform;
 
varying vec4 vector_ws;
varying vec4 vector_os;
float saturate_f(float x)
{
    return max(0,min(1,x));
}
 
vec3 saturate_v3(vec3 value)
{
    vec3 result;
    result.x = max(0,min(1,value.x));
    result.y= max(0,min(1,value.y));
    result.z== max(0,min(1,value.z));
    return result;
}
 
void vertex()
{
    vector_os.xyz = VERTEX;
    vector_ws= WORLD_MATRIX* vec4(vector_os.xyz,1.0);
    vec4 vertex_vs = INV_CAMERA_MATRIX*vector_ws ; //INV_CAMERA_MATRIX in godot 3.5 is equivalent to VIEW_MATRIX in godot 4
    VERTEX = vertex_vs.xyz; 
}
 
void light()
 
{
vec3 i = ATTENUATION;
vec3 l = LIGHT;
vec3 n = NORMAL;
 
vec3 viewDirection= normalize(CAMERA_MATRIX[3]-vector_ws).xyz;
 
float roughness=ROUGHNESS;
float roughnessSqr = roughness *roughness;
vec3 o_n_fraction = roughnessSqr / (roughnessSqr+vec3(0.33,0.13,0.09));
vec3 oren_nayer = vec3(1,0,0)+ vec3(-0.3,0.17,0.45)+o_n_fraction;
 
float cos_ndotl = saturate_f(dot(n,l));
float cos_ndotv = saturate_f(dot(n,viewDirection));
float oren_nayar_s = saturate_f(dot(l,viewDirection)) - cos_ndotl * cos_ndotv;
oren_nayar_s /= mix(max(cos_ndotl,cos_ndotv),1,step(oren_nayar_s,0));
 
 
vec3 lightingModel = ALBEDO* cos_ndotl * (oren_nayer.x+ALBEDO*oren_nayer.y+oren_nayer.z*oren_nayar_s);
vec3 attenColor = i * LIGHT_COLOR;
vec4 finalDiffuse = vec4(lightingModel*attenColor,1);
 
vec3 nDotL = max(0.0, dot(n, l)) * i;
vec3 vDotL = max(0.0, dot(n, l)) * i;
 
 
DIFFUSE_LIGHT += finalDiffuse.xyz;
 
}
Live Preview
Tags
light, lighting, nayer, oren, oren-nayer
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from FP2

Related shaders

guest

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
ElSuicio
16 days ago

The full Oren-Nayar model is based on angles, not roughness. I recommend you read this article: A tiny improvement of Oren-Nayar reflectance model

This page contains an interactive example: Visual Shader Full-Oren-Nayar Light Model Node – Godot 4.4+ by InterDreamSoft

I think this might be useful to you:

// ElSuicio, 2025.
// GODOT v4.5.1.stable.
// x.com/ElSuicio
// github.com/ElSuicio
// Contact email [interdreamsoft@gmail.com]


shader_type spatial;
render_mode skip_vertex_transform;


#define TRANSLATION_MATRIX(t) mat4(\
	vec4(1, 0, 0, t.x),\
	vec4(0, 1, 0, t.y),\
	vec4(0, 0, 1, t.z),\
	vec4(0, 0, 0, 1)\
	);


#define ROTATION_MATRIX_X(a) mat4(\
	vec4(1, 0, 0, 0),\
	vec4(0, cos(a), -sin(a), 0),\
	vec4(0, sin(a),  cos(a), 0),\
	vec4(0, 0, 0, 1)\
	);
#define ROTATION_MATRIX_Y(b) mat4(\
	vec4( cos(b), 0, sin(b), 0),\
	vec4(0, 1, 0, 0),\
	vec4(-sin(b), 0, cos(b), 0),\
	vec4(0, 0, 0, 1)\
	);
#define ROTATION_MATRIX_Z(c) mat4(\
	vec4(cos(c), -sin(c), 0, 0),\
	vec4(sin(c),  cos(c), 0, 0),\
	vec4(0, 0, 1, 0),\
	vec4(0, 0, 0, 1)\
	);


#define ESCALATION_MATRIX(s) mat4(\
	vec4(s.x, 0, 0, 0),\
	vec4(0, s.y, 0, 0),\
	vec4(0, 0, s.z, 0),\
	vec4(0, 0, 0, 1)\
	);


group_uniforms _Transform;
uniform vec3 _Position = vec3(0.0);
uniform vec3 _Rotation = vec3(0.0);
uniform vec3 _Scale = vec3(1.0);


group_uniforms _FullOrenNayar;
uniform vec3 _DiffuseColor : source_color = vec3(1.0);
uniform float _Rho : hint_range(0.0, 1.0, 1e-3) = 0.9;
uniform float _Sigma : hint_range(0.0, 90.0, 1e-3) = 30.0;


varying vec4 _vertex_os;
varying vec4 _normal_os;
varying vec4 _binormal_os;
varying vec4 _tangent_os;


mat4 ROTATION_MATRIX(vec3 euler_angles_rad)
{
	mat4 x = ROTATION_MATRIX_X(euler_angles_rad.x)
	mat4 y = ROTATION_MATRIX_Y(euler_angles_rad.y)
	mat4 z = ROTATION_MATRIX_Z(euler_angles_rad.z)
	return y * x * z;
}


vec3 normalizeLinf(vec3 x)
{
	float norm = max(max(abs(x.x), abs(x.y)), abs(x.z));
	return x / norm;
}


void vertex()
{
	/* Rotation */
	vec3 angle_rad = radians(_Rotation);
	
	/* Vertex */
	_vertex_os = vec4(VERTEX, 1.0);
	
	_vertex_os *= ESCALATION_MATRIX(_Scale)
	
	_vertex_os *= ROTATION_MATRIX(angle_rad);
	
	vec4 vertex_ws = MODEL_MATRIX * _vertex_os;
	
	vertex_ws *= TRANSLATION_MATRIX(_Position)
	
	vec4 vertex_vs = VIEW_MATRIX * vertex_ws;
	
	VERTEX = vertex_vs.xyz;
	
	/* Normal */
	_normal_os = vec4(NORMAL, 0.0);
	
	_normal_os *= ROTATION_MATRIX(angle_rad);
	
	vec4 normal_ws = MODEL_MATRIX * _normal_os;
	
	vec4 normal_vs = VIEW_MATRIX * normal_ws;
	
	NORMAL = normalize(normal_vs.xyz);
	
	/* Binormal */
	_binormal_os = vec4(BINORMAL, 0.0);
	
	_binormal_os *= ROTATION_MATRIX(angle_rad);
	
	vec4 binormal_ws = MODEL_MATRIX * _binormal_os;
	
	vec4 binormal_vs = VIEW_MATRIX * binormal_ws;
	
	BINORMAL = normalize(binormal_vs.xyz);
	
	/* Tangent */
	_tangent_os = vec4(TANGENT, 0.0);
	
	_tangent_os *= ROTATION_MATRIX(angle_rad);
	
	vec4 tangent_ws = MODEL_MATRIX * _tangent_os;
	
	vec4 tangent_vs = VIEW_MATRIX * tangent_ws;
	
	TANGENT = normalize(tangent_vs.xyz);
	
	/* Projection */
	vec4 vertex_proj = PROJECTION_MATRIX * vertex_vs;
	
	POSITION = vertex_proj;
}


void fragment() {
	ALBEDO = _DiffuseColor;
}


void light() {
	vec3 n = normalize(NORMAL);
	vec3 l = normalize(LIGHT);
	vec3 v = normalize(VIEW);
	
	float cNdotL = max(dot(n, l), 0.0);
	float cNdotV = max(dot(n, v), 0.0);
	
	// https://dl.acm.org/doi/pdf/10.1145/192161.192213
	
	float sigma2 = pow(_Sigma * PI / 180.0, 2.0);
	
	float theta_i = acos(cNdotL);
	float theta_r = acos(cNdotV);
	
	vec3 l_proj = normalize(l - n * cNdotL);
	vec3 v_proj = normalize(v - n * cNdotV);
	
	float cos_phi = dot(v_proj, l_proj);
	
	float alpha = max(theta_i, theta_r);
	float beta = min(theta_i, theta_r);
	
	float C1 = 1.0 - 0.5 * sigma2 / (sigma2 + 0.33);
	
	float C2 = 0.45 * sigma2 / (sigma2 + 0.09);
	if(cos_phi >= 0.0)
	{
		C2 *= sin(alpha);
	}
	else
	{
		C2 *= sin(alpha) - pow( 2.0 * beta / PI, 3.0);
	}
	
	float C3 = 0.125 * sigma2 / (sigma2 + 0.09) * pow((4.0 * alpha * beta) / (PI * PI), 2.0);
	
	float L1 = _Rho / PI * (C1 + cos_phi * C2 * tan(beta) + (1.0 - abs(cos_phi)) * C3 * tan((alpha + beta) / 2.0));
	float L2 = 0.17 * _Rho * _Rho / PI * sigma2 / (sigma2 + 0.13) * (1.0 - cos_phi * (4.0 * beta * beta) / (PI * PI));
	
	float f_d = max(min(L1 + L2, 1.0), 0.0) * cNdotL;
	
	DIFFUSE_LIGHT += normalizeLinf(LIGHT_COLOR) * ATTENUATION * f_d;
}

Check: specular highlights of low roughness surfaces break conservation of energy for big light sources · Issue #111853 · godotengine/godot

ElSuicio
15 days ago
Reply to  FP2

Sure, you can use it. I’m glad it might be helpful!