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 };