1 /* 2 Copyright (c) 2017-2018 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 } 186 187 final auto opDispatch(string name)() @property 188 { 189 return (name in inputs); 190 } 191 192 final void opDispatch(string name, T)(T value) @property 193 { 194 setInput(name, value); 195 } 196 197 final MaterialInput* setInput(T)(string name, T value) 198 { 199 MaterialInput input; 200 static if (is(T == bool)) 201 { 202 input.type = MaterialInputType.Bool; 203 input.asBool = value; 204 } 205 else static if (is(T == int)) 206 { 207 input.type = MaterialInputType.Integer; 208 input.asInteger = value; 209 } 210 else static if (is(T == float) || is(T == double)) 211 { 212 input.type = MaterialInputType.Float; 213 input.asFloat = value; 214 } 215 else static if (is(T == Vector2f)) 216 { 217 input.type = MaterialInputType.Vec2; 218 input.asVector2f = value; 219 } 220 else static if (is(T == Vector3f)) 221 { 222 input.type = MaterialInputType.Vec3; 223 input.asVector3f = value; 224 } 225 else static if (is(T == Vector4f)) 226 { 227 input.type = MaterialInputType.Vec4; 228 input.asVector4f = value; 229 } 230 else static if (is(T == Color4f)) 231 { 232 input.type = MaterialInputType.Vec4; 233 input.asVector4f = value; 234 } 235 else static if (is(T == Texture)) 236 { 237 input.texture = value; 238 if (value.format == GL_RED) 239 input.type = MaterialInputType.Float; 240 else if (value.format == GL_RG) 241 input.type = MaterialInputType.Vec2; 242 else if (value.format == GL_RGB) 243 input.type = MaterialInputType.Vec3; 244 else if (value.format == GL_RGBA) 245 input.type = MaterialInputType.Vec4; 246 } 247 else 248 { 249 input.type = MaterialInputType.Undefined; 250 } 251 252 inputs[name] = input; 253 return (name in inputs); 254 } 255 256 final bool boolProp(string prop) 257 { 258 auto p = prop in inputs; 259 bool res = false; 260 if (p.type == MaterialInputType.Bool || 261 p.type == MaterialInputType.Integer) 262 { 263 res = p.asBool; 264 } 265 return res; 266 } 267 268 final int intProp(string prop) 269 { 270 auto p = prop in inputs; 271 int res = 0; 272 if (p.type == MaterialInputType.Bool || 273 p.type == MaterialInputType.Integer) 274 { 275 res = p.asInteger; 276 } 277 else if (p.type == MaterialInputType.Float) 278 { 279 res = cast(int)p.asFloat; 280 } 281 return res; 282 } 283 284 final Texture makeTexture(Color4f rgb, Texture alpha) 285 { 286 SuperImage rgbaImg = New!UnmanagedImageRGBA8(alpha.width, alpha.height); 287 288 foreach(y; 0..alpha.height) 289 foreach(x; 0..alpha.width) 290 { 291 Color4f col = rgb; 292 col.a = alpha.image[x, y].r; 293 rgbaImg[x, y] = col; 294 } 295 296 auto tex = New!Texture(rgbaImg, this); 297 return tex; 298 } 299 300 final Texture makeTexture(Texture rgb, float alpha) 301 { 302 SuperImage rgbaImg = New!UnmanagedImageRGBA8(rgb.width, rgb.height); 303 304 foreach(y; 0..rgb.height) 305 foreach(x; 0..rgb.width) 306 { 307 Color4f col = rgb.image[x, y]; 308 col.a = alpha; 309 rgbaImg[x, y] = col; 310 } 311 312 auto tex = New!Texture(rgbaImg, this); 313 return tex; 314 } 315 316 final Texture makeTexture(Texture rgb, Texture alpha) 317 { 318 uint width = max(rgb.width, alpha.width); 319 uint height = max(rgb.height, alpha.height); 320 321 SuperImage rgbaImg = New!UnmanagedImageRGBA8(width, height); 322 323 foreach(y; 0..rgbaImg.height) 324 foreach(x; 0..rgbaImg.width) 325 { 326 float u = cast(float)x / cast(float)width; 327 float v = cast(float)y / cast(float)height; 328 329 Color4f col = rgb.sample(u, v); 330 col.a = alpha.sample(u, v).r; 331 332 rgbaImg[x, y] = col; 333 } 334 335 auto tex = New!Texture(rgbaImg, this); 336 return tex; 337 } 338 339 final Texture makeTexture(MaterialInput r, MaterialInput g, MaterialInput b, MaterialInput a) 340 { 341 uint width = 8; 342 uint height = 8; 343 344 if (r.texture !is null) 345 { 346 width = max(width, r.texture.width); 347 height = max(height, r.texture.height); 348 } 349 350 if (g.texture !is null) 351 { 352 width = max(width, g.texture.width); 353 height = max(height, g.texture.height); 354 } 355 356 if (b.texture !is null) 357 { 358 width = max(width, b.texture.width); 359 height = max(height, b.texture.height); 360 } 361 362 if (a.texture !is null) 363 { 364 width = max(width, a.texture.width); 365 height = max(height, a.texture.height); 366 } 367 368 SuperImage img = New!UnmanagedImageRGBA8(width, height); 369 370 foreach(y; 0..img.height) 371 foreach(x; 0..img.width) 372 { 373 Color4f col = Color4f(0, 0, 0, 0); 374 375 float u = cast(float)x / cast(float)img.width; 376 float v = cast(float)y / cast(float)img.height; 377 378 col.r = r.sample(u, v).r; 379 col.g = g.sample(u, v).r; 380 col.b = b.sample(u, v).r; 381 col.a = a.sample(u, v).r; 382 383 img[x, y] = col; 384 } 385 386 auto tex = New!Texture(img, this); 387 return tex; 388 } 389 390 bool isTransparent() 391 { 392 auto iblending = "blending" in inputs; 393 int b = iblending.asInteger; 394 return (b == Transparent || b == Additive); 395 } 396 397 bool usesCustomShader() 398 { 399 return customShader; 400 } 401 402 void bind(RenderingContext* rc) 403 { 404 auto iblending = "blending" in inputs; 405 auto iculling = "culling" in inputs; 406 auto icolorWrite = "colorWrite" in inputs; 407 auto idepthWrite = "depthWrite" in inputs; 408 409 if (iblending.asInteger == Transparent) 410 { 411 glEnablei(GL_BLEND, 0); 412 glEnablei(GL_BLEND, 1); 413 glEnablei(GL_BLEND, 2); 414 glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 415 glBlendFunci(1, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 416 glBlendFunci(2, GL_SRC_ALPHA, GL_ONE); 417 } 418 else if (iblending.asInteger == Additive) 419 { 420 glEnablei(GL_BLEND, 0); 421 glEnablei(GL_BLEND, 1); 422 glEnablei(GL_BLEND, 2); 423 glBlendFunci(0, GL_SRC_ALPHA, GL_ONE); 424 glBlendFunci(1, GL_SRC_ALPHA, GL_ONE); 425 glBlendFunci(2, GL_SRC_ALPHA, GL_ONE); 426 } 427 428 if (iculling.asBool) 429 { 430 glEnable(GL_CULL_FACE); 431 } 432 433 if (!icolorWrite.asBool) 434 { 435 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 436 } 437 438 if (!idepthWrite.asBool && !rc.shadowPass) 439 { 440 glDepthMask(GL_FALSE); 441 } 442 443 RenderingContext rcLocal = *rc; 444 rcLocal.material = this; 445 446 if (rc.overrideShader) 447 rc.overrideShader.bind(&rcLocal); 448 else if (shader) 449 shader.bind(&rcLocal); 450 } 451 452 void unbind(RenderingContext* rc) 453 { 454 auto icolorWrite = "colorWrite" in inputs; 455 auto idepthWrite = "depthWrite" in inputs; 456 457 RenderingContext rcLocal = *rc; 458 rcLocal.material = this; 459 460 if (rc.overrideShader) 461 rc.overrideShader.unbind(&rcLocal); 462 else if (shader) 463 shader.unbind(&rcLocal); 464 465 if (!idepthWrite.asBool && rc.depthPass) 466 { 467 glDepthMask(GL_TRUE); 468 } 469 470 if (!icolorWrite.asBool && rc.colorPass) 471 { 472 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 473 } 474 475 glDisable(GL_CULL_FACE); 476 477 glDisablei(GL_BLEND, 0); 478 glDisablei(GL_BLEND, 1); 479 glDisablei(GL_BLEND, 2); 480 } 481 } 482 483 deprecated("use `Material` instead") alias GenericMaterial = Material; 484 deprecated("use `Material` instead") alias ShaderMaterial = Material;