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.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("roughness", 0.5f); 172 setInput("metallic", 0.0f); 173 setInput("specularity", 1.0f); 174 setInput("translucency", 0.0f); 175 setInput("normal", Vector3f(0.0f, 0.0f, 1.0f)); 176 setInput("height", 0.0f); 177 setInput("parallax", ParallaxNone); 178 setInput("parallaxScale", 0.03f); 179 setInput("parallaxBias", -0.01f); 180 setInput("shadowsEnabled", true); 181 setInput("shadowFilter", ShadowFilterPCF); 182 setInput("fogEnabled", true); 183 setInput("blending", Opaque); 184 setInput("culling", true); 185 setInput("colorWrite", true); 186 setInput("depthWrite", true); 187 setInput("particleColor", Color4f(1.0f, 1.0f, 1.0f, 1.0f)); 188 setInput("particleSphericalNormal", false); 189 setInput("textureScale", Vector2f(1.0f, 1.0f)); 190 setInput("sphericalNormal", false); 191 192 setInput("outputColor", true); 193 setInput("outputNormal", true); 194 setInput("outputPBR", true); 195 setInput("outputEmission", true); 196 197 setInput("diffuse2", Color4f(0.8f, 0.8f, 0.8f, 1.0f)); 198 setInput("diffuse3", Color4f(0.8f, 0.8f, 0.8f, 1.0f)); 199 setInput("diffuse4", Color4f(0.8f, 0.8f, 0.8f, 1.0f)); 200 201 setInput("normal2", Vector3f(0.0f, 0.0f, 1.0f)); 202 setInput("normal3", Vector3f(0.0f, 0.0f, 1.0f)); 203 setInput("normal4", Vector3f(0.0f, 0.0f, 1.0f)); 204 205 setInput("height2", 0.0f); 206 setInput("height3", 0.0f); 207 setInput("height4", 0.0f); 208 209 setInput("roughness2", 0.5f); 210 setInput("roughness3", 0.5f); 211 setInput("roughness4", 0.5f); 212 213 setInput("metallic2", 0.0f); 214 setInput("metallic3", 0.0f); 215 setInput("metallic4", 0.0f); 216 217 setInput("textureScale2", Vector2f(1.0f, 1.0f)); 218 setInput("textureScale3", Vector2f(1.0f, 1.0f)); 219 setInput("textureScale4", Vector2f(1.0f, 1.0f)); 220 221 setInput("splatmap1", Color4f(1.0f, 1.0f, 1.0f, 1.0f)); 222 setInput("splatmap2", Color4f(0.0f, 0.0f, 0.0f, 0.0f)); 223 setInput("splatmap3", Color4f(0.0f, 0.0f, 0.0f, 0.0f)); 224 setInput("splatmap4", Color4f(0.0f, 0.0f, 0.0f, 0.0f)); 225 } 226 227 final auto opDispatch(string name)() @property 228 { 229 return (name in inputs); 230 } 231 232 final void opDispatch(string name, T)(T value) @property 233 { 234 setInput(name, value); 235 } 236 237 final MaterialInput* setInput(T)(string name, T value) 238 { 239 MaterialInput input; 240 static if (is(T == bool)) 241 { 242 input.type = MaterialInputType.Bool; 243 input.asBool = value; 244 } 245 else static if (is(T == int)) 246 { 247 input.type = MaterialInputType.Integer; 248 input.asInteger = value; 249 } 250 else static if (is(T == float) || is(T == double)) 251 { 252 input.type = MaterialInputType.Float; 253 input.asFloat = value; 254 } 255 else static if (is(T == Vector2f)) 256 { 257 input.type = MaterialInputType.Vec2; 258 input.asVector2f = value; 259 } 260 else static if (is(T == Vector3f)) 261 { 262 input.type = MaterialInputType.Vec3; 263 input.asVector3f = value; 264 } 265 else static if (is(T == Vector4f)) 266 { 267 input.type = MaterialInputType.Vec4; 268 input.asVector4f = value; 269 } 270 else static if (is(T == Color4f)) 271 { 272 input.type = MaterialInputType.Vec4; 273 input.asVector4f = value; 274 } 275 else static if (is(T == Texture) || is(T == Cubemap)) 276 { 277 input.texture = cast(Texture)value; 278 if (value.format == GL_RED) 279 input.type = MaterialInputType.Float; 280 else if (value.format == GL_RG) 281 input.type = MaterialInputType.Vec2; 282 else if (value.format == GL_RGB) 283 input.type = MaterialInputType.Vec3; 284 else if (value.format == GL_RGBA) 285 input.type = MaterialInputType.Vec4; 286 } 287 else static assert("Unsupported type " ~ T.stringof); 288 289 inputs[name] = input; 290 return (name in inputs); 291 } 292 293 final bool boolProp(string prop) 294 { 295 auto p = prop in inputs; 296 bool res = false; 297 if (p.type == MaterialInputType.Bool || 298 p.type == MaterialInputType.Integer) 299 { 300 res = p.asBool; 301 } 302 return res; 303 } 304 305 final int intProp(string prop) 306 { 307 auto p = prop in inputs; 308 int res = 0; 309 if (p.type == MaterialInputType.Bool || 310 p.type == MaterialInputType.Integer) 311 { 312 res = p.asInteger; 313 } 314 else if (p.type == MaterialInputType.Float) 315 { 316 res = cast(int)p.asFloat; 317 } 318 return res; 319 } 320 321 final Texture makeTexture(Color4f rgb) 322 { 323 SuperImage rgbaImg = New!UnmanagedImageRGBA8(8, 8); 324 325 foreach(y; 0..rgbaImg.height) 326 foreach(x; 0..rgbaImg.width) 327 { 328 rgbaImg[x, y] = rgb; 329 } 330 331 auto tex = New!Texture(rgbaImg, this); 332 return tex; 333 } 334 335 final Texture makeTexture(Color4f rgb, Texture alpha) 336 { 337 SuperImage rgbaImg = New!UnmanagedImageRGBA8(alpha.width, alpha.height); 338 339 foreach(y; 0..alpha.height) 340 foreach(x; 0..alpha.width) 341 { 342 Color4f col = rgb; 343 col.a = alpha.image[x, y].r; 344 rgbaImg[x, y] = col; 345 } 346 347 auto tex = New!Texture(rgbaImg, this); 348 return tex; 349 } 350 351 final Texture makeTexture(Texture rgb, float alpha) 352 { 353 SuperImage rgbaImg = New!UnmanagedImageRGBA8(rgb.width, rgb.height); 354 355 foreach(y; 0..rgb.height) 356 foreach(x; 0..rgb.width) 357 { 358 Color4f col = rgb.image[x, y]; 359 col.a = alpha; 360 rgbaImg[x, y] = col; 361 } 362 363 auto tex = New!Texture(rgbaImg, this); 364 return tex; 365 } 366 367 final Texture makeTexture(Texture rgb, Texture alpha) 368 { 369 uint width = max(rgb.width, alpha.width); 370 uint height = max(rgb.height, alpha.height); 371 372 SuperImage rgbaImg = New!UnmanagedImageRGBA8(width, height); 373 374 foreach(y; 0..rgbaImg.height) 375 foreach(x; 0..rgbaImg.width) 376 { 377 float u = cast(float)x / cast(float)width; 378 float v = cast(float)y / cast(float)height; 379 380 Color4f col = rgb.sample(u, v); 381 col.a = alpha.sample(u, v).r; 382 383 rgbaImg[x, y] = col; 384 } 385 386 auto tex = New!Texture(rgbaImg, this); 387 return tex; 388 } 389 390 final Texture makeTexture(MaterialInput r, MaterialInput g, MaterialInput b, MaterialInput a) 391 { 392 uint width = 8; 393 uint height = 8; 394 395 if (r.texture !is null) 396 { 397 width = max(width, r.texture.width); 398 height = max(height, r.texture.height); 399 } 400 401 if (g.texture !is null) 402 { 403 width = max(width, g.texture.width); 404 height = max(height, g.texture.height); 405 } 406 407 if (b.texture !is null) 408 { 409 width = max(width, b.texture.width); 410 height = max(height, b.texture.height); 411 } 412 413 if (a.texture !is null) 414 { 415 width = max(width, a.texture.width); 416 height = max(height, a.texture.height); 417 } 418 419 SuperImage img = New!UnmanagedImageRGBA8(width, height); 420 421 foreach(y; 0..img.height) 422 foreach(x; 0..img.width) 423 { 424 Color4f col = Color4f(0, 0, 0, 0); 425 426 float u = cast(float)x / cast(float)img.width; 427 float v = cast(float)y / cast(float)img.height; 428 429 col.r = r.sample(u, v).r; 430 col.g = g.sample(u, v).r; 431 col.b = b.sample(u, v).r; 432 col.a = a.sample(u, v).r; 433 434 img[x, y] = col; 435 } 436 437 auto tex = New!Texture(img, this); 438 return tex; 439 } 440 441 bool isTransparent() 442 { 443 auto iblending = "blending" in inputs; 444 int b = iblending.asInteger; 445 return (b == Transparent || b == Additive); 446 } 447 448 void bind(GraphicsState* state) 449 { 450 auto iblending = "blending" in inputs; 451 auto iculling = "culling" in inputs; 452 auto icolorWrite = "colorWrite" in inputs; 453 auto idepthWrite = "depthWrite" in inputs; 454 455 if (iblending.asInteger == Transparent) 456 { 457 glEnablei(GL_BLEND, 0); 458 glEnablei(GL_BLEND, 1); 459 glEnablei(GL_BLEND, 2); 460 glBlendFuncSeparatei(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 461 glBlendFuncSeparatei(1, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 462 glBlendFuncSeparatei(2, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 463 } 464 else if (iblending.asInteger == Additive) 465 { 466 glEnablei(GL_BLEND, 0); 467 glEnablei(GL_BLEND, 1); 468 glEnablei(GL_BLEND, 2); 469 glBlendFunci(0, GL_SRC_ALPHA, GL_ONE); 470 glBlendFunci(1, GL_SRC_ALPHA, GL_ONE); 471 glBlendFunci(2, GL_SRC_ALPHA, GL_ONE); 472 } 473 474 if (iculling.asBool && state.culling) 475 { 476 glEnable(GL_CULL_FACE); 477 } 478 else 479 { 480 glDisable(GL_CULL_FACE); 481 } 482 483 if (!icolorWrite.asBool || !state.colorMask) 484 { 485 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 486 } 487 488 if (!idepthWrite.asBool || !state.depthMask) 489 { 490 glDepthMask(GL_FALSE); 491 } 492 493 state.material = this; 494 } 495 496 void unbind(GraphicsState* state) 497 { 498 state.material = null; 499 500 glDepthMask(GL_TRUE); 501 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 502 503 glDisable(GL_CULL_FACE); 504 505 glDisablei(GL_BLEND, 0); 506 glDisablei(GL_BLEND, 1); 507 glDisablei(GL_BLEND, 2); 508 } 509 }