1 /**
2 * Defines Shader class and the Shaders collection for loading, binding, and setting values in GLSL shaders
3 */
4 module dash.graphics.shaders.shaders;
5 import dash.core, dash.components, dash.graphics, dash.utility;
6 import dash.graphics.shaders.glsl;
7 
8 import derelict.opengl3.gl3;
9 
10 import std..string, std.traits, std.algorithm, std.array, std.regex;
11 
12 /*
13  * String constants for our shader uniforms
14  */
15 private enum ShaderUniform
16 {
17     /// Matrices
18     World = "world",
19     WorldProj = "worldProj", // used this for scaling & orthogonal UI drawing
20     WorldView = "worldView",
21     WorldViewProjection = "worldViewProj",
22     InverseProjection = "invProj",
23     LightProjectionView = "lightProjView",
24     CameraView = "cameraView",
25     /// Floats
26     ProjectionConstants = "projectionConstants",
27     /// Textures
28     UITexture = "uiTexture",
29     DiffuseTexture = "diffuseTexture",
30     NormalTexture = "normalTexture",
31     SpecularTexture = "specularTexture",
32     DepthTexture = "depthTexture",
33     ShadowMap = "shadowMap",
34     /// Lights
35     LightDirection = "light.direction",
36     LightColor = "light.color",
37     LightRadius = "light.radius",
38     LightFalloffRate = "light.falloffRate",
39     LightPosition = "light.pos_v",
40     LightShadowless = "light.shadowless",
41     EyePosition = "eyePosition_w",
42     /// Animations
43     Bones = "bones",
44     /// Object data
45     ObjectId = "objectId",
46 }
47 
48 /**
49 * A constant string representing immutable uint fields for each ShaderUniform enum values
50 */
51 enum ShaderUniformFields = reduce!( ( a, b ) => a ~ "immutable uint " ~ b ~ ";\n" )( "", [__traits(allMembers,ShaderUniform )] );
52 
53 /**
54 * Loads necessary shaders into variables, and any custom user shaders into an associative array
55 */
56 final abstract class Shaders
57 {
58 static:
59 private:
60     Shader[string] shaders;
61 
62 public:
63     /// Geometry Shader
64     Shader geometry;
65     /// Animated Geometry Shader
66     Shader animatedGeometry;
67     /// Ambient Lighting Shader
68     Shader ambientLight;
69     /// Directional Lighting shader
70     Shader directionalLight;
71     /// Point Lighting shader
72     Shader pointLight;
73     /// User Interface shader
74     Shader userInterface;
75     /// Shader for depth of inanimate objects.
76     Shader shadowMap;
77     /// Shader for depth of animated objects.
78     Shader animatedShadowMap;
79 
80     /**
81     * Loads the field-shaders first, then any additional shaders in the Shaders folder
82     */
83     final void initialize()
84     {
85         geometry = new Shader( "Geometry", geometryVS, geometryFS, true );
86         animatedGeometry = new Shader( "AnimatedGeometry", animatedGeometryVS, geometryFS, true ); // Only VS changed, FS stays the same
87         ambientLight = new Shader( "AmbientLight", ambientlightVS, ambientlightFS, true );
88         directionalLight = new Shader( "DirectionalLight", directionallightVS, directionallightFS, true );
89         pointLight = new Shader( "PointLight", pointlightVS, pointlightFS, true );
90         userInterface = new Shader( "UserInterface", userinterfaceVS, userinterfaceFS, true );
91         shadowMap = new Shader( "ShadowMap", shadowmapVS, shadowmapFS, true );
92         animatedShadowMap = new Shader( "AnimatedShadowMap", animatedshadowmapVS, shadowmapFS, true );
93 
94         foreach( file; scanDirectory( Resources.Shaders, "*.fs.glsl" ) )
95         {
96             // Strip .fs from file name
97             string name = file.baseFileName[ 0..$-3 ];
98             shaders[ name ] = new Shader( name, file.directory ~ "\\" ~ name ~ ".vs.glsl", file.fullPath );
99         }
100 
101         shaders.rehash();
102     }
103 
104     /**
105     * Empties the array of shaders and calls their Shutdown function
106     */
107     final void shutdown()
108     {
109         foreach_reverse( index; 0 .. shaders.length )
110         {
111             auto name = shaders.keys[ index ];
112             shaders[ name ].shutdown();
113             shaders.remove( name );
114         }
115     }
116 
117     /**
118     * Returns a Shader based on its string name
119     */
120     final Shader opIndex( string name )
121     {
122         return get( name );
123     }
124 
125     /**
126     * Returns a Shader based on its string name
127     */
128     final Shader get( string name )
129     {
130         Shader* shader = name in shaders;
131         return shader is null ? null : *shader;
132     }
133 }
134 
135 /**
136 * Class storing the programID, VS ID, FS ID and ShaderUniform locations for a given Shader program
137 */
138 final package class Shader
139 {
140 private:
141     uint _programID, _vertexShaderID, _fragmentShaderID;
142     string _shaderName;
143     auto versionRegex = ctRegex!r"\#version\s400";
144     auto layoutRegex = ctRegex!r"layout\(location\s\=\s[0-9]+\)\s";
145 
146 public:
147     /// The program ID for the shader
148     mixin( Property!_programID );
149     /// The ID for the vertex shader
150     mixin( Property!_vertexShaderID );
151     /// The ID for the fragment shader
152     mixin( Property!_fragmentShaderID );
153     /// The string name of the Shader
154     mixin( Property!_shaderName );
155     /// Uint locations for each possible Shader Uniform
156     mixin( ShaderUniformFields );
157 
158     /**
159     * Creates a Shader Program from the name, and either the vertex and fragment shader strings, or their file names
160     */
161     this(string name, string vertex, string fragment, bool preloaded = false )
162     {
163         shaderName = name;
164         // Create shader
165         vertexShaderID = glCreateShader( GL_VERTEX_SHADER );
166         fragmentShaderID = glCreateShader( GL_FRAGMENT_SHADER );
167         programID = glCreateProgram();
168 
169         if(!preloaded)
170         {
171             auto vertexFile = Resource( vertex );
172             auto fragmentFile = Resource( fragment );
173             string vertexBody = vertexFile.readText();
174             string fragmentBody = fragmentFile.readText();
175 
176             //If we're using OpenGL 3.3 then we need to
177             //change our GLSL version to match, and remove
178             //any layout(location = x) qualifiers (they
179             //aren't supported in GLSL 330)
180             if(config.graphics.usingGl33)
181             {
182                 vertexBody = replaceAll(vertexBody, layoutRegex, ""); 
183                 vertexBody = replaceAll(vertexBody, versionRegex, "#version 330"); 
184 
185                 fragmentBody = replaceAll(fragmentBody, layoutRegex, ""); 
186                 fragmentBody = replaceAll(fragmentBody, versionRegex, "#version 330"); 
187 
188                 trace( vertexBody );
189             }
190 
191             compile( vertexBody, fragmentBody );
192         }
193         else
194         {
195             if(config.graphics.usingGl33)
196             {
197                     vertex = replaceAll(vertex, layoutRegex, ""); 
198                     vertex = replaceAll(vertex, versionRegex, "#version 330"); 
199 
200                     fragment = replaceAll(fragment, layoutRegex, ""); 
201                     fragment = replaceAll(fragment, versionRegex, "#version 330"); 
202             }
203 
204             compile( vertex, fragment );
205         }
206 
207         //uniform is the *name* of the enum member not it's value
208         foreach( uniform; __traits( allMembers, ShaderUniform ) )
209         {
210             mixin(uniform) = glGetUniformLocation( programID, mixin("ShaderUniform." ~ uniform).ptr );
211         }
212     }
213 
214     /**
215     * Compiles a Vertex and Fragment shader into a Shader Program
216     */
217     void compile( string vertexBody, string fragmentBody )
218     {
219         auto vertexCBody = vertexBody.ptr;
220         auto fragmentCBody = fragmentBody.ptr;
221         int vertexSize = cast(int)vertexBody.length;
222         int fragmentSize = cast(int)fragmentBody.length;
223 
224         glShaderSource( vertexShaderID, 1, &vertexCBody, &vertexSize );
225         glShaderSource( fragmentShaderID, 1, &fragmentCBody, &fragmentSize );
226 
227         GLint compileStatus = GL_TRUE;
228         glCompileShader( vertexShaderID );
229         glGetShaderiv( vertexShaderID, GL_COMPILE_STATUS, &compileStatus );
230         if( compileStatus != GL_TRUE )
231         {
232             errorf( "%s Vertex Shader compile error", shaderName );
233             char[1000] errorLog;
234             auto info = errorLog.ptr;
235             glGetShaderInfoLog( vertexShaderID, 1000, null, info );
236             error( errorLog );
237             assert(false);
238         }
239 
240         glCompileShader( fragmentShaderID );
241         glGetShaderiv( fragmentShaderID, GL_COMPILE_STATUS, &compileStatus );
242         if( compileStatus != GL_TRUE )
243         {
244             errorf( "%s Fragment Shader compile error", shaderName );
245             char[1000] errorLog;
246             auto info = errorLog.ptr;
247             glGetShaderInfoLog( fragmentShaderID, 1000, null, info );
248             error( errorLog );
249             assert(false);
250         }
251 
252         // Attach shaders to program
253         glAttachShader( programID, vertexShaderID );
254         glAttachShader( programID, fragmentShaderID );
255         glLinkProgram( programID );
256 
257         glGetProgramiv( programID, GL_LINK_STATUS, &compileStatus );
258         if( compileStatus != GL_TRUE )
259         {
260             errorf( "%s Shader program linking error", shaderName );
261             char[1000] errorLog;
262             auto info = errorLog.ptr;
263             glGetProgramInfoLog( programID, 1000, null, info );
264             error( errorLog );
265             assert(false);
266         }
267     }
268 
269     /**
270      * Pass through for glUniform1f
271      */
272     final void bindUniform1f( uint uniform, const float value )
273     {
274         glUniform1f( uniform, value );
275     }
276 
277     /**
278      * Pass through for glUniform2f
279      */
280     final void bindUniform2f( uint uniform, const vec2f value )
281     {
282         glUniform2f( uniform, value.x, value.y );
283     }
284 
285     /**
286      * Pass through for glUniform 3f
287      * Passes to the shader in XYZ order
288      */
289     final void bindUniform3f( uint uniform, const vec3f value )
290     {
291         glUniform3f( uniform, value.x, value.y, value.z );
292     }
293 
294     /**
295      * Pass through for glUniform2f
296      */
297     final void bindUniform1ui( uint uniform, const uint value )
298     {
299         glUniform1ui( uniform, value );
300     }
301 
302     /**
303      *  pass through for glUniformMatrix4fv
304      */
305     final void bindUniformMatrix4fv( uint uniform, mat4f matrix )
306     {
307         glUniformMatrix4fv( uniform, 1, true, matrix.value_ptr );
308     }
309 
310     /**
311      * Bind an array of mat4s.
312      */
313     final void bindUniformMatrix4fvArray( uint uniform, mat4f[] matrices )
314     {
315         auto matptr = appender!(float[]);
316         foreach( matrix; matrices )
317         {
318             matptr ~= matrix.value_ptr()[0..16];
319         }
320         glUniformMatrix4fv( uniform, cast(int)matrices.length, true, matptr.data.ptr );
321     }
322 
323     /**
324      * Binds diffuse, normal, and specular textures to the shader
325      */
326     final void bindMaterial( Material material )
327     in
328     {
329         assert( material, "Cannot bind null material." );
330         assert( material.diffuse && material.normal && material.specular, "Material must have diffuse, normal, and specular components." );
331     }
332     body
333     {
334         //This is finding the uniform for the given texture, and setting that texture to the appropriate one for the object
335         glUniform1i( DiffuseTexture, 0 );
336         glActiveTexture( GL_TEXTURE0 );
337         glBindTexture( GL_TEXTURE_2D, material.diffuse.glID );
338 
339         glUniform1i( NormalTexture, 1 );
340         glActiveTexture( GL_TEXTURE1 );
341         glBindTexture( GL_TEXTURE_2D, material.normal.glID );
342 
343         glUniform1i( SpecularTexture, 2 );
344         glActiveTexture( GL_TEXTURE2 );
345         glBindTexture( GL_TEXTURE_2D, material.specular.glID );
346     }
347 
348     /**
349      * Binds a UI's texture
350      */
351     final void bindUI( UserInterface ui )
352     {
353         // This is part of a bigger problem. But in the interest of dope screenshots...
354         version( OSX )
355         if( !ui.view )
356             return;
357 
358         glUniform1i( UITexture, 0 );
359         glActiveTexture( GL_TEXTURE0 );
360         glBindTexture( GL_TEXTURE_2D, ui.view.glID );
361     }
362 
363     /**
364      * Bind an ambient light
365      */
366     final void bindAmbientLight( AmbientLight light )
367     {
368         bindUniform3f( LightColor, light.color );
369     }
370 
371     /**
372      * Bind a directional light
373      */
374     final void bindDirectionalLight( DirectionalLight light )
375     {
376         bindUniform3f( LightDirection, light.direction);
377         bindUniform3f( LightColor, light.color );
378         bindUniform1f( LightShadowless, cast(float)(!light.castShadows) );
379     }
380 
381     /**
382      * Bind a directional light after a modifying transform
383      */
384     final void bindDirectionalLight( DirectionalLight light, mat4f transform )
385     {
386         bindUniform3f( LightDirection, ( transform * vec4f( light.direction, 0.0f ) ).xyz );
387         bindUniform3f( LightColor, light.color );
388         bindUniform1f( LightShadowless, cast(float)(!light.castShadows) );
389     }
390 
391     /**
392      * Bind a point light
393      */
394     final void bindPointLight( PointLight light )
395     {
396         bindUniform3f( LightColor, light.color );
397         bindUniform3f( LightPosition, light.owner.transform.worldPosition );
398         bindUniform1f( LightRadius, light.radius );
399         bindUniform1f( LightFalloffRate, light.falloffRate );
400     }
401 
402     /**
403      * Bind a point light after a modifying transform
404      */
405     final void bindPointLight( PointLight light, mat4f transform )
406     {
407         bindUniform3f( LightColor, light.color );
408         bindUniform3f( LightPosition, ( transform * vec4f( light.owner.transform.worldPosition, 1.0f ) ).xyz);
409         bindUniform1f( LightRadius, light.radius );
410         bindUniform1f( LightFalloffRate, light.falloffRate );
411     }
412 
413 
414     /**
415      * Sets the eye position for lighting calculations
416      */
417     final void setEyePosition( vec3f pos )
418     {
419         glUniform3f( EyePosition, pos.x, pos.y, pos.z );
420     }
421 
422     /**
423      * Clean up the shader
424      */
425     void shutdown()
426     {
427         glDeleteProgram( programID );
428     }
429 }