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;