material_editor_plugin.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. /**************************************************************************/
  2. /* material_editor_plugin.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "material_editor_plugin.h"
  31. #include "core/config/project_settings.h"
  32. #include "editor/editor_node.h"
  33. #include "editor/editor_string_names.h"
  34. #include "editor/editor_undo_redo_manager.h"
  35. #include "editor/settings/editor_settings.h"
  36. #include "editor/themes/editor_scale.h"
  37. #include "scene/gui/box_container.h"
  38. #include "scene/gui/button.h"
  39. #include "scene/gui/color_rect.h"
  40. #include "scene/gui/label.h"
  41. #include "scene/gui/subviewport_container.h"
  42. #include "scene/main/viewport.h"
  43. #include "scene/resources/canvas_item_material.h"
  44. #include "scene/resources/particle_process_material.h"
  45. // 3D.
  46. #include "scene/3d/camera_3d.h"
  47. #include "scene/3d/light_3d.h"
  48. #include "scene/3d/mesh_instance_3d.h"
  49. Ref<ShaderMaterial> MaterialEditor::make_shader_material(const Ref<Material> &p_from, bool p_copy_params) {
  50. ERR_FAIL_COND_V(p_from.is_null(), Ref<ShaderMaterial>());
  51. Ref<ShaderMaterial> smat;
  52. smat.instantiate();
  53. Ref<Shader> shader;
  54. shader.instantiate();
  55. String code = RS::get_singleton()->shader_get_code(p_from->get_shader_rid());
  56. shader->set_code(code);
  57. smat->set_shader(shader);
  58. if (p_copy_params) {
  59. List<PropertyInfo> params;
  60. RS::get_singleton()->get_shader_parameter_list(p_from->get_shader_rid(), &params);
  61. for (const PropertyInfo &E : params) {
  62. Variant value = RS::get_singleton()->material_get_param(p_from->get_rid(), E.name);
  63. smat->set_shader_parameter(E.name, value);
  64. }
  65. }
  66. smat->set_render_priority(p_from->get_render_priority());
  67. smat->set_local_to_scene(p_from->is_local_to_scene());
  68. smat->set_name(p_from->get_name());
  69. return smat;
  70. }
  71. void MaterialEditor::gui_input(const Ref<InputEvent> &p_event) {
  72. ERR_FAIL_COND(p_event.is_null());
  73. Ref<InputEventMouseMotion> mm = p_event;
  74. if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
  75. rot.x -= mm->get_relative().y * 0.01;
  76. rot.y -= mm->get_relative().x * 0.01;
  77. if (quad_instance->is_visible()) {
  78. // Clamp rotation so the quad is always visible.
  79. const real_t limit = Math::deg_to_rad(80.0);
  80. rot = rot.clampf(-limit, limit);
  81. } else {
  82. rot.x = CLAMP(rot.x, -Math::PI / 2, Math::PI / 2);
  83. }
  84. _update_rotation();
  85. _store_rotation_metadata();
  86. }
  87. }
  88. void MaterialEditor::_update_theme_item_cache() {
  89. Control::_update_theme_item_cache();
  90. theme_cache.light_1_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight1"));
  91. theme_cache.light_2_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight2"));
  92. theme_cache.sphere_icon = get_editor_theme_icon(SNAME("MaterialPreviewSphere"));
  93. theme_cache.box_icon = get_editor_theme_icon(SNAME("MaterialPreviewCube"));
  94. theme_cache.quad_icon = get_editor_theme_icon(SNAME("MaterialPreviewQuad"));
  95. theme_cache.checkerboard = get_editor_theme_icon(SNAME("Checkerboard"));
  96. }
  97. void MaterialEditor::_notification(int p_what) {
  98. switch (p_what) {
  99. case NOTIFICATION_THEME_CHANGED: {
  100. light_1_switch->set_button_icon(theme_cache.light_1_icon);
  101. light_2_switch->set_button_icon(theme_cache.light_2_icon);
  102. sphere_switch->set_button_icon(theme_cache.sphere_icon);
  103. box_switch->set_button_icon(theme_cache.box_icon);
  104. quad_switch->set_button_icon(theme_cache.quad_icon);
  105. error_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  106. } break;
  107. case NOTIFICATION_DRAW: {
  108. if (!is_unsupported_shader_mode) {
  109. Size2 size = get_size();
  110. draw_texture_rect(theme_cache.checkerboard, Rect2(Point2(), size), true);
  111. }
  112. } break;
  113. }
  114. }
  115. void MaterialEditor::_set_rotation(real_t p_x_degrees, real_t p_y_degrees) {
  116. rot.x = Math::deg_to_rad(p_x_degrees);
  117. rot.y = Math::deg_to_rad(p_y_degrees);
  118. _update_rotation();
  119. }
  120. // Store the rotation so it can persist when switching between materials.
  121. void MaterialEditor::_store_rotation_metadata() {
  122. Vector2 rotation_degrees = Vector2(Math::rad_to_deg(rot.x), Math::rad_to_deg(rot.y));
  123. EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_rotation", rotation_degrees);
  124. }
  125. void MaterialEditor::_update_rotation() {
  126. Transform3D t;
  127. t.basis.rotate(Vector3(0, 1, 0), -rot.y);
  128. t.basis.rotate(Vector3(1, 0, 0), -rot.x);
  129. rotation->set_transform(t);
  130. }
  131. void MaterialEditor::edit(Ref<Material> p_material, const Ref<Environment> &p_env) {
  132. material = p_material;
  133. camera->set_environment(p_env);
  134. is_unsupported_shader_mode = false;
  135. if (material.is_valid()) {
  136. Shader::Mode mode = p_material->get_shader_mode();
  137. switch (mode) {
  138. case Shader::MODE_CANVAS_ITEM:
  139. layout_error->hide();
  140. layout_3d->hide();
  141. layout_2d->show();
  142. rect_instance->set_material(material);
  143. vc->hide();
  144. break;
  145. case Shader::MODE_SPATIAL:
  146. layout_error->hide();
  147. layout_2d->hide();
  148. layout_3d->show();
  149. sphere_instance->set_material_override(material);
  150. box_instance->set_material_override(material);
  151. quad_instance->set_material_override(material);
  152. vc->show();
  153. break;
  154. default:
  155. layout_error->show();
  156. layout_2d->hide();
  157. layout_3d->hide();
  158. is_unsupported_shader_mode = true;
  159. vc->hide();
  160. break;
  161. }
  162. } else {
  163. hide();
  164. }
  165. }
  166. void MaterialEditor::_on_light_1_switch_pressed() {
  167. light1->set_visible(light_1_switch->is_pressed());
  168. }
  169. void MaterialEditor::_on_light_2_switch_pressed() {
  170. light2->set_visible(light_2_switch->is_pressed());
  171. }
  172. void MaterialEditor::_on_sphere_switch_pressed() {
  173. sphere_instance->show();
  174. box_instance->hide();
  175. quad_instance->hide();
  176. box_switch->set_pressed(false);
  177. quad_switch->set_pressed(false);
  178. _set_rotation(-15.0, 30.0);
  179. _store_rotation_metadata();
  180. EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_mesh", "sphere");
  181. }
  182. void MaterialEditor::_on_box_switch_pressed() {
  183. sphere_instance->hide();
  184. box_instance->show();
  185. quad_instance->hide();
  186. sphere_switch->set_pressed(false);
  187. quad_switch->set_pressed(false);
  188. _set_rotation(-15.0, 30.0);
  189. _store_rotation_metadata();
  190. EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_mesh", "box");
  191. }
  192. void MaterialEditor::_on_quad_switch_pressed() {
  193. sphere_instance->hide();
  194. box_instance->hide();
  195. quad_instance->show();
  196. sphere_switch->set_pressed(false);
  197. box_switch->set_pressed(false);
  198. _set_rotation(0.0, 0.0);
  199. _store_rotation_metadata();
  200. EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_mesh", "quad");
  201. }
  202. MaterialEditor::MaterialEditor() {
  203. set_custom_minimum_size(Size2(1, 150) * EDSCALE);
  204. // Canvas item
  205. vc_2d = memnew(SubViewportContainer);
  206. vc_2d->set_stretch(true);
  207. add_child(vc_2d);
  208. vc_2d->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
  209. viewport_2d = memnew(SubViewport);
  210. vc_2d->add_child(viewport_2d);
  211. viewport_2d->set_disable_input(true);
  212. viewport_2d->set_transparent_background(true);
  213. layout_2d = memnew(HBoxContainer);
  214. layout_2d->set_alignment(BoxContainer::ALIGNMENT_CENTER);
  215. viewport_2d->add_child(layout_2d);
  216. layout_2d->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
  217. rect_instance = memnew(ColorRect);
  218. layout_2d->add_child(rect_instance);
  219. rect_instance->set_custom_minimum_size(Size2(150, 150) * EDSCALE);
  220. layout_2d->set_visible(false);
  221. layout_error = memnew(VBoxContainer);
  222. layout_error->set_alignment(BoxContainer::ALIGNMENT_CENTER);
  223. layout_error->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
  224. error_label = memnew(Label);
  225. error_label->set_focus_mode(FOCUS_ACCESSIBILITY);
  226. error_label->set_text(TTR("Preview is not available for this shader mode."));
  227. error_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
  228. error_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
  229. error_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
  230. layout_error->add_child(error_label);
  231. layout_error->hide();
  232. add_child(layout_error);
  233. // Spatial
  234. vc = memnew(SubViewportContainer);
  235. vc->set_stretch(true);
  236. add_child(vc);
  237. vc->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
  238. viewport = memnew(SubViewport);
  239. Ref<World3D> world_3d;
  240. world_3d.instantiate();
  241. viewport->set_world_3d(world_3d); // Use own world.
  242. vc->add_child(viewport);
  243. viewport->set_disable_input(true);
  244. viewport->set_transparent_background(true);
  245. viewport->set_msaa_3d(Viewport::MSAA_4X);
  246. camera = memnew(Camera3D);
  247. camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 1.1)));
  248. // Use low field of view so the sphere/box/quad is fully encompassed within the preview,
  249. // without much distortion.
  250. camera->set_perspective(20, 0.1, 10);
  251. camera->make_current();
  252. if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
  253. camera_attributes.instantiate();
  254. camera->set_attributes(camera_attributes);
  255. }
  256. viewport->add_child(camera);
  257. light1 = memnew(DirectionalLight3D);
  258. light1->set_transform(Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
  259. viewport->add_child(light1);
  260. light2 = memnew(DirectionalLight3D);
  261. light2->set_transform(Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
  262. light2->set_color(Color(0.7, 0.7, 0.7));
  263. viewport->add_child(light2);
  264. rotation = memnew(Node3D);
  265. viewport->add_child(rotation);
  266. sphere_instance = memnew(MeshInstance3D);
  267. rotation->add_child(sphere_instance);
  268. box_instance = memnew(MeshInstance3D);
  269. rotation->add_child(box_instance);
  270. quad_instance = memnew(MeshInstance3D);
  271. rotation->add_child(quad_instance);
  272. sphere_instance->set_transform(Transform3D() * 0.375);
  273. box_instance->set_transform(Transform3D() * 0.25);
  274. quad_instance->set_transform(Transform3D() * 0.375);
  275. sphere_mesh.instantiate();
  276. sphere_instance->set_mesh(sphere_mesh);
  277. box_mesh.instantiate();
  278. box_instance->set_mesh(box_mesh);
  279. quad_mesh.instantiate();
  280. quad_instance->set_mesh(quad_mesh);
  281. layout_3d = memnew(HBoxContainer);
  282. add_child(layout_3d);
  283. layout_3d->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 2);
  284. VBoxContainer *vb_shape = memnew(VBoxContainer);
  285. layout_3d->add_child(vb_shape);
  286. sphere_switch = memnew(Button);
  287. sphere_switch->set_theme_type_variation("PreviewLightButton");
  288. sphere_switch->set_toggle_mode(true);
  289. sphere_switch->set_accessibility_name(TTRC("Sphere"));
  290. vb_shape->add_child(sphere_switch);
  291. sphere_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_sphere_switch_pressed));
  292. box_switch = memnew(Button);
  293. box_switch->set_theme_type_variation("PreviewLightButton");
  294. box_switch->set_toggle_mode(true);
  295. box_switch->set_accessibility_name(TTRC("Box"));
  296. vb_shape->add_child(box_switch);
  297. box_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_box_switch_pressed));
  298. quad_switch = memnew(Button);
  299. quad_switch->set_theme_type_variation("PreviewLightButton");
  300. quad_switch->set_toggle_mode(true);
  301. quad_switch->set_accessibility_name(TTRC("Quad"));
  302. vb_shape->add_child(quad_switch);
  303. quad_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_quad_switch_pressed));
  304. layout_3d->add_spacer();
  305. VBoxContainer *vb_light = memnew(VBoxContainer);
  306. layout_3d->add_child(vb_light);
  307. light_1_switch = memnew(Button);
  308. light_1_switch->set_theme_type_variation("PreviewLightButton");
  309. light_1_switch->set_toggle_mode(true);
  310. light_1_switch->set_pressed(true);
  311. light_1_switch->set_accessibility_name(TTRC("First Light"));
  312. vb_light->add_child(light_1_switch);
  313. light_1_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_light_1_switch_pressed));
  314. light_2_switch = memnew(Button);
  315. light_2_switch->set_theme_type_variation("PreviewLightButton");
  316. light_2_switch->set_toggle_mode(true);
  317. light_2_switch->set_pressed(true);
  318. light_2_switch->set_accessibility_name(TTRC("Second Light"));
  319. vb_light->add_child(light_2_switch);
  320. light_2_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_light_2_switch_pressed));
  321. String shape = EditorSettings::get_singleton()->get_project_metadata("inspector_options", "material_preview_mesh", "sphere");
  322. if (shape == "sphere") {
  323. box_instance->hide();
  324. quad_instance->hide();
  325. sphere_switch->set_pressed_no_signal(true);
  326. } else if (shape == "box") {
  327. sphere_instance->hide();
  328. quad_instance->hide();
  329. box_switch->set_pressed_no_signal(true);
  330. } else {
  331. sphere_instance->hide();
  332. box_instance->hide();
  333. quad_switch->set_pressed_no_signal(true);
  334. }
  335. Vector2 stored_rot = EditorSettings::get_singleton()->get_project_metadata("inspector_options", "material_preview_rotation", Vector2());
  336. _set_rotation(stored_rot.x, stored_rot.y);
  337. }
  338. ///////////////////////
  339. bool EditorInspectorPluginMaterial::can_handle(Object *p_object) {
  340. Material *material = Object::cast_to<Material>(p_object);
  341. if (!material) {
  342. return false;
  343. }
  344. Shader::Mode mode = material->get_shader_mode();
  345. return mode == Shader::MODE_SPATIAL || mode == Shader::MODE_CANVAS_ITEM;
  346. }
  347. void EditorInspectorPluginMaterial::parse_begin(Object *p_object) {
  348. Material *material = Object::cast_to<Material>(p_object);
  349. if (!material) {
  350. return;
  351. }
  352. Ref<Material> m(material);
  353. MaterialEditor *editor = memnew(MaterialEditor);
  354. editor->edit(m, env);
  355. add_custom_control(editor);
  356. }
  357. void EditorInspectorPluginMaterial::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, const String &p_property, const Variant &p_new_value) {
  358. EditorUndoRedoManager *undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo);
  359. ERR_FAIL_NULL(undo_redo);
  360. // For BaseMaterial3D, if a roughness or metallic textures is being assigned to an empty slot,
  361. // set the respective metallic or roughness factor to 1.0 as a convenience feature
  362. BaseMaterial3D *base_material = Object::cast_to<StandardMaterial3D>(p_edited);
  363. if (base_material) {
  364. Texture2D *texture = Object::cast_to<Texture2D>(p_new_value);
  365. if (texture) {
  366. if (p_property == "roughness_texture") {
  367. if (base_material->get_texture(StandardMaterial3D::TEXTURE_ROUGHNESS).is_null()) {
  368. undo_redo->add_do_property(p_edited, "roughness", 1.0);
  369. bool valid = false;
  370. Variant value = p_edited->get("roughness", &valid);
  371. if (valid) {
  372. undo_redo->add_undo_property(p_edited, "roughness", value);
  373. }
  374. }
  375. } else if (p_property == "metallic_texture") {
  376. if (base_material->get_texture(StandardMaterial3D::TEXTURE_METALLIC).is_null()) {
  377. undo_redo->add_do_property(p_edited, "metallic", 1.0);
  378. bool valid = false;
  379. Variant value = p_edited->get("metallic", &valid);
  380. if (valid) {
  381. undo_redo->add_undo_property(p_edited, "metallic", value);
  382. }
  383. }
  384. }
  385. }
  386. }
  387. }
  388. EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() {
  389. env.instantiate();
  390. Ref<Sky> sky = memnew(Sky());
  391. env->set_sky(sky);
  392. env->set_background(Environment::BG_COLOR);
  393. env->set_ambient_source(Environment::AMBIENT_SOURCE_SKY);
  394. env->set_reflection_source(Environment::REFLECTION_SOURCE_SKY);
  395. EditorNode::get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &EditorInspectorPluginMaterial::_undo_redo_inspector_callback));
  396. }
  397. MaterialEditorPlugin::MaterialEditorPlugin() {
  398. Ref<EditorInspectorPluginMaterial> plugin;
  399. plugin.instantiate();
  400. add_inspector_plugin(plugin);
  401. }
  402. String ParticleProcessMaterialConversionPlugin::converts_to() const {
  403. return "ShaderMaterial";
  404. }
  405. bool ParticleProcessMaterialConversionPlugin::handles(const Ref<Resource> &p_resource) const {
  406. Ref<ParticleProcessMaterial> mat = p_resource;
  407. return mat.is_valid();
  408. }
  409. Ref<Resource> ParticleProcessMaterialConversionPlugin::convert(const Ref<Resource> &p_resource) const {
  410. return MaterialEditor::make_shader_material(p_resource);
  411. }
  412. String CanvasItemMaterialConversionPlugin::converts_to() const {
  413. return "ShaderMaterial";
  414. }
  415. bool CanvasItemMaterialConversionPlugin::handles(const Ref<Resource> &p_resource) const {
  416. Ref<CanvasItemMaterial> mat = p_resource;
  417. return mat.is_valid();
  418. }
  419. Ref<Resource> CanvasItemMaterialConversionPlugin::convert(const Ref<Resource> &p_resource) const {
  420. ERR_FAIL_COND_V(!Object::cast_to<CanvasItemMaterial>(*p_resource) || !Object::cast_to<CanvasItemMaterial>(*p_resource)->_is_initialized(), Ref<CanvasItemMaterial>());
  421. return MaterialEditor::make_shader_material(p_resource);
  422. }