1 module graphics.shaders;
2 import core.properties;
3 import components;
4 import graphics.graphics;
5 import utility.filepath, utility.output;
6 
7 import derelict.opengl3.gl3;
8 import gl3n.linalg;
9 
10 import std..string, std.traits;
11 
12 final abstract class Shaders
13 {
14 public static:
15 	final void initialize()
16 	{
17 		shaders[ "geometry" ] = new Shader( "geometry", geometryVS, geometryFS, true );
18 		shaders[ "lighting" ] = new Shader( "lighting", lightingVS, lightingFS, true );
19 		foreach( file; FilePath.scanDirectory( FilePath.Resources.Shaders, "*.fs.glsl" ) )
20 		{
21 			// Strip .fs from file name
22 			string name = file.baseFileName[ 0..$-3 ];
23 			if(name != "geometry" && name != "lighting")
24 			{
25 				shaders[ name ] = new Shader( name, file.directory ~ "\\" ~ name ~ ".vs.glsl", file.fullPath );
26 			}
27 			else
28 			{
29 				log( OutputType.Warning, "Shader not loaded: Shader found which would overwrite " ~ name ~ " shader" );
30 			}
31 		}
32 
33 		shaders.rehash();
34 	}
35 
36 	final void shutdown()
37 	{
38 		foreach_reverse( index; 0 .. shaders.length )
39 		{
40 			auto name = shaders.keys[ index ];
41 			shaders[ name ].shutdown();
42 			shaders.remove( name );
43 		}
44 		/*foreach( name, shader; shaders )
45 		{
46 			shader.shutdown();
47 			shaders.remove( name );
48 		}*/
49 	}
50 
51 	final Shader opIndex( string name )
52 	{
53 		return get( name );
54 	}
55 
56 	final Shader get( string name )
57 	{
58 		auto shader = name in shaders;
59 		return shader is null ? null : *shader;
60 	}
61 
62 private:
63 	Shader[string] shaders;
64 }
65 
66 public enum ShaderUniform 
67 {
68 	World = "world",
69 		WorldView = "worldView",
70 		WorldViewProjection = "worldViewProj",
71 		DiffuseTexture = "diffuseTexture",
72 		NormalTexture = "normalTexture",
73 		DepthTexture = "depthTexture",
74 		DirectionalLightDirection = "dirLight.direction",
75 		DirectionalLightColor = "dirLight.color",
76 		AmbientLight = "ambientLight"
77 }
78 
79 final package class Shader
80 {
81 public:
82 	mixin Property!( "uint", "programID", "protected" );
83 	mixin Property!( "uint", "vertexShaderID", "protected" );
84 	mixin Property!( "uint", "fragmentShaderID", "protected" );
85 	mixin Property!( "string", "shaderName", "protected" );
86 	protected int[string] uniformLocations;
87 
88 	this(string name, string vertex, string fragment, bool preloaded = false )
89 	{
90 		shaderName = name;
91 		// Create shader
92         vertexShaderID = glCreateShader( GL_VERTEX_SHADER );
93         fragmentShaderID = glCreateShader( GL_FRAGMENT_SHADER );
94         programID = glCreateProgram();
95 
96 		if(!preloaded)
97 		{
98 			auto vertexFile = new FilePath( vertex );
99 			auto fragmentFile = new FilePath( fragment );
100 			string vertexBody = vertexFile.getContents();
101 			string fragmentBody = fragmentFile.getContents();
102 			compile( vertexBody, fragmentBody );
103 		}
104 		else
105 		{
106 			compile( vertex, fragment );
107 		}
108 	}
109 
110 	void compile( string vertexBody, string fragmentBody )
111 	{
112 		auto vertexCBody = vertexBody.ptr;
113 		auto fragmentCBody = fragmentBody.ptr;
114 		int vertexSize = cast(int)vertexBody.length;
115 		int fragmentSize = cast(int)fragmentBody.length;
116 
117 		glShaderSource( vertexShaderID, 1, &vertexCBody, &vertexSize );
118 		glShaderSource( fragmentShaderID, 1, &fragmentCBody, &fragmentSize );
119 
120 		GLint compileStatus = GL_TRUE;
121 		glCompileShader( vertexShaderID );
122 		glGetShaderiv( vertexShaderID, GL_COMPILE_STATUS, &compileStatus );
123 		if( compileStatus != GL_TRUE )
124 		{
125 			log( OutputType.Error, shaderName ~ " Vertex Shader compile error" );
126 			char[1000] errorLog;
127 			auto info = errorLog.ptr;
128 			glGetShaderInfoLog( vertexShaderID, 1000, null, info );
129 			log( OutputType.Error, errorLog );
130 			assert(false);
131 		}
132 
133 		glCompileShader( fragmentShaderID );
134 		glGetShaderiv( fragmentShaderID, GL_COMPILE_STATUS, &compileStatus );
135 		if( compileStatus != GL_TRUE )
136 		{
137 			log( OutputType.Error, shaderName ~ " Fragment Shader compile error" );
138 			char[1000] errorLog;
139 			auto info = errorLog.ptr;
140 			glGetShaderInfoLog( fragmentShaderID, 1000, null, info );
141 			log( OutputType.Error, errorLog );
142 			assert(false);
143 		}
144 
145 		// Attach shaders to program
146         glAttachShader( programID, vertexShaderID );
147         glAttachShader( programID, fragmentShaderID );
148 		glLinkProgram( programID );
149 
150 		bindUniforms();
151 
152 		glGetProgramiv( programID, GL_LINK_STATUS, &compileStatus );
153         if( compileStatus != GL_TRUE )
154         {
155 			log( OutputType.Error, shaderName ~ " Shader program linking error" );
156 			char[1000] errorLog;
157 			auto info = errorLog.ptr;
158 			glGetProgramInfoLog( programID, 1000, null, info );
159 			log( OutputType.Error, errorLog );
160 			assert(false);
161 		}
162 	}
163 
164 	void bindUniforms()
165 	{
166 		//uniform is the *name* of the enum member not it's value
167 		foreach( uniform; [ EnumMembers!ShaderUniform ] )
168 		{
169 			//thus we use the mixin to get the value at compile time
170 			int uniformLocation = glGetUniformLocation( programID, uniform.ptr );
171 
172 			uniformLocations[ uniform ] = uniformLocation;
173 		}
174 	}
175 
176 	int getUniformLocation( ShaderUniform uniform )
177 	{
178 		return uniformLocations[ uniform ];
179 	}
180 
181 	final void bindUniform1f( ShaderUniform uniform, const float value )
182 	{
183 		auto currentUniform = getUniformLocation( uniform );
184 
185 		glUniform1f( currentUniform, value );
186 	}
187 
188 	final void bindUniformMatrix4fv( ShaderUniform uniform, mat4 matrix )
189 	{
190 		auto currentUniform = getUniformLocation( uniform );
191 
192 		glUniformMatrix4fv( currentUniform, 1, true, matrix.value_ptr );
193 	}
194 
195 	final void bindMaterial( Material material )
196 	{
197 		//This is finding the uniform for the given texture, and setting that texture to the appropriate one for the object
198 		GLint textureLocation = getUniformLocation( ShaderUniform.DiffuseTexture );
199 		glUniform1i( textureLocation, 0 );
200 		glActiveTexture( GL_TEXTURE0 );
201 		glBindTexture( GL_TEXTURE_2D, material.diffuse.glID );
202 
203 		textureLocation = getUniformLocation( ShaderUniform.NormalTexture );
204 		glUniform1i( textureLocation, 1 );
205 		glActiveTexture( GL_TEXTURE1 );
206 		glBindTexture( GL_TEXTURE_2D, material.normal.glID );
207 	}
208 
209 	final void bindAmbientLight( AmbientLight light )
210 	{
211 		glUniform3f( getUniformLocation( ShaderUniform.AmbientLight ), light.color.x, light.color.y, light.color.z );
212 	}
213 
214 	final void bindDirectionalLight( DirectionalLight light )
215 	{
216 		// buffer light here
217 		glUniform3f( getUniformLocation( ShaderUniform.DirectionalLightDirection ), (cast(DirectionalLight)light).direction.x, (cast(DirectionalLight)light).direction.y, (cast(DirectionalLight)light).direction.z );
218 		glUniform3f( getUniformLocation( ShaderUniform.DirectionalLightColor ), light.color.x, light.color.y, light.color.z );
219 	}
220 
221 	void shutdown()
222 	{
223 
224 	}
225 }
226 
227 immutable string geometryVS = q{
228 #version 400
229 
230 layout(location = 0) in vec3 vPosition_m;
231 layout(location = 1) in vec2 vUV;
232 layout(location = 2) in vec3 vNormal_m;
233 layout(location = 3) in vec3 vTangent_m;
234 
235 out vec4 fPosition_s;
236 out vec3 fNormal_w;
237 out vec2 fUV;
238 out vec3 fTangent_w;
239 out vec3 fBitangent_w;
240 
241 uniform mat4 world;
242 uniform mat4 worldView;
243 uniform mat4 worldViewProj;
244 
245 void main( void )
246 {
247 	// gl_Position is like SV_Position
248 	fPosition_s = worldViewProj * vec4( vPosition_m, 1.0f );
249 	gl_Position = fPosition_s;
250 	fUV = vUV;
251 
252 	fNormal_w = ( world * vec4( vNormal_m, 0.0f ) ).xyz;
253 	fTangent_w =  ( world * vec4( vTangent_m, 0.0f ) ).xyz;
254 }
255 };
256 
257 immutable string geometryFS = q{
258 #version 400
259 
260 in vec4 fPosition_s;
261 in vec3 fNormal_w;
262 in vec2 fUV;
263 in vec3 fTangent_w;
264 
265 layout( location = 0 ) out vec4 color;
266 layout( location = 1 ) out vec4 normal_w;
267 
268 uniform sampler2D diffuseTexture;
269 uniform sampler2D normalTexture;
270 
271 vec2 encode( vec3 normal )
272 {
273 	float t = sqrt( 2 / 1 - normal.z );
274 	return normal.xy * t;
275 }
276 
277 vec3 calculateMappedNormal()
278 {
279 	vec3 normal = normalize( fNormal_w );
280 	vec3 tangent = normalize( fTangent_w );
281 	//Use Gramm-Schmidt process to orthogonalize the two
282 	tangent = normalize( tangent - dot( tangent, normal ) * normal );
283 	vec3 bitangent = cross( tangent, normal );
284 	vec3 normalMap = ((texture( normalTexture, fUV ).xyz) * 2) - 1;
285 	mat3 TBN = mat3( tangent, bitangent, normal );
286 	return normalize( TBN * normalMap );
287 }
288 
289 void main( void )
290 {
291 	color = texture( diffuseTexture, fUV );	
292 	normal_w = vec4( calculateMappedNormal(), 1.0f );
293 }
294 };
295 
296 immutable string lightingVS = q{
297 #version 400
298 
299 layout(location = 0) in vec3 vPosition_s;
300 layout(location = 1) in vec2 vUV;
301 
302 out vec4 fPosition_s;
303 out vec2 fUV;
304 
305 void main( void )
306 {
307 	fPosition_s = vec4( vPosition_s, 1.0f );
308 	gl_Position = fPosition_s;
309 	fUV = vUV;
310 }
311 };
312 
313 immutable string lightingFS = q{
314 #version 400
315 
316 struct DirectionalLight
317 {
318 	vec3 color;
319 	vec3 direction;
320 };
321 
322 in vec4 fPosition;
323 in vec2 fUV;
324 
325 uniform sampler2D diffuseTexture;
326 uniform sampler2D normalTexture;
327 uniform sampler2D depthTexture;
328 uniform DirectionalLight dirLight;
329 uniform vec3 ambientLight;
330 
331 // https://stackoverflow.com/questions/9222217/how-does-the-fragment-shader-know-what-variable-to-use-for-the-color-of-a-pixel
332 out vec4 color;
333 
334 vec3 decode( vec2 enc )
335 {
336 	float t = ( ( enc.x * enc.x ) + ( enc.y * enc.y ) ) / 4;
337 	float ti = sqrt( 1 - t );
338 	return vec3( ti * enc.x, ti * enc.y, -1 + t * 2 );
339 }
340 
341 void main( void )
342 {
343 	vec4 textureColor = texture( diffuseTexture, fUV );
344 	vec3 normal = texture( normalTexture, fUV ).xyz;
345 
346 	// temp vars until we get lights in
347 	//vec3 lightDirection = vec3( 0.0f, -1.0f, 0.5f );
348 	//vec4 diffuseColor = vec4( 1.0f, 1.0f, 1.0f, 1.0f );
349 
350 	float diffuseIntensity = clamp( dot( normal, -dirLight.direction ), 0, 1 );
351 	color = ( vec4( ambientLight, 1.0f ) + ( diffuseIntensity * vec4( dirLight.color, 1.0f ) ) ) * textureColor;
352 }
353 };