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 }