Path: blob/master/editor/scene/material_editor_plugin.cpp
20837 views
/**************************************************************************/1/* material_editor_plugin.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "material_editor_plugin.h"3132#include "core/config/project_settings.h"33#include "editor/editor_node.h"34#include "editor/editor_string_names.h"35#include "editor/editor_undo_redo_manager.h"36#include "editor/settings/editor_settings.h"37#include "editor/themes/editor_scale.h"38#include "scene/gui/box_container.h"39#include "scene/gui/button.h"40#include "scene/gui/color_rect.h"41#include "scene/gui/label.h"42#include "scene/gui/subviewport_container.h"43#include "scene/main/viewport.h"44#include "scene/resources/blit_material.h"45#include "scene/resources/canvas_item_material.h"46#include "scene/resources/particle_process_material.h"47#include "scene/resources/sky.h"4849// 3D.50#include "scene/3d/camera_3d.h"51#include "scene/3d/light_3d.h"52#include "scene/3d/mesh_instance_3d.h"5354Ref<ShaderMaterial> MaterialEditor::make_shader_material(const Ref<Material> &p_from, bool p_copy_params) {55ERR_FAIL_COND_V(p_from.is_null(), Ref<ShaderMaterial>());5657Ref<ShaderMaterial> smat;58smat.instantiate();5960Ref<Shader> shader;61shader.instantiate();6263String code = RS::get_singleton()->shader_get_code(p_from->get_shader_rid());64shader->set_code(code);65smat->set_shader(shader);6667if (p_copy_params) {68List<PropertyInfo> params;69RS::get_singleton()->get_shader_parameter_list(p_from->get_shader_rid(), ¶ms);7071for (const PropertyInfo &E : params) {72Variant value = RS::get_singleton()->material_get_param(p_from->get_rid(), E.name);73smat->set_shader_parameter(E.name, value);74}75}7677smat->set_render_priority(p_from->get_render_priority());78smat->set_local_to_scene(p_from->is_local_to_scene());79smat->set_name(p_from->get_name());80return smat;81}8283void MaterialEditor::gui_input(const Ref<InputEvent> &p_event) {84ERR_FAIL_COND(p_event.is_null());8586Ref<InputEventMouseMotion> mm = p_event;87if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {88rot.x -= mm->get_relative().y * 0.01;89rot.y -= mm->get_relative().x * 0.01;90if (quad_instance->is_visible()) {91// Clamp rotation so the quad is always visible.92const real_t limit = Math::deg_to_rad(80.0);93rot = rot.clampf(-limit, limit);94} else {95rot.x = CLAMP(rot.x, -Math::PI / 2, Math::PI / 2);96}97_update_rotation();98_store_rotation_metadata();99}100}101102void MaterialEditor::_update_theme_item_cache() {103Control::_update_theme_item_cache();104105theme_cache.light_1_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight1"));106theme_cache.light_2_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight2"));107108theme_cache.sphere_icon = get_editor_theme_icon(SNAME("MaterialPreviewSphere"));109theme_cache.box_icon = get_editor_theme_icon(SNAME("MaterialPreviewCube"));110theme_cache.quad_icon = get_editor_theme_icon(SNAME("MaterialPreviewQuad"));111112theme_cache.checkerboard = get_editor_theme_icon(SNAME("Checkerboard"));113}114115void MaterialEditor::_notification(int p_what) {116switch (p_what) {117case NOTIFICATION_THEME_CHANGED: {118light_1_switch->set_button_icon(theme_cache.light_1_icon);119light_2_switch->set_button_icon(theme_cache.light_2_icon);120121sphere_switch->set_button_icon(theme_cache.sphere_icon);122box_switch->set_button_icon(theme_cache.box_icon);123quad_switch->set_button_icon(theme_cache.quad_icon);124125error_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));126} break;127128case NOTIFICATION_DRAW: {129if (!is_unsupported_shader_mode) {130Size2 size = get_size();131draw_texture_rect(theme_cache.checkerboard, Rect2(Point2(), size), true);132}133} break;134}135}136137void MaterialEditor::_set_rotation(real_t p_x_degrees, real_t p_y_degrees) {138rot.x = Math::deg_to_rad(p_x_degrees);139rot.y = Math::deg_to_rad(p_y_degrees);140_update_rotation();141}142143// Store the rotation so it can persist when switching between materials.144void MaterialEditor::_store_rotation_metadata() {145Vector2 rotation_degrees = Vector2(Math::rad_to_deg(rot.x), Math::rad_to_deg(rot.y));146EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_rotation", rotation_degrees);147}148149void MaterialEditor::_update_rotation() {150Transform3D t;151t.basis.rotate(Vector3(0, 1, 0), -rot.y);152t.basis.rotate(Vector3(1, 0, 0), -rot.x);153rotation->set_transform(t);154}155156void MaterialEditor::edit(Ref<Material> p_material, const Ref<Environment> &p_env) {157material = p_material;158camera->set_environment(p_env);159160is_unsupported_shader_mode = false;161if (material.is_valid()) {162Shader::Mode mode = p_material->get_shader_mode();163switch (mode) {164case Shader::MODE_CANVAS_ITEM:165layout_error->hide();166layout_3d->hide();167layout_2d->show();168rect_instance->set_material(material);169vc->hide();170break;171case Shader::MODE_SPATIAL:172layout_error->hide();173layout_2d->hide();174layout_3d->show();175sphere_instance->set_material_override(material);176box_instance->set_material_override(material);177quad_instance->set_material_override(material);178vc->show();179break;180default:181layout_error->show();182layout_2d->hide();183layout_3d->hide();184is_unsupported_shader_mode = true;185vc->hide();186break;187}188} else {189hide();190}191}192193void MaterialEditor::_on_light_1_switch_pressed() {194light1->set_visible(light_1_switch->is_pressed());195}196197void MaterialEditor::_on_light_2_switch_pressed() {198light2->set_visible(light_2_switch->is_pressed());199}200201void MaterialEditor::_on_sphere_switch_pressed() {202sphere_instance->show();203box_instance->hide();204quad_instance->hide();205box_switch->set_pressed(false);206quad_switch->set_pressed(false);207_set_rotation(-15.0, 30.0);208_store_rotation_metadata();209EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_mesh", "sphere");210}211212void MaterialEditor::_on_box_switch_pressed() {213sphere_instance->hide();214box_instance->show();215quad_instance->hide();216sphere_switch->set_pressed(false);217quad_switch->set_pressed(false);218_set_rotation(-15.0, 30.0);219_store_rotation_metadata();220EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_mesh", "box");221}222223void MaterialEditor::_on_quad_switch_pressed() {224sphere_instance->hide();225box_instance->hide();226quad_instance->show();227sphere_switch->set_pressed(false);228box_switch->set_pressed(false);229_set_rotation(0.0, 0.0);230_store_rotation_metadata();231EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_mesh", "quad");232}233234MaterialEditor::MaterialEditor() {235set_custom_minimum_size(Size2(1, 150) * EDSCALE);236237// Canvas item238239vc_2d = memnew(SubViewportContainer);240vc_2d->set_stretch(true);241add_child(vc_2d);242vc_2d->set_anchors_and_offsets_preset(PRESET_FULL_RECT);243244viewport_2d = memnew(SubViewport);245vc_2d->add_child(viewport_2d);246viewport_2d->set_disable_input(true);247viewport_2d->set_transparent_background(true);248249layout_2d = memnew(HBoxContainer);250layout_2d->set_alignment(BoxContainer::ALIGNMENT_CENTER);251viewport_2d->add_child(layout_2d);252layout_2d->set_anchors_and_offsets_preset(PRESET_FULL_RECT);253254rect_instance = memnew(ColorRect);255layout_2d->add_child(rect_instance);256rect_instance->set_custom_minimum_size(Size2(150, 150) * EDSCALE);257258layout_2d->set_visible(false);259260layout_error = memnew(VBoxContainer);261layout_error->set_alignment(BoxContainer::ALIGNMENT_CENTER);262layout_error->set_anchors_and_offsets_preset(PRESET_FULL_RECT);263264error_label = memnew(Label);265error_label->set_focus_mode(FOCUS_ACCESSIBILITY);266error_label->set_text(TTR("Preview is not available for this shader mode."));267error_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);268error_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);269error_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);270271layout_error->add_child(error_label);272layout_error->hide();273add_child(layout_error);274275// Spatial276277vc = memnew(SubViewportContainer);278vc->set_stretch(true);279add_child(vc);280vc->set_anchors_and_offsets_preset(PRESET_FULL_RECT);281viewport = memnew(SubViewport);282Ref<World3D> world_3d;283world_3d.instantiate();284viewport->set_world_3d(world_3d); // Use own world.285vc->add_child(viewport);286viewport->set_disable_input(true);287viewport->set_transparent_background(true);288viewport->set_msaa_3d(Viewport::MSAA_4X);289290camera = memnew(Camera3D);291camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 1.1)));292// Use low field of view so the sphere/box/quad is fully encompassed within the preview,293// without much distortion.294camera->set_perspective(20, 0.1, 10);295camera->make_current();296if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {297camera_attributes.instantiate();298camera->set_attributes(camera_attributes);299}300viewport->add_child(camera);301302light1 = memnew(DirectionalLight3D);303light1->set_transform(Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));304viewport->add_child(light1);305306light2 = memnew(DirectionalLight3D);307light2->set_transform(Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));308light2->set_color(Color(0.7, 0.7, 0.7));309viewport->add_child(light2);310311rotation = memnew(Node3D);312viewport->add_child(rotation);313314sphere_instance = memnew(MeshInstance3D);315rotation->add_child(sphere_instance);316317box_instance = memnew(MeshInstance3D);318rotation->add_child(box_instance);319320quad_instance = memnew(MeshInstance3D);321rotation->add_child(quad_instance);322323sphere_instance->set_transform(Transform3D() * 0.375);324box_instance->set_transform(Transform3D() * 0.25);325quad_instance->set_transform(Transform3D() * 0.375);326327sphere_mesh.instantiate();328sphere_instance->set_mesh(sphere_mesh);329box_mesh.instantiate();330box_instance->set_mesh(box_mesh);331quad_mesh.instantiate();332quad_instance->set_mesh(quad_mesh);333334layout_3d = memnew(HBoxContainer);335add_child(layout_3d);336layout_3d->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 2);337338VBoxContainer *vb_shape = memnew(VBoxContainer);339layout_3d->add_child(vb_shape);340341sphere_switch = memnew(Button);342sphere_switch->set_theme_type_variation("PreviewLightButton");343sphere_switch->set_toggle_mode(true);344sphere_switch->set_accessibility_name(TTRC("Sphere"));345vb_shape->add_child(sphere_switch);346sphere_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_sphere_switch_pressed));347348box_switch = memnew(Button);349box_switch->set_theme_type_variation("PreviewLightButton");350box_switch->set_toggle_mode(true);351box_switch->set_accessibility_name(TTRC("Box"));352vb_shape->add_child(box_switch);353box_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_box_switch_pressed));354355quad_switch = memnew(Button);356quad_switch->set_theme_type_variation("PreviewLightButton");357quad_switch->set_toggle_mode(true);358quad_switch->set_accessibility_name(TTRC("Quad"));359vb_shape->add_child(quad_switch);360quad_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_quad_switch_pressed));361362layout_3d->add_spacer();363364VBoxContainer *vb_light = memnew(VBoxContainer);365layout_3d->add_child(vb_light);366367light_1_switch = memnew(Button);368light_1_switch->set_theme_type_variation("PreviewLightButton");369light_1_switch->set_toggle_mode(true);370light_1_switch->set_pressed(true);371light_1_switch->set_accessibility_name(TTRC("First Light"));372vb_light->add_child(light_1_switch);373light_1_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_light_1_switch_pressed));374375light_2_switch = memnew(Button);376light_2_switch->set_theme_type_variation("PreviewLightButton");377light_2_switch->set_toggle_mode(true);378light_2_switch->set_pressed(true);379light_2_switch->set_accessibility_name(TTRC("Second Light"));380vb_light->add_child(light_2_switch);381light_2_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_light_2_switch_pressed));382383String shape = EditorSettings::get_singleton()->get_project_metadata("inspector_options", "material_preview_mesh", "sphere");384if (shape == "sphere") {385box_instance->hide();386quad_instance->hide();387sphere_switch->set_pressed_no_signal(true);388} else if (shape == "box") {389sphere_instance->hide();390quad_instance->hide();391box_switch->set_pressed_no_signal(true);392} else {393sphere_instance->hide();394box_instance->hide();395quad_switch->set_pressed_no_signal(true);396}397398Vector2 stored_rot = EditorSettings::get_singleton()->get_project_metadata("inspector_options", "material_preview_rotation", Vector2());399_set_rotation(stored_rot.x, stored_rot.y);400}401402///////////////////////403404bool EditorInspectorPluginMaterial::can_handle(Object *p_object) {405Material *material = Object::cast_to<Material>(p_object);406if (!material) {407return false;408}409Shader::Mode mode = material->get_shader_mode();410return mode == Shader::MODE_SPATIAL || mode == Shader::MODE_CANVAS_ITEM;411}412413void EditorInspectorPluginMaterial::parse_begin(Object *p_object) {414Material *material = Object::cast_to<Material>(p_object);415if (!material) {416return;417}418Ref<Material> m(material);419420MaterialEditor *editor = memnew(MaterialEditor);421editor->edit(m, env);422add_custom_control(editor);423}424425void EditorInspectorPluginMaterial::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, const String &p_property, const Variant &p_new_value) {426EditorUndoRedoManager *undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo);427ERR_FAIL_NULL(undo_redo);428429// For BaseMaterial3D, if a roughness or metallic textures is being assigned to an empty slot,430// set the respective metallic or roughness factor to 1.0 as a convenience feature431BaseMaterial3D *base_material = Object::cast_to<StandardMaterial3D>(p_edited);432if (base_material) {433Texture2D *texture = Object::cast_to<Texture2D>(p_new_value);434if (texture) {435if (p_property == "roughness_texture") {436if (base_material->get_texture(StandardMaterial3D::TEXTURE_ROUGHNESS).is_null()) {437undo_redo->add_do_property(p_edited, "roughness", 1.0);438439bool valid = false;440Variant value = p_edited->get("roughness", &valid);441if (valid) {442undo_redo->add_undo_property(p_edited, "roughness", value);443}444}445} else if (p_property == "metallic_texture") {446if (base_material->get_texture(StandardMaterial3D::TEXTURE_METALLIC).is_null()) {447undo_redo->add_do_property(p_edited, "metallic", 1.0);448449bool valid = false;450Variant value = p_edited->get("metallic", &valid);451if (valid) {452undo_redo->add_undo_property(p_edited, "metallic", value);453}454}455}456}457}458}459460EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() {461env.instantiate();462Ref<Sky> sky = memnew(Sky());463env->set_sky(sky);464env->set_background(Environment::BG_COLOR);465env->set_ambient_source(Environment::AMBIENT_SOURCE_SKY);466env->set_reflection_source(Environment::REFLECTION_SOURCE_SKY);467468EditorNode::get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &EditorInspectorPluginMaterial::_undo_redo_inspector_callback));469}470471MaterialEditorPlugin::MaterialEditorPlugin() {472Ref<EditorInspectorPluginMaterial> plugin;473plugin.instantiate();474add_inspector_plugin(plugin);475}476477String ParticleProcessMaterialConversionPlugin::converts_to() const {478return "ShaderMaterial";479}480481bool ParticleProcessMaterialConversionPlugin::handles(const Ref<Resource> &p_resource) const {482Ref<ParticleProcessMaterial> mat = p_resource;483return mat.is_valid();484}485486Ref<Resource> ParticleProcessMaterialConversionPlugin::convert(const Ref<Resource> &p_resource) const {487return MaterialEditor::make_shader_material(p_resource);488}489490String CanvasItemMaterialConversionPlugin::converts_to() const {491return "ShaderMaterial";492}493494bool CanvasItemMaterialConversionPlugin::handles(const Ref<Resource> &p_resource) const {495Ref<CanvasItemMaterial> mat = p_resource;496return mat.is_valid();497}498499Ref<Resource> CanvasItemMaterialConversionPlugin::convert(const Ref<Resource> &p_resource) const {500ERR_FAIL_COND_V(!Object::cast_to<CanvasItemMaterial>(*p_resource) || !Object::cast_to<CanvasItemMaterial>(*p_resource)->_is_initialized(), Ref<CanvasItemMaterial>());501return MaterialEditor::make_shader_material(p_resource);502}503504String BlitMaterialConversionPlugin::converts_to() const {505return "ShaderMaterial";506}507508bool BlitMaterialConversionPlugin::handles(const Ref<Resource> &p_resource) const {509Ref<BlitMaterial> mat = p_resource;510return mat.is_valid();511}512513Ref<Resource> BlitMaterialConversionPlugin::convert(const Ref<Resource> &p_resource) const {514Ref<BlitMaterial> mat = p_resource;515ERR_FAIL_COND_V(mat.is_null(), Ref<Resource>());516517Ref<ShaderMaterial> smat;518smat.instantiate();519520Ref<Shader> shader;521shader.instantiate();522523String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid());524525shader->set_code(code);526527smat->set_shader(shader);528529List<PropertyInfo> params;530RS::get_singleton()->get_shader_parameter_list(mat->get_shader_rid(), ¶ms);531532for (const PropertyInfo &E : params) {533Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name);534smat->set_shader_parameter(E.name, value);535}536537smat->set_render_priority(mat->get_render_priority());538smat->set_local_to_scene(mat->is_local_to_scene());539smat->set_name(mat->get_name());540return smat;541}542543544