1 /*
2 Copyright (c) 2017-2020 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.material;
29 
30 import std.traits;
31 import std.math;
32 import std.algorithm;
33 
34 import dlib.core.memory;
35 import dlib.core.ownership;
36 import dlib.math.vector;
37 import dlib.image.color;
38 import dlib.image.image;
39 import dlib.image.unmanaged;
40 import dlib.container.dict;
41 
42 import dagon.core.bindings;
43 import dagon.graphics.texture;
44 import dagon.graphics.cubemap;
45 import dagon.graphics.state;
46 import dagon.graphics.shader;
47 import dagon.graphics.light;
48 
49 enum
50 {
51     CBlack = Color4f(0.0f, 0.0f, 0.0f, 1.0f),
52     CWhite = Color4f(1.0f, 1.0f, 1.0f, 1.0f),
53     CRed = Color4f(1.0f, 0.0f, 0.0f, 1.0f),
54     COrange = Color4f(1.0f, 0.5f, 0.0f, 1.0f),
55     CYellow = Color4f(1.0f, 1.0f, 0.0f, 1.0f),
56     CGreen = Color4f(0.0f, 1.0f, 0.0f, 1.0f),
57     CCyan = Color4f(0.0f, 1.0f, 1.0f, 1.0f),
58     CBlue = Color4f(0.0f, 0.0f, 1.0f, 1.0f),
59     CPurple = Color4f(0.5f, 0.0f, 1.0f, 1.0f),
60     CMagenta = Color4f(1.0f, 0.0f, 1.0f, 1.0f)
61 }
62 
63 enum int None = 0;
64 
65 enum int ShadowFilterNone = 0;
66 enum int ShadowFilterPCF = 1;
67 
68 enum int ParallaxNone = 0;
69 enum int ParallaxSimple = 1;
70 enum int ParallaxOcclusionMapping = 2;
71 
72 enum int Opaque = 0;
73 enum int Transparent = 1;
74 enum int Additive = 2;
75 
76 enum MaterialInputType
77 {
78     Undefined,
79     Bool,
80     Integer,
81     Float,
82     Vec2,
83     Vec3,
84     Vec4
85 }
86 
87 struct MaterialInput
88 {
89     MaterialInputType type;
90     union
91     {
92         bool asBool;
93         int asInteger;
94         float asFloat;
95         Vector2f asVector2f;
96         Vector3f asVector3f;
97         Vector4f asVector4f;
98     }
99     Texture texture;
100 
101     float getNumericValue()
102     {
103         float res;
104         if (type == MaterialInputType.Bool ||
105             type == MaterialInputType.Integer)
106         {
107             res = asInteger;
108         }
109         else if (type == MaterialInputType.Float)
110         {
111             res = asFloat;
112         }
113         return res;
114     }
115 
116     Color4f sample(float u, float v)
117     {
118         if (texture !is null)
119             return texture.sample(u, v);
120         else if (type == MaterialInputType.Vec4)
121             return Color4f(asVector4f);
122         else if (type == MaterialInputType.Vec3)
123             return Color4f(asVector3f.x, asVector3f.y, asVector3f.z, 1.0f);
124         else if (type == MaterialInputType.Vec2)
125             return Color4f(asVector2f.x, asVector2f.y, 1.0f, 1.0f);
126         else if (type == MaterialInputType.Float)
127             return Color4f(asFloat, 1.0f, 1.0f, 1.0f);
128         else if (type == MaterialInputType.Bool ||
129                  type == MaterialInputType.Integer)
130             return Color4f(cast(float)asInteger, 1.0f, 1.0f, 1.0f);
131         else
132             return Color4f(0.0f, 0.0f, 0.0f, 0.0f);
133     }
134 }
135 
136 MaterialInput materialInput(float v)
137 {
138     MaterialInput mi;
139     mi.asFloat = v;
140     mi.type = MaterialInputType.Float;
141     return mi;
142 }
143 
144 class Material: Owner
145 {
146     Dict!(MaterialInput, string) inputs;
147     Shader shader;
148     Light sun;
149 
150     this(Owner o)
151     {
152         super(o);
153 
154         inputs = New!(Dict!(MaterialInput, string));
155         setStandardInputs();
156     }
157 
158     ~this()
159     {
160         Delete(inputs);
161     }
162 
163     void setStandardInputs()
164     {
165         setInput("diffuse", Color4f(0.8f, 0.8f, 0.8f, 1.0f));
166         setInput("specular", Color4f(1.0f, 1.0f, 1.0f, 1.0f));
167         setInput("shadeless", false);
168         setInput("emission", Color4f(0.0f, 0.0f, 0.0f, 1.0f));
169         setInput("energy", 1.0f);
170         setInput("transparency", 1.0f);
171         setInput("clipThreshold", 0.5f);
172         setInput("roughness", 0.5f);
173         setInput("metallic", 0.0f);
174         setInput("roughnessMetallic", 0.0f);
175         setInput("specularity", 1.0f);
176         setInput("translucency", 0.0f);
177         setInput("normal", Vector3f(0.0f, 0.0f, 1.0f));
178         setInput("invertNormalY", true);
179         setInput("height", 0.0f);
180         setInput("parallax", ParallaxNone);
181         setInput("parallaxScale", 0.03f);
182         setInput("parallaxBias", -0.01f);
183         setInput("shadowsEnabled", true);
184         setInput("shadowFilter", ShadowFilterPCF);
185         setInput("fogEnabled", true);
186         setInput("blending", Opaque);
187         setInput("culling", true);
188         setInput("colorWrite", true);
189         setInput("depthWrite", true);
190         setInput("particleColor", Color4f(1.0f, 1.0f, 1.0f, 1.0f));
191         setInput("particleSphericalNormal", false);
192         setInput("textureScale", Vector2f(1.0f, 1.0f));
193         setInput("sphericalNormal", false);
194         
195         setInput("outputColor", true);
196         setInput("outputNormal", true);
197         setInput("outputPBR", true);
198         setInput("outputEmission", true);
199         
200         setInput("diffuse2", Color4f(0.8f, 0.8f, 0.8f, 1.0f));
201         setInput("diffuse3", Color4f(0.8f, 0.8f, 0.8f, 1.0f));
202         setInput("diffuse4", Color4f(0.8f, 0.8f, 0.8f, 1.0f));
203 
204         setInput("normal2", Vector3f(0.0f, 0.0f, 1.0f));
205         setInput("normal3", Vector3f(0.0f, 0.0f, 1.0f));
206         setInput("normal4", Vector3f(0.0f, 0.0f, 1.0f));
207 
208         setInput("height2", 0.0f);
209         setInput("height3", 0.0f);
210         setInput("height4", 0.0f);
211 
212         setInput("roughness2", 0.5f);
213         setInput("roughness3", 0.5f);
214         setInput("roughness4", 0.5f);
215 
216         setInput("metallic2", 0.0f);
217         setInput("metallic3", 0.0f);
218         setInput("metallic4", 0.0f);
219 
220         setInput("textureScale2", Vector2f(1.0f, 1.0f));
221         setInput("textureScale3", Vector2f(1.0f, 1.0f));
222         setInput("textureScale4", Vector2f(1.0f, 1.0f));
223 
224         setInput("splatmap1", Color4f(1.0f, 1.0f, 1.0f, 1.0f));
225         setInput("splatmap2", Color4f(0.0f, 0.0f, 0.0f, 0.0f));
226         setInput("splatmap3", Color4f(0.0f, 0.0f, 0.0f, 0.0f));
227         setInput("splatmap4", Color4f(0.0f, 0.0f, 0.0f, 0.0f));
228     }
229 
230     final auto opDispatch(string name)() @property
231     {
232         return (name in inputs);
233     }
234 
235     final void opDispatch(string name, T)(T value) @property
236     {
237         setInput(name, value);
238     }
239 
240     final MaterialInput* setInput(T)(string name, T value)
241     {
242         MaterialInput input;
243         static if (is(T == bool))
244         {
245             input.type = MaterialInputType.Bool;
246             input.asBool = value;
247         }
248         else static if (is(T == int))
249         {
250             input.type = MaterialInputType.Integer;
251             input.asInteger = value;
252         }
253         else static if (is(T == float) || is(T == double))
254         {
255             input.type = MaterialInputType.Float;
256             input.asFloat = value;
257         }
258         else static if (is(T == Vector2f))
259         {
260             input.type = MaterialInputType.Vec2;
261             input.asVector2f = value;
262         }
263         else static if (is(T == Vector3f))
264         {
265             input.type = MaterialInputType.Vec3;
266             input.asVector3f = value;
267         }
268         else static if (is(T == Vector4f))
269         {
270             input.type = MaterialInputType.Vec4;
271             input.asVector4f = value;
272         }
273         else static if (is(T == Color4f))
274         {
275             input.type = MaterialInputType.Vec4;
276             input.asVector4f = value;
277         }
278         else static if (is(T == Texture) || is(T == Cubemap))
279         {
280             input.texture = cast(Texture)value;
281             if (value.format == GL_RED)
282                 input.type = MaterialInputType.Float;
283             else if (value.format == GL_RG)
284                 input.type = MaterialInputType.Vec2;
285             else if (value.format == GL_RGB)
286                 input.type = MaterialInputType.Vec3;
287             else if (value.format == GL_RGBA)
288                 input.type = MaterialInputType.Vec4;
289         }
290         else static assert("Unsupported type " ~ T.stringof);
291 
292         inputs[name] = input;
293         return (name in inputs);
294     }
295 
296     final bool boolProp(string prop)
297     {
298         auto p = prop in inputs;
299         bool res = false;
300         if (p.type == MaterialInputType.Bool ||
301             p.type == MaterialInputType.Integer)
302         {
303             res = p.asBool;
304         }
305         return res;
306     }
307 
308     final int intProp(string prop)
309     {
310         auto p = prop in inputs;
311         int res = 0;
312         if (p.type == MaterialInputType.Bool ||
313             p.type == MaterialInputType.Integer)
314         {
315             res = p.asInteger;
316         }
317         else if (p.type == MaterialInputType.Float)
318         {
319             res = cast(int)p.asFloat;
320         }
321         return res;
322     }
323     
324     final Texture makeTexture(Color4f rgb)
325     {
326         SuperImage rgbaImg = New!UnmanagedImageRGBA8(8, 8);
327 
328         foreach(y; 0..rgbaImg.height)
329         foreach(x; 0..rgbaImg.width)
330         {
331             rgbaImg[x, y] = rgb;
332         }
333 
334         auto tex = New!Texture(rgbaImg, this);
335         return tex;
336     }
337 
338     final Texture makeTexture(Color4f rgb, Texture alpha)
339     {
340         SuperImage rgbaImg = New!UnmanagedImageRGBA8(alpha.width, alpha.height);
341 
342         foreach(y; 0..alpha.height)
343         foreach(x; 0..alpha.width)
344         {
345             Color4f col = rgb;
346             col.a = alpha.image[x, y].r;
347             rgbaImg[x, y] = col;
348         }
349 
350         auto tex = New!Texture(rgbaImg, this);
351         return tex;
352     }
353 
354     final Texture makeTexture(Texture rgb, float alpha)
355     {
356         SuperImage rgbaImg = New!UnmanagedImageRGBA8(rgb.width, rgb.height);
357 
358         foreach(y; 0..rgb.height)
359         foreach(x; 0..rgb.width)
360         {
361             Color4f col = rgb.image[x, y];
362             col.a = alpha;
363             rgbaImg[x, y] = col;
364         }
365 
366         auto tex = New!Texture(rgbaImg, this);
367         return tex;
368     }
369 
370     final Texture makeTexture(Texture rgb, Texture alpha)
371     {
372         uint width = max(rgb.width, alpha.width);
373         uint height = max(rgb.height, alpha.height);
374 
375         SuperImage rgbaImg = New!UnmanagedImageRGBA8(width, height);
376 
377         foreach(y; 0..rgbaImg.height)
378         foreach(x; 0..rgbaImg.width)
379         {
380             float u = cast(float)x / cast(float)width;
381             float v = cast(float)y / cast(float)height;
382 
383             Color4f col = rgb.sample(u, v);
384             col.a = alpha.sample(u, v).r;
385 
386             rgbaImg[x, y] = col;
387         }
388 
389         auto tex = New!Texture(rgbaImg, this);
390         return tex;
391     }
392 
393     final Texture makeTexture(MaterialInput r, MaterialInput g, MaterialInput b, MaterialInput a)
394     {
395         uint width = 8;
396         uint height = 8;
397 
398         if (r.texture !is null)
399         {
400             width = max(width, r.texture.width);
401             height = max(height, r.texture.height);
402         }
403 
404         if (g.texture !is null)
405         {
406             width = max(width, g.texture.width);
407             height = max(height, g.texture.height);
408         }
409 
410         if (b.texture !is null)
411         {
412             width = max(width, b.texture.width);
413             height = max(height, b.texture.height);
414         }
415 
416         if (a.texture !is null)
417         {
418             width = max(width, a.texture.width);
419             height = max(height, a.texture.height);
420         }
421 
422         SuperImage img = New!UnmanagedImageRGBA8(width, height);
423 
424         foreach(y; 0..img.height)
425         foreach(x; 0..img.width)
426         {
427             Color4f col = Color4f(0, 0, 0, 0);
428 
429             float u = cast(float)x / cast(float)img.width;
430             float v = cast(float)y / cast(float)img.height;
431 
432             col.r = r.sample(u, v).r;
433             col.g = g.sample(u, v).r;
434             col.b = b.sample(u, v).r;
435             col.a = a.sample(u, v).r;
436 
437             img[x, y] = col;
438         }
439 
440         auto tex = New!Texture(img, this);
441         return tex;
442     }
443     
444     /*
445     final Texture makeTexture(MaterialInput rg, MaterialInput b, MaterialInput a)
446     {
447         uint width = 8;
448         uint height = 8;
449 
450         if (rg.texture !is null)
451         {
452             width = max(width, rg.texture.width);
453             height = max(height, rg.texture.height);
454         }
455 
456         if (b.texture !is null)
457         {
458             width = max(width, b.texture.width);
459             height = max(height, b.texture.height);
460         }
461 
462         if (a.texture !is null)
463         {
464             width = max(width, a.texture.width);
465             height = max(height, a.texture.height);
466         }
467 
468         SuperImage img = New!UnmanagedImageRGBA8(width, height);
469 
470         foreach(y; 0..img.height)
471         foreach(x; 0..img.width)
472         {
473             Color4f col = Color4f(0, 0, 0, 0);
474 
475             float u = cast(float)x / cast(float)img.width;
476             float v = cast(float)y / cast(float)img.height;
477 
478             col.r = rg.sample(u, v).r;
479             col.g = rg.sample(u, v).g;
480             col.b = b.sample(u, v).r;
481             col.a = a.sample(u, v).r;
482 
483             img[x, y] = col;
484         }
485 
486         auto tex = New!Texture(img, this);
487         return tex;
488     }
489     */
490     
491     bool isTransparent()
492     {
493         auto iblending = "blending" in inputs;
494         int b = iblending.asInteger;
495         return (b == Transparent || b == Additive);
496     }
497 
498     void bind(GraphicsState* state)
499     {
500         auto iblending = "blending" in inputs;
501         auto iculling = "culling" in inputs;
502         auto icolorWrite = "colorWrite" in inputs;
503         auto idepthWrite = "depthWrite" in inputs;
504 
505         if (iblending.asInteger == Transparent)
506         {
507             glEnablei(GL_BLEND, 0);
508             glEnablei(GL_BLEND, 1);
509             glEnablei(GL_BLEND, 2);
510             glBlendFuncSeparatei(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
511             glBlendFuncSeparatei(1, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
512             glBlendFuncSeparatei(2, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
513         }
514         else if (iblending.asInteger == Additive)
515         {
516             glEnablei(GL_BLEND, 0);
517             glEnablei(GL_BLEND, 1);
518             glEnablei(GL_BLEND, 2);
519             glBlendFunci(0, GL_SRC_ALPHA, GL_ONE);
520             glBlendFunci(1, GL_SRC_ALPHA, GL_ONE);
521             glBlendFunci(2, GL_SRC_ALPHA, GL_ONE);
522         }
523 
524         if (iculling.asBool && state.culling)
525         {
526             glEnable(GL_CULL_FACE);
527         }
528         else
529         {
530             glDisable(GL_CULL_FACE);
531         }
532 
533         if (!icolorWrite.asBool || !state.colorMask)
534         {
535             glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
536         }
537 
538         if (!idepthWrite.asBool || !state.depthMask)
539         {
540             glDepthMask(GL_FALSE);
541         }
542 
543         state.material = this;
544     }
545 
546     void unbind(GraphicsState* state)
547     {
548         state.material = null;
549 
550         glDepthMask(GL_TRUE);
551         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
552 
553         glDisable(GL_CULL_FACE);
554 
555         glDisablei(GL_BLEND, 0);
556         glDisablei(GL_BLEND, 1);
557         glDisablei(GL_BLEND, 2);
558     }
559 }