1 /*
2 Copyright (c) 2017-2019 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("roughness", 0.5f);
172         setInput("metallic", 0.0f);
173         setInput("specularity", 1.0f);
174         setInput("translucency", 0.0f);
175         setInput("normal", Vector3f(0.0f, 0.0f, 1.0f));
176         setInput("height", 0.0f);
177         setInput("parallax", ParallaxNone);
178         setInput("parallaxScale", 0.03f);
179         setInput("parallaxBias", -0.01f);
180         setInput("shadowsEnabled", true);
181         setInput("shadowFilter", ShadowFilterPCF);
182         setInput("fogEnabled", true);
183         setInput("blending", Opaque);
184         setInput("culling", true);
185         setInput("colorWrite", true);
186         setInput("depthWrite", true);
187         setInput("particleColor", Color4f(1.0f, 1.0f, 1.0f, 1.0f));
188         setInput("particleSphericalNormal", false);
189         setInput("textureScale", Vector2f(1.0f, 1.0f));
190         setInput("sphericalNormal", false);
191         
192         setInput("outputColor", true);
193         setInput("outputNormal", true);
194         setInput("outputPBR", true);
195         setInput("outputEmission", true);
196         
197         setInput("diffuse2", Color4f(0.8f, 0.8f, 0.8f, 1.0f));
198         setInput("diffuse3", Color4f(0.8f, 0.8f, 0.8f, 1.0f));
199         setInput("diffuse4", Color4f(0.8f, 0.8f, 0.8f, 1.0f));
200 
201         setInput("normal2", Vector3f(0.0f, 0.0f, 1.0f));
202         setInput("normal3", Vector3f(0.0f, 0.0f, 1.0f));
203         setInput("normal4", Vector3f(0.0f, 0.0f, 1.0f));
204 
205         setInput("height2", 0.0f);
206         setInput("height3", 0.0f);
207         setInput("height4", 0.0f);
208 
209         setInput("roughness2", 0.5f);
210         setInput("roughness3", 0.5f);
211         setInput("roughness4", 0.5f);
212 
213         setInput("metallic2", 0.0f);
214         setInput("metallic3", 0.0f);
215         setInput("metallic4", 0.0f);
216 
217         setInput("textureScale2", Vector2f(1.0f, 1.0f));
218         setInput("textureScale3", Vector2f(1.0f, 1.0f));
219         setInput("textureScale4", Vector2f(1.0f, 1.0f));
220 
221         setInput("splatmap1", Color4f(1.0f, 1.0f, 1.0f, 1.0f));
222         setInput("splatmap2", Color4f(0.0f, 0.0f, 0.0f, 0.0f));
223         setInput("splatmap3", Color4f(0.0f, 0.0f, 0.0f, 0.0f));
224         setInput("splatmap4", Color4f(0.0f, 0.0f, 0.0f, 0.0f));
225     }
226 
227     final auto opDispatch(string name)() @property
228     {
229         return (name in inputs);
230     }
231 
232     final void opDispatch(string name, T)(T value) @property
233     {
234         setInput(name, value);
235     }
236 
237     final MaterialInput* setInput(T)(string name, T value)
238     {
239         MaterialInput input;
240         static if (is(T == bool))
241         {
242             input.type = MaterialInputType.Bool;
243             input.asBool = value;
244         }
245         else static if (is(T == int))
246         {
247             input.type = MaterialInputType.Integer;
248             input.asInteger = value;
249         }
250         else static if (is(T == float) || is(T == double))
251         {
252             input.type = MaterialInputType.Float;
253             input.asFloat = value;
254         }
255         else static if (is(T == Vector2f))
256         {
257             input.type = MaterialInputType.Vec2;
258             input.asVector2f = value;
259         }
260         else static if (is(T == Vector3f))
261         {
262             input.type = MaterialInputType.Vec3;
263             input.asVector3f = value;
264         }
265         else static if (is(T == Vector4f))
266         {
267             input.type = MaterialInputType.Vec4;
268             input.asVector4f = value;
269         }
270         else static if (is(T == Color4f))
271         {
272             input.type = MaterialInputType.Vec4;
273             input.asVector4f = value;
274         }
275         else static if (is(T == Texture) || is(T == Cubemap))
276         {
277             input.texture = cast(Texture)value;
278             if (value.format == GL_RED)
279                 input.type = MaterialInputType.Float;
280             else if (value.format == GL_RG)
281                 input.type = MaterialInputType.Vec2;
282             else if (value.format == GL_RGB)
283                 input.type = MaterialInputType.Vec3;
284             else if (value.format == GL_RGBA)
285                 input.type = MaterialInputType.Vec4;
286         }
287         else static assert("Unsupported type " ~ T.stringof);
288 
289         inputs[name] = input;
290         return (name in inputs);
291     }
292 
293     final bool boolProp(string prop)
294     {
295         auto p = prop in inputs;
296         bool res = false;
297         if (p.type == MaterialInputType.Bool ||
298             p.type == MaterialInputType.Integer)
299         {
300             res = p.asBool;
301         }
302         return res;
303     }
304 
305     final int intProp(string prop)
306     {
307         auto p = prop in inputs;
308         int res = 0;
309         if (p.type == MaterialInputType.Bool ||
310             p.type == MaterialInputType.Integer)
311         {
312             res = p.asInteger;
313         }
314         else if (p.type == MaterialInputType.Float)
315         {
316             res = cast(int)p.asFloat;
317         }
318         return res;
319     }
320     
321     final Texture makeTexture(Color4f rgb)
322     {
323         SuperImage rgbaImg = New!UnmanagedImageRGBA8(8, 8);
324 
325         foreach(y; 0..rgbaImg.height)
326         foreach(x; 0..rgbaImg.width)
327         {
328             rgbaImg[x, y] = rgb;
329         }
330 
331         auto tex = New!Texture(rgbaImg, this);
332         return tex;
333     }
334 
335     final Texture makeTexture(Color4f rgb, Texture alpha)
336     {
337         SuperImage rgbaImg = New!UnmanagedImageRGBA8(alpha.width, alpha.height);
338 
339         foreach(y; 0..alpha.height)
340         foreach(x; 0..alpha.width)
341         {
342             Color4f col = rgb;
343             col.a = alpha.image[x, y].r;
344             rgbaImg[x, y] = col;
345         }
346 
347         auto tex = New!Texture(rgbaImg, this);
348         return tex;
349     }
350 
351     final Texture makeTexture(Texture rgb, float alpha)
352     {
353         SuperImage rgbaImg = New!UnmanagedImageRGBA8(rgb.width, rgb.height);
354 
355         foreach(y; 0..rgb.height)
356         foreach(x; 0..rgb.width)
357         {
358             Color4f col = rgb.image[x, y];
359             col.a = alpha;
360             rgbaImg[x, y] = col;
361         }
362 
363         auto tex = New!Texture(rgbaImg, this);
364         return tex;
365     }
366 
367     final Texture makeTexture(Texture rgb, Texture alpha)
368     {
369         uint width = max(rgb.width, alpha.width);
370         uint height = max(rgb.height, alpha.height);
371 
372         SuperImage rgbaImg = New!UnmanagedImageRGBA8(width, height);
373 
374         foreach(y; 0..rgbaImg.height)
375         foreach(x; 0..rgbaImg.width)
376         {
377             float u = cast(float)x / cast(float)width;
378             float v = cast(float)y / cast(float)height;
379 
380             Color4f col = rgb.sample(u, v);
381             col.a = alpha.sample(u, v).r;
382 
383             rgbaImg[x, y] = col;
384         }
385 
386         auto tex = New!Texture(rgbaImg, this);
387         return tex;
388     }
389 
390     final Texture makeTexture(MaterialInput r, MaterialInput g, MaterialInput b, MaterialInput a)
391     {
392         uint width = 8;
393         uint height = 8;
394 
395         if (r.texture !is null)
396         {
397             width = max(width, r.texture.width);
398             height = max(height, r.texture.height);
399         }
400 
401         if (g.texture !is null)
402         {
403             width = max(width, g.texture.width);
404             height = max(height, g.texture.height);
405         }
406 
407         if (b.texture !is null)
408         {
409             width = max(width, b.texture.width);
410             height = max(height, b.texture.height);
411         }
412 
413         if (a.texture !is null)
414         {
415             width = max(width, a.texture.width);
416             height = max(height, a.texture.height);
417         }
418 
419         SuperImage img = New!UnmanagedImageRGBA8(width, height);
420 
421         foreach(y; 0..img.height)
422         foreach(x; 0..img.width)
423         {
424             Color4f col = Color4f(0, 0, 0, 0);
425 
426             float u = cast(float)x / cast(float)img.width;
427             float v = cast(float)y / cast(float)img.height;
428 
429             col.r = r.sample(u, v).r;
430             col.g = g.sample(u, v).r;
431             col.b = b.sample(u, v).r;
432             col.a = a.sample(u, v).r;
433 
434             img[x, y] = col;
435         }
436 
437         auto tex = New!Texture(img, this);
438         return tex;
439     }
440 
441     bool isTransparent()
442     {
443         auto iblending = "blending" in inputs;
444         int b = iblending.asInteger;
445         return (b == Transparent || b == Additive);
446     }
447 
448     void bind(GraphicsState* state)
449     {
450         auto iblending = "blending" in inputs;
451         auto iculling = "culling" in inputs;
452         auto icolorWrite = "colorWrite" in inputs;
453         auto idepthWrite = "depthWrite" in inputs;
454 
455         if (iblending.asInteger == Transparent)
456         {
457             glEnablei(GL_BLEND, 0);
458             glEnablei(GL_BLEND, 1);
459             glEnablei(GL_BLEND, 2);
460             glBlendFuncSeparatei(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
461             glBlendFuncSeparatei(1, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
462             glBlendFuncSeparatei(2, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
463         }
464         else if (iblending.asInteger == Additive)
465         {
466             glEnablei(GL_BLEND, 0);
467             glEnablei(GL_BLEND, 1);
468             glEnablei(GL_BLEND, 2);
469             glBlendFunci(0, GL_SRC_ALPHA, GL_ONE);
470             glBlendFunci(1, GL_SRC_ALPHA, GL_ONE);
471             glBlendFunci(2, GL_SRC_ALPHA, GL_ONE);
472         }
473 
474         if (iculling.asBool && state.culling)
475         {
476             glEnable(GL_CULL_FACE);
477         }
478         else
479         {
480             glDisable(GL_CULL_FACE);
481         }
482 
483         if (!icolorWrite.asBool || !state.colorMask)
484         {
485             glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
486         }
487 
488         if (!idepthWrite.asBool || !state.depthMask)
489         {
490             glDepthMask(GL_FALSE);
491         }
492 
493         state.material = this;
494     }
495 
496     void unbind(GraphicsState* state)
497     {
498         state.material = null;
499 
500         glDepthMask(GL_TRUE);
501         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
502 
503         glDisable(GL_CULL_FACE);
504 
505         glDisablei(GL_BLEND, 0);
506         glDisablei(GL_BLEND, 1);
507         glDisablei(GL_BLEND, 2);
508     }
509 }