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 }