1 /*
2 Copyright (c) 2017-2018 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.graphics.materials.terrain;
29 
30 import std.stdio;
31 import std.math;
32 import std.conv;
33 
34 import dlib.core.memory;
35 import dlib.math.vector;
36 import dlib.math.matrix;
37 import dlib.image.color;
38 import dlib.image.unmanaged;
39 
40 import derelict.opengl;
41 
42 import dagon.core.ownership;
43 import dagon.graphics.rc;
44 import dagon.graphics.shadow;
45 import dagon.graphics.clustered;
46 import dagon.graphics.material;
47 import dagon.graphics.materials.generic;
48 
49 /*
50  * Backend for terrain material. 
51  * Currently supports automatic blending of two textures (grass and mountains) based on slopeness.
52  */
53 
54 class TerrainBackend: GLSLMaterialBackend
55 {    
56     private string vsText = q{
57         #version 330 core
58         
59         layout (location = 0) in vec3 va_Vertex;
60         layout (location = 1) in vec3 va_Normal;
61         layout (location = 2) in vec2 va_Texcoord;
62         
63         out vec2 texCoord;
64         out vec3 eyePosition;
65         out vec3 worldPosition;
66         out vec3 eyeNormal;
67         out vec3 worldNormal;
68         
69         out vec4 shadowCoord1;
70         out vec4 shadowCoord2;
71         out vec4 shadowCoord3;
72         
73         uniform mat4 modelViewMatrix;
74         uniform mat4 normalMatrix;
75         uniform mat4 projectionMatrix;
76         uniform mat4 invViewMatrix;
77         
78         uniform mat4 shadowMatrix1;
79         uniform mat4 shadowMatrix2;
80         uniform mat4 shadowMatrix3;
81         
82         const float texScale = 100.0;
83         const float eyeSpaceNormalShift = 0.05;
84     
85         void main()
86         {
87             texCoord = va_Texcoord * texScale;
88             worldNormal = va_Normal;
89             eyeNormal = (normalMatrix * vec4(va_Normal, 0.0)).xyz;
90             
91             vec4 pos = modelViewMatrix * vec4(va_Vertex, 1.0);
92             eyePosition = pos.xyz;
93             
94             worldPosition = (invViewMatrix * pos).xyz;
95             
96             vec4 posShifted = pos + vec4(eyeNormal * eyeSpaceNormalShift, 0.0);
97             shadowCoord1 = shadowMatrix1 * posShifted;
98             shadowCoord2 = shadowMatrix2 * posShifted;
99             shadowCoord3 = shadowMatrix3 * posShifted;
100             
101             gl_Position = projectionMatrix * pos;
102         }
103     };
104     
105     private string fsText = q{
106         #version 330 core
107         
108         in vec2 texCoord;
109         in vec3 eyePosition;
110         in vec3 worldPosition;
111         in vec3 eyeNormal;
112         in vec3 worldNormal;
113         
114         in vec4 shadowCoord1;
115         in vec4 shadowCoord2;
116         in vec4 shadowCoord3;
117         
118         out vec4 frag_color;
119         
120         uniform mat4 viewMatrix;
121         uniform mat4 invViewMatrix;
122         
123         uniform float roughness;
124         
125         uniform sampler2D grassTexture;
126         uniform sampler2D mountsTexture;
127         
128         uniform sampler2D grassNormalTexture;
129         uniform sampler2D mountsNormalTexture;
130         
131         uniform sampler2DArrayShadow shadowTextureArray;
132         uniform float shadowTextureSize;
133         uniform bool useShadows;
134         
135         uniform float invLightDomainSize;
136         uniform usampler2D lightClusterTexture;
137         uniform usampler1D lightIndexTexture;
138         uniform sampler2D lightsTexture;
139         
140         uniform vec4 environmentColor;
141         uniform vec3 sunDirection;
142         uniform vec3 sunColor;
143         uniform vec4 fogColor;
144         uniform float fogStart;
145         uniform float fogEnd;
146         
147         mat3 cotangentFrame(in vec3 N, in vec3 p, in vec2 uv)
148         {
149             vec3 dp1 = dFdx(p);
150             vec3 dp2 = dFdy(p);
151             vec2 duv1 = dFdx(uv);
152             vec2 duv2 = dFdy(uv);
153             vec3 dp2perp = cross(dp2, N);
154             vec3 dp1perp = cross(N, dp1);
155             vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
156             vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
157             float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
158             return mat3(T * invmax, B * invmax, N);
159         }
160         
161         float shadowLookup(in sampler2DArrayShadow depths, in float layer, in vec4 coord, in vec2 offset)
162         {
163             float texelSize = 1.0 / shadowTextureSize;
164             vec2 v = offset * texelSize * coord.w;
165             vec4 c = (coord + vec4(v.x, v.y, 0.0, 0.0)) / coord.w;
166             c.w = c.z;
167             c.z = layer;
168             float s = texture(depths, c);
169             return s;
170         }
171         
172         float pcf(in sampler2DArrayShadow depths, in float layer, in vec4 coord, in float radius, in float yshift)
173         {
174             float s = 0.0;
175             float x, y;
176 	        for (y = -radius ; y < radius ; y += 1.0)
177 	        for (x = -radius ; x < radius ; x += 1.0)
178             {
179 	            s += shadowLookup(depths, layer, coord, vec2(x, y + yshift));
180             }
181 	        s /= radius * radius * 4.0;
182             return s;
183         }
184         
185         float weight(in vec4 tc)
186         {
187             vec2 proj = vec2(tc.x / tc.w, tc.y / tc.w);
188             proj = (1.0 - abs(proj * 2.0 - 1.0)) * 8.0;
189             proj = clamp(proj, 0.0, 1.0);
190             return min(proj.x, proj.y);
191         }
192         
193         void main()
194         {
195             vec3 N = normalize(eyeNormal);
196             vec3 Nw = normalize(worldNormal);
197             vec3 E = normalize(-eyePosition);
198             
199             mat3 TBN = cotangentFrame(N, eyePosition, texCoord);
200             vec3 tE = normalize(E * TBN);
201             
202             vec3 cameraPosition = invViewMatrix[3].xyz;
203             
204             float slope = pow(dot(Nw, vec3(0.0, 1.0, 0.0)), 5.0);
205             
206             // Normal mapping
207             vec3 tN1 = normalize(texture(grassNormalTexture, texCoord).rgb * 2.0 - 1.0);
208             tN1.y = -tN1.y;
209             vec3 N1 = normalize(TBN * tN1);
210 
211             vec3 tN2 = normalize(texture(mountsNormalTexture, texCoord).rgb * 2.0 - 1.0);
212             tN2.y = -tN2.y;
213             vec3 N2 = normalize(TBN * tN1);
214             
215             // Roughness to blinn-phong specular power
216             float gloss = 1.0 - roughness;
217             float shininess = gloss * 128.0;
218             
219             // Sun light
220             float sunDiffBrightness1 = clamp(dot(N1, sunDirection), 0.0, 1.0);
221             float sunDiffBrightness2 = clamp(dot(N2, sunDirection), 0.0, 1.0);
222             
223             // Calculate shadow from 3 cascades
224             float s1, s2, s3;
225             if (useShadows)
226             {
227                 s1 = pcf(shadowTextureArray, 0.0, shadowCoord1, 3.0, 0.0);
228                 s2 = pcf(shadowTextureArray, 1.0, shadowCoord2, 2.0, 0.0);
229                 s3 = pcf(shadowTextureArray, 2.0, shadowCoord3, 1.0, 0.0);
230                 float w1 = weight(shadowCoord1);
231                 float w2 = weight(shadowCoord2);
232                 float w3 = weight(shadowCoord3);
233                 s3 = mix(1.0, s3, w3); 
234                 s2 = mix(s3, s2, w2);
235                 s1 = mix(s2, s1, w1); // s1 stores resulting shadow value
236             }
237             else
238             {
239                 s1 = 1.0f;
240             }
241             
242             vec3 R = reflect(E, N);
243             
244             // Fetch light cluster slice
245             vec2 clusterCoord = (worldPosition.xz - cameraPosition.xz) * invLightDomainSize + 0.5;
246             uint clusterIndex = texture(lightClusterTexture, clusterCoord).r;
247             uint offset = (clusterIndex << 16) >> 16;
248             uint size = (clusterIndex >> 16);
249             
250             vec3 pointDiffSum = vec3(0.0, 0.0, 0.0);
251             vec3 pointSpecSum = vec3(0.0, 0.0, 0.0);
252             for (uint i = 0u; i < size; i++)
253             {
254                 // Read light data
255                 uint u = texelFetch(lightIndexTexture, int(offset + i), 0).r;
256                 vec3 lightPos = texelFetch(lightsTexture, ivec2(u, 0), 0).xyz; 
257                 vec3 lightColor = texelFetch(lightsTexture, ivec2(u, 1), 0).xyz; 
258                 vec3 lightProps = texelFetch(lightsTexture, ivec2(u, 2), 0).xyz;
259                 float lightRadius = lightProps.x;
260                 float lightAreaRadius = lightProps.y;
261                 float lightEnergy = lightProps.z;
262                 
263                 vec3 lightPosEye = (viewMatrix * vec4(lightPos, 1.0)).xyz;
264                 
265                 vec3 positionToLightSource = lightPosEye - eyePosition;
266                 float distanceToLight = length(positionToLightSource);
267                 vec3 directionToLight = normalize(positionToLightSource);                
268                 float attenuation = clamp(1.0 - (distanceToLight / lightRadius), 0.0, 1.0) * lightEnergy;
269                 
270                 float diff = clamp(dot(N, directionToLight), 0.0, 1.0);
271                 pointDiffSum += lightColor * diff * attenuation;
272             }
273             
274             // Fog
275             float fogDistance = gl_FragCoord.z / gl_FragCoord.w;
276             float fogFactor = clamp((fogEnd - fogDistance) / (fogEnd - fogStart), 0.0, 1.0);
277             
278             vec3 colorGrass = texture(grassTexture, texCoord).rgb; //vec3(0.0, 0.5, 0.0);
279             vec3 colorMounts = texture(mountsTexture, texCoord).rgb; 
280             vec3 diffColor = mix(colorMounts, colorGrass, slope);
281             
282             float diffuse = mix(sunDiffBrightness2, sunDiffBrightness1, slope);
283             
284             vec3 objColor = diffColor * (environmentColor.rgb + pointDiffSum + sunColor * diffuse * s1);
285             
286             vec3 fragColor = mix(fogColor.rgb, objColor, fogFactor);
287             
288             frag_color = vec4(fragColor, 1.0);
289         }
290     };
291     
292     override string vertexShaderSrc() {return vsText;}
293     override string fragmentShaderSrc() {return fsText;}
294 
295     GLint viewMatrixLoc;
296     GLint modelViewMatrixLoc;
297     GLint projectionMatrixLoc;
298     GLint normalMatrixLoc;
299     GLint invViewMatrixLoc;
300     
301     GLint environmentColorLoc;
302     GLint sunDirectionLoc;
303     GLint sunColorLoc;
304     GLint fogStartLoc;
305     GLint fogEndLoc;
306     GLint fogColorLoc;
307     
308     GLint shadowMatrix1Loc;
309     GLint shadowMatrix2Loc; 
310     GLint shadowMatrix3Loc;
311     GLint shadowTextureArrayLoc;
312     GLint shadowTextureSizeLoc;
313     GLint useShadowsLoc;
314     
315     GLint grassTextureLoc;
316     GLint mountsTextureLoc;
317     GLint grassNormalTextureLoc;
318     
319     GLint invLightDomainSizeLoc;
320     GLint clusterTextureLoc;
321     GLint lightsTextureLoc;
322     GLint indexTextureLoc;
323     
324     ClusteredLightManager lightManager;
325     CascadedShadowMap shadowMap;
326     Matrix4x4f defaultShadowMat;
327     Vector3f defaultLightDir;
328     
329     this(ClusteredLightManager clm, Owner o)
330     {
331         super(o);
332         
333         lightManager = clm;
334         
335         viewMatrixLoc = glGetUniformLocation(shaderProgram, "viewMatrix");
336         modelViewMatrixLoc = glGetUniformLocation(shaderProgram, "modelViewMatrix");
337         projectionMatrixLoc = glGetUniformLocation(shaderProgram, "projectionMatrix");
338         normalMatrixLoc = glGetUniformLocation(shaderProgram, "normalMatrix");
339         invViewMatrixLoc = glGetUniformLocation(shaderProgram, "invViewMatrix");
340         
341         environmentColorLoc = glGetUniformLocation(shaderProgram, "environmentColor");
342         sunDirectionLoc = glGetUniformLocation(shaderProgram, "sunDirection");
343         sunColorLoc = glGetUniformLocation(shaderProgram, "sunColor");
344         fogStartLoc = glGetUniformLocation(shaderProgram, "fogStart");
345         fogEndLoc = glGetUniformLocation(shaderProgram, "fogEnd");
346         fogColorLoc = glGetUniformLocation(shaderProgram, "fogColor");
347         
348         shadowMatrix1Loc = glGetUniformLocation(shaderProgram, "shadowMatrix1");
349         shadowMatrix2Loc = glGetUniformLocation(shaderProgram, "shadowMatrix2");
350         shadowMatrix3Loc = glGetUniformLocation(shaderProgram, "shadowMatrix3");
351         shadowTextureArrayLoc = glGetUniformLocation(shaderProgram, "shadowTextureArray");
352         shadowTextureSizeLoc = glGetUniformLocation(shaderProgram, "shadowTextureSize");
353         useShadowsLoc = glGetUniformLocation(shaderProgram, "useShadows");
354             
355         grassTextureLoc = glGetUniformLocation(shaderProgram, "grassTexture");
356         mountsTextureLoc = glGetUniformLocation(shaderProgram, "mountsTexture");
357         grassNormalTextureLoc = glGetUniformLocation(shaderProgram, "grassNormalTexture");
358         
359         clusterTextureLoc = glGetUniformLocation(shaderProgram, "lightClusterTexture");
360         invLightDomainSizeLoc = glGetUniformLocation(shaderProgram, "invLightDomainSize");
361         lightsTextureLoc = glGetUniformLocation(shaderProgram, "lightsTexture");
362         indexTextureLoc = glGetUniformLocation(shaderProgram, "lightIndexTexture");
363     }
364     
365     override void bind(GenericMaterial mat, RenderingContext* rc)
366     {
367         auto igrass = "grass" in mat.inputs;
368         if (igrass is null)
369             igrass = mat.setInput("grass", Color4f(0.0f, 0.5f, 0.0f, 1.0f));
370             
371         auto imounts = "mounts" in mat.inputs;
372         if (imounts is null)
373             imounts = mat.setInput("mounts", Color4f(0.2f, 0.2f, 0.2f, 1.0f));
374             
375         auto igrassNormal = "grassNormal" in mat.inputs;
376         if (igrassNormal is null)
377             igrassNormal = mat.setInput("grassNormal", Color4f(0.0f, 0.0f, 0.0f, 0.0f));
378             
379         bool fogEnabled = boolProp(mat, "fogEnabled");
380         bool shadowsEnabled = boolProp(mat, "shadowsEnabled");
381 
382         glUseProgram(shaderProgram);
383         
384         // Matrices
385         glUniformMatrix4fv(viewMatrixLoc, 1, GL_FALSE, rc.viewMatrix.arrayof.ptr);
386         glUniformMatrix4fv(modelViewMatrixLoc, 1, GL_FALSE, rc.modelViewMatrix.arrayof.ptr);
387         glUniformMatrix4fv(projectionMatrixLoc, 1, GL_FALSE, rc.projectionMatrix.arrayof.ptr);
388         glUniformMatrix4fv(normalMatrixLoc, 1, GL_FALSE, rc.normalMatrix.arrayof.ptr);
389         glUniformMatrix4fv(invViewMatrixLoc, 1, GL_FALSE, rc.invViewMatrix.arrayof.ptr);
390         
391         // Environment parameters
392         Color4f environmentColor = Color4f(0.0f, 0.0f, 0.0f, 1.0f);
393         Vector4f sunHGVector = Vector4f(0.0f, 1.0f, 0.0, 0.0f);
394         Vector3f sunColor = Vector3f(1.0f, 1.0f, 1.0f);
395         if (rc.environment)
396         {
397             environmentColor = rc.environment.ambientConstant;
398             sunHGVector = Vector4f(rc.environment.sunDirection);
399             sunHGVector.w = 0.0;
400             sunColor = rc.environment.sunColor;
401         }
402         glUniform4fv(environmentColorLoc, 1, environmentColor.arrayof.ptr);
403         Vector3f sunDirectionEye = sunHGVector * rc.viewMatrix;
404         glUniform3fv(sunDirectionLoc, 1, sunDirectionEye.arrayof.ptr);
405         glUniform3fv(sunColorLoc, 1, sunColor.arrayof.ptr);
406         Color4f fogColor = Color4f(0.0f, 0.0f, 0.0f, 1.0f);
407         float fogStart = float.max;
408         float fogEnd = float.max;
409         if (fogEnabled)
410         {
411             if (rc.environment)
412             {                
413                 fogColor = rc.environment.fogColor;
414                 fogStart = rc.environment.fogStart;
415                 fogEnd = rc.environment.fogEnd;
416             }
417         }
418         glUniform4fv(fogColorLoc, 1, fogColor.arrayof.ptr);
419         glUniform1f(fogStartLoc, fogStart);
420         glUniform1f(fogEndLoc, fogEnd);
421 
422         // Texture 0 - grass texture
423         if (igrass.texture is null)
424         {
425             Color4f color = Color4f(igrass.asVector4f);
426             igrass.texture = makeOnePixelTexture(mat, color);
427         }
428         glActiveTexture(GL_TEXTURE0);
429         igrass.texture.bind();
430         glUniform1i(grassTextureLoc, 0);
431         
432         // Texture 1 - mounts texture
433         if (imounts.texture is null)
434         {
435             Color4f color = Color4f(imounts.asVector4f);
436             imounts.texture = makeOnePixelTexture(mat, color);
437         }
438         glActiveTexture(GL_TEXTURE1);
439         imounts.texture.bind();
440         glUniform1i(mountsTextureLoc, 1);
441         
442         // Texture 2 - grass normal map
443         if (igrassNormal.texture is null)
444         {
445             Color4f color = Color4f(0.5f, 0.5f, 1.0f, 0.0f); // default normal pointing upwards
446             igrassNormal.texture = makeOnePixelTexture(mat, color);
447         }
448         glActiveTexture(GL_TEXTURE2);
449         igrassNormal.texture.bind();
450         glUniform1i(grassNormalTextureLoc, 2);
451         
452         // Texture 5 - shadow map cascades (3 layer texture array)
453         if (shadowMap && shadowsEnabled)
454         {
455             glActiveTexture(GL_TEXTURE5);
456             glBindTexture(GL_TEXTURE_2D_ARRAY, shadowMap.depthTexture);
457 
458             glUniform1i(shadowTextureArrayLoc, 5);
459             glUniform1f(shadowTextureSizeLoc, cast(float)shadowMap.size);
460             glUniformMatrix4fv(shadowMatrix1Loc, 1, 0, shadowMap.area1.shadowMatrix.arrayof.ptr);
461             glUniformMatrix4fv(shadowMatrix2Loc, 1, 0, shadowMap.area2.shadowMatrix.arrayof.ptr);
462             glUniformMatrix4fv(shadowMatrix3Loc, 1, 0, shadowMap.area3.shadowMatrix.arrayof.ptr);
463             glUniform1i(useShadowsLoc, 1);
464             
465             // TODO: shadowFilter
466         }
467         else
468         {        
469             glUniformMatrix4fv(shadowMatrix1Loc, 1, 0, defaultShadowMat.arrayof.ptr);
470             glUniformMatrix4fv(shadowMatrix2Loc, 1, 0, defaultShadowMat.arrayof.ptr);
471             glUniformMatrix4fv(shadowMatrix3Loc, 1, 0, defaultShadowMat.arrayof.ptr);
472             glUniform1i(useShadowsLoc, 0);
473         }
474         
475         // Texture 6 - light clusters
476         glActiveTexture(GL_TEXTURE6);
477         lightManager.bindClusterTexture();
478         glUniform1i(clusterTextureLoc, 6);
479         glUniform1f(invLightDomainSizeLoc, lightManager.invSceneSize);
480         
481         // Texture 7 - light data
482         glActiveTexture(GL_TEXTURE7);
483         lightManager.bindLightTexture();
484         glUniform1i(lightsTextureLoc, 7);
485         
486         // Texture 8 - light indices per cluster
487         glActiveTexture(GL_TEXTURE8);
488         lightManager.bindIndexTexture();
489         glUniform1i(indexTextureLoc, 8);
490     }
491     
492     override void unbind(GenericMaterial mat, RenderingContext* rc)
493     {
494         auto igrass = "grass" in mat.inputs;
495         auto imounts = "mounts" in mat.inputs;
496         auto igrassNormal = "grassNormal" in mat.inputs;
497         
498         glActiveTexture(GL_TEXTURE0);
499         igrass.texture.unbind();
500         
501         glActiveTexture(GL_TEXTURE1);
502         imounts.texture.unbind();
503         
504         glActiveTexture(GL_TEXTURE2);
505         igrassNormal.texture.unbind();
506         
507         glActiveTexture(GL_TEXTURE5);
508         glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
509         
510         glActiveTexture(GL_TEXTURE6);
511         lightManager.unbindClusterTexture();
512         
513         glActiveTexture(GL_TEXTURE7);
514         lightManager.unbindLightTexture();
515         
516         glActiveTexture(GL_TEXTURE8);
517         lightManager.unbindIndexTexture();
518         
519         glActiveTexture(GL_TEXTURE0);
520         
521         glUseProgram(0);
522     }
523 }