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