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     bool isTransparent()
445     {
446         auto iblending = "blending" in inputs;
447         int b = iblending.asInteger;
448         return (b == Transparent || b == Additive);
449     }
450 
451     void bind(GraphicsState* state)
452     {
453         auto iblending = "blending" in inputs;
454         auto iculling = "culling" in inputs;
455         auto icolorWrite = "colorWrite" in inputs;
456         auto idepthWrite = "depthWrite" in inputs;
457 
458         if (iblending.asInteger == Transparent)
459         {
460             glEnablei(GL_BLEND, 0);
461             glEnablei(GL_BLEND, 1);
462             glEnablei(GL_BLEND, 2);
463             glEnablei(GL_BLEND, 3);
464             glEnablei(GL_BLEND, 4);
465             glBlendFuncSeparatei(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
466             glBlendFuncSeparatei(1, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
467             glBlendFuncSeparatei(2, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
468             glBlendFuncSeparatei(3, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
469             glBlendFuncSeparatei(4, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
470         }
471         else if (iblending.asInteger == Additive)
472         {
473             glEnablei(GL_BLEND, 0);
474             glEnablei(GL_BLEND, 1);
475             glEnablei(GL_BLEND, 2);
476             glEnablei(GL_BLEND, 3);
477             glEnablei(GL_BLEND, 4);
478             glBlendFunci(0, GL_SRC_ALPHA, GL_ONE);
479             glBlendFunci(1, GL_SRC_ALPHA, GL_ONE);
480             glBlendFunci(2, GL_SRC_ALPHA, GL_ONE);
481             glBlendFunci(3, GL_SRC_ALPHA, GL_ONE);
482             glBlendFunci(4, GL_SRC_ALPHA, GL_ONE);
483         }
484 
485         if (iculling.asBool && state.culling)
486         {
487             glEnable(GL_CULL_FACE);
488         }
489         else
490         {
491             glDisable(GL_CULL_FACE);
492         }
493 
494         if (!icolorWrite.asBool || !state.colorMask)
495         {
496             glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
497         }
498 
499         if (!idepthWrite.asBool || !state.depthMask)
500         {
501             glDepthMask(GL_FALSE);
502         }
503 
504         state.material = this;
505     }
506 
507     void unbind(GraphicsState* state)
508     {
509         state.material = null;
510 
511         glDepthMask(GL_TRUE);
512         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
513 
514         glDisable(GL_CULL_FACE);
515 
516         glDisablei(GL_BLEND, 0);
517         glDisablei(GL_BLEND, 1);
518         glDisablei(GL_BLEND, 2);
519     }
520 }