3429 lines
148 KiB
Python
3429 lines
148 KiB
Python
from traceback import print_exc
|
||
from types import new_class
|
||
from typing import Hashable
|
||
|
||
from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton,
|
||
QTreeWidget, QTreeWidgetItem, QMenu,QCheckBox)
|
||
from PyQt5.QtCore import Qt
|
||
from panda3d.core import Vec3, Vec4, transpose
|
||
|
||
|
||
class PropertyPanelManager:
|
||
"""属性面板管理器"""
|
||
|
||
def __init__(self, world):
|
||
"""初始化属性面板管理器"""
|
||
self.world = world
|
||
self._propertyLayout = None
|
||
|
||
def setPropertyLayout(self, layout):
|
||
"""设置属性面板布局引用"""
|
||
print("开始设置属性布局")
|
||
print(f"布局类型: {type(layout)}")
|
||
|
||
# 保存布局引用
|
||
self._propertyLayout = layout
|
||
|
||
# 确保布局有父部件
|
||
if not layout.parent():
|
||
print("布局没有父部件,创建新的容器")
|
||
from PyQt5.QtWidgets import QWidget
|
||
container = QWidget()
|
||
container.setObjectName("PropertyContainer")
|
||
container.setLayout(layout)
|
||
|
||
print(f"布局父部件: {self._propertyLayout.parent().objectName() if self._propertyLayout.parent() else 'None'}")
|
||
print(f"布局项目数: {self._propertyLayout.count()}")
|
||
|
||
return True
|
||
|
||
def clearPropertyPanel(self):
|
||
"""清空属性面板"""
|
||
if self._propertyLayout:
|
||
while self._propertyLayout.count():
|
||
item = self._propertyLayout.takeAt(0)
|
||
if item.widget():
|
||
item.widget().deleteLater()
|
||
|
||
def updatePropertyPanel(self, item):
|
||
"""更新属性面板显示"""
|
||
if not self._propertyLayout or not self._propertyLayout.parent():
|
||
print("属性布局未设置或没有父部件!")
|
||
return
|
||
|
||
self.clearPropertyPanel()
|
||
|
||
itemText = item.text(0)
|
||
|
||
# 如果点击的是场景根节点,显示提示信息
|
||
if itemText == "场景":
|
||
tipLabel = QLabel("")
|
||
tipLabel.setStyleSheet("color: gray;")
|
||
self._propertyLayout.addRow(tipLabel)
|
||
return
|
||
|
||
# 创建通用属性
|
||
nameLabel = QLabel("名称:")
|
||
nameEdit = QLineEdit(itemText)
|
||
self._propertyLayout.addRow(nameLabel, nameEdit)
|
||
|
||
# 获取节点对象
|
||
model = item.data(0, Qt.UserRole)
|
||
|
||
# 检查是否是GUI元素
|
||
if model and hasattr(model, 'getTag') and model.getTag("gui_type"):
|
||
self.updateGUIPropertyPanel(model)
|
||
elif model and hasattr(model,'getTag') and model.getTag("light_type"):
|
||
self.updateLightPropertyPanel(model)
|
||
# 如果找到模型,显示其属性
|
||
elif model:
|
||
self._updateModelPropertyPanel(model)
|
||
# 显示脚本属性
|
||
self._updateScriptPropertyPanel(model)
|
||
|
||
# 强制更新布局
|
||
if self._propertyLayout:
|
||
self._propertyLayout.update()
|
||
propertyWidget = self._propertyLayout.parentWidget()
|
||
if propertyWidget:
|
||
propertyWidget.update()
|
||
|
||
def _updateModelPropertyPanel(self, model):
|
||
"""更新模型属性面板"""
|
||
# 获取父节点
|
||
parent = model.getParent()
|
||
|
||
# 位置属性(相对于父节点)
|
||
relativePos = model.getPos(parent) if parent else model.getPos()
|
||
|
||
xPos = QDoubleSpinBox()
|
||
xPos.setRange(-1000, 1000)
|
||
xPos.setValue(relativePos.getX())
|
||
xPos.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v))
|
||
self._propertyLayout.addRow("相对位置 X:", xPos)
|
||
|
||
yPos = QDoubleSpinBox()
|
||
yPos.setRange(-1000, 1000)
|
||
yPos.setValue(relativePos.getY())
|
||
yPos.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v))
|
||
self._propertyLayout.addRow("相对位置 Y:", yPos)
|
||
|
||
zPos = QDoubleSpinBox()
|
||
zPos.setRange(-1000, 1000)
|
||
zPos.setValue(relativePos.getZ())
|
||
zPos.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v))
|
||
self._propertyLayout.addRow("相对位置 Z:", zPos)
|
||
|
||
# 世界位置(只读)
|
||
worldPos = model.getPos(self.world.render)
|
||
worldXPos = QDoubleSpinBox()
|
||
worldXPos.setRange(-1000, 1000)
|
||
worldXPos.setValue(worldPos.getX())
|
||
worldXPos.setReadOnly(True)
|
||
self._propertyLayout.addRow("世界位置 X:", worldXPos)
|
||
|
||
worldYPos = QDoubleSpinBox()
|
||
worldYPos.setRange(-1000, 1000)
|
||
worldYPos.setValue(worldPos.getY())
|
||
worldYPos.setReadOnly(True)
|
||
self._propertyLayout.addRow("世界位置 Y:", worldYPos)
|
||
|
||
worldZPos = QDoubleSpinBox()
|
||
worldZPos.setRange(-1000, 1000)
|
||
worldZPos.setValue(worldPos.getZ())
|
||
worldZPos.setReadOnly(True)
|
||
self._propertyLayout.addRow("世界位置 Z:", worldZPos)
|
||
|
||
# 旋转属性
|
||
hRot = QDoubleSpinBox()
|
||
hRot.setRange(-180, 180)
|
||
hRot.setValue(model.getH())
|
||
hRot.valueChanged.connect(lambda v: model.setH(v))
|
||
self._propertyLayout.addRow("旋转 H:", hRot)
|
||
|
||
pRot = QDoubleSpinBox()
|
||
pRot.setRange(-180, 180)
|
||
pRot.setValue(model.getP())
|
||
pRot.valueChanged.connect(lambda v: model.setP(v))
|
||
self._propertyLayout.addRow("旋转 P:", pRot)
|
||
|
||
rRot = QDoubleSpinBox()
|
||
rRot.setRange(-180, 180)
|
||
rRot.setValue(model.getR())
|
||
rRot.valueChanged.connect(lambda v: model.setR(v))
|
||
self._propertyLayout.addRow("旋转 R:", rRot)
|
||
|
||
# 缩放属性
|
||
xScale = QDoubleSpinBox()
|
||
xScale.setRange(0.01, 100)
|
||
xScale.setSingleStep(0.1)
|
||
xScale.setValue(model.getScale().getX())
|
||
xScale.valueChanged.connect(lambda v: model.setScale(v, model.getScale().getY(), model.getScale().getZ()))
|
||
self._propertyLayout.addRow("缩放 X:", xScale)
|
||
|
||
yScale = QDoubleSpinBox()
|
||
yScale.setRange(0.01, 100)
|
||
yScale.setSingleStep(0.1)
|
||
yScale.setValue(model.getScale().getY())
|
||
yScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), v, model.getScale().getZ()))
|
||
self._propertyLayout.addRow("缩放 Y:", yScale)
|
||
|
||
zScale = QDoubleSpinBox()
|
||
zScale.setRange(0.01, 100)
|
||
zScale.setSingleStep(0.1)
|
||
zScale.setValue(model.getScale().getZ())
|
||
zScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v))
|
||
self._propertyLayout.addRow("缩放 Z:", zScale)
|
||
|
||
material_title = QLabel("材质属性")
|
||
material_title.setStyleSheet("color: #FF6B6B;font-weight:bold;font-size:14px;margin-top:10px;")
|
||
self._propertyLayout.addRow(material_title)
|
||
|
||
self._updateModelMaterialPanel(model)
|
||
|
||
def updateGUIPropertyPanel(self, gui_element):
|
||
"""更新GUI元素属性面板"""
|
||
gui_type = gui_element.getTag("gui_type")
|
||
gui_text = gui_element.getTag("gui_text")
|
||
|
||
# GUI类型显示
|
||
typeLabel = QLabel("GUI类型:")
|
||
typeValue = QLabel(gui_type)
|
||
typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;")
|
||
self._propertyLayout.addRow(typeLabel, typeValue)
|
||
|
||
# 文本属性(如果适用)
|
||
if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]:
|
||
textLabel = QLabel("文本:")
|
||
textEdit = QLineEdit(gui_text or "")
|
||
|
||
# 创建一个更新函数来处理文本变化
|
||
def updateText(text):
|
||
success = self.world.gui_manager.editGUIElement(gui_element, "text", text)
|
||
if success:
|
||
# 更新场景树显示的名称
|
||
self.world.scene_manager.updateSceneTree()
|
||
|
||
textEdit.textChanged.connect(updateText)
|
||
self._propertyLayout.addRow(textLabel, textEdit)
|
||
|
||
# 位置属性
|
||
if hasattr(gui_element, 'getPos'):
|
||
pos = gui_element.getPos()
|
||
|
||
# 根据GUI类型决定位置编辑方式
|
||
if gui_type in ["button", "label", "entry"]:
|
||
# 2D GUI组件使用屏幕坐标
|
||
logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
|
||
logical_z = pos.getZ() / 0.1
|
||
|
||
xPos = QDoubleSpinBox()
|
||
xPos.setRange(-50, 50)
|
||
xPos.setValue(logical_x)
|
||
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v))
|
||
self._propertyLayout.addRow("屏幕位置 X:", xPos)
|
||
|
||
zPos = QDoubleSpinBox()
|
||
zPos.setRange(-50, 50)
|
||
zPos.setValue(logical_z)
|
||
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v))
|
||
self._propertyLayout.addRow("屏幕位置 Z:", zPos)
|
||
|
||
# 显示实际屏幕坐标(只读)
|
||
actualXLabel = QLabel(f"{pos.getX():.3f}")
|
||
actualXLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||
self._propertyLayout.addRow("实际屏幕 X:", actualXLabel)
|
||
|
||
actualZLabel = QLabel(f"{pos.getZ():.3f}")
|
||
actualZLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||
self._propertyLayout.addRow("实际屏幕 Z:", actualZLabel)
|
||
|
||
else:
|
||
# 3D GUI组件使用世界坐标
|
||
xPos = QDoubleSpinBox()
|
||
xPos.setRange(-1000, 1000)
|
||
xPos.setValue(pos.getX())
|
||
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "position", [v, pos.getY(), pos.getZ()]))
|
||
self._propertyLayout.addRow("位置 X:", xPos)
|
||
|
||
yPos = QDoubleSpinBox()
|
||
yPos.setRange(-1000, 1000)
|
||
yPos.setValue(pos.getY())
|
||
yPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "position", [pos.getX(), v, pos.getZ()]))
|
||
self._propertyLayout.addRow("位置 Y:", yPos)
|
||
|
||
zPos = QDoubleSpinBox()
|
||
zPos.setRange(-1000, 1000)
|
||
zPos.setValue(pos.getZ())
|
||
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "position", [pos.getX(), pos.getY(), v]))
|
||
self._propertyLayout.addRow("位置 Z:", zPos)
|
||
|
||
# 缩放属性
|
||
if hasattr(gui_element, 'getScale'):
|
||
scale = gui_element.getScale()
|
||
|
||
scaleSpinBox = QDoubleSpinBox()
|
||
scaleSpinBox.setRange(0.01, 10)
|
||
scaleSpinBox.setSingleStep(0.1)
|
||
scaleSpinBox.setValue(scale.getX())
|
||
scaleSpinBox.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "scale", v))
|
||
self._propertyLayout.addRow("缩放:", scaleSpinBox)
|
||
|
||
# 颜色属性(针对2D GUI)
|
||
if gui_type in ["button", "label"]:
|
||
colorButton = QPushButton("选择颜色")
|
||
colorButton.clicked.connect(lambda: self.world.gui_manager.selectGUIColor(gui_element))
|
||
self._propertyLayout.addRow("背景颜色:", colorButton)
|
||
|
||
def _updateScriptPropertyPanel(self, game_object):
|
||
"""更新脚本属性面板"""
|
||
# 获取对象上的脚本
|
||
scripts = self.world.getScripts(game_object)
|
||
|
||
if scripts:
|
||
# 添加脚本信息标题
|
||
scriptTitleLabel = QLabel("已挂载脚本:")
|
||
scriptTitleLabel.setStyleSheet("color: #00AAFF; font-weight: bold; font-size: 12px;")
|
||
self._propertyLayout.addRow(scriptTitleLabel)
|
||
|
||
# 显示每个脚本的信息
|
||
for i, script_component in enumerate(scripts):
|
||
script_name = script_component.script_name
|
||
enabled = script_component.enabled
|
||
|
||
# 脚本名称和状态
|
||
scriptLabel = QLabel(f"脚本 {i+1}:")
|
||
scriptInfo = QLabel(f"{script_name}")
|
||
scriptInfo.setStyleSheet("color: green; font-weight: bold;" if enabled else "color: gray;")
|
||
self._propertyLayout.addRow(scriptLabel, scriptInfo)
|
||
|
||
# 脚本启用/禁用按钮
|
||
enableButton = QPushButton("禁用" if enabled else "启用")
|
||
enableButton.setStyleSheet(
|
||
"background-color: #FF6B6B; color: white;" if enabled
|
||
else "background-color: #4ECDC4; color: white;"
|
||
)
|
||
enableButton.clicked.connect(
|
||
lambda checked, sc=script_component: self._toggleScriptEnabled(sc)
|
||
)
|
||
self._propertyLayout.addRow("状态:", enableButton)
|
||
|
||
# 分隔线
|
||
if i < len(scripts) - 1:
|
||
separator = QLabel("─" * 20)
|
||
separator.setStyleSheet("color: lightgray;")
|
||
self._propertyLayout.addRow(separator)
|
||
else:
|
||
# 显示无脚本信息
|
||
noScriptLabel = QLabel("无挂载脚本")
|
||
noScriptLabel.setStyleSheet("color: gray; font-style: italic;")
|
||
self._propertyLayout.addRow("脚本:", noScriptLabel)
|
||
|
||
def _toggleScriptEnabled(self, script_component):
|
||
"""切换脚本启用状态"""
|
||
script_component.enabled = not script_component.enabled
|
||
status = "启用" if script_component.enabled else "禁用"
|
||
print(f"脚本 {script_component.script_name} 已{status}")
|
||
|
||
# 刷新属性面板显示
|
||
if hasattr(self.world.selection, 'selectedObject') and self.world.selection.selectedObject:
|
||
# 找到当前选中项并更新
|
||
tree_widget = self.world.treeWidget
|
||
if tree_widget and tree_widget.currentItem():
|
||
self.updatePropertyPanel(tree_widget.currentItem())
|
||
|
||
def updateLightPropertyPanel(self, model):
|
||
"""更新模型属性面板"""
|
||
|
||
light_object = model.getPythonTag("rp_light_object")
|
||
|
||
if light_object:
|
||
current_pos = light_object.pos
|
||
|
||
xPos = QDoubleSpinBox()
|
||
xPos.setRange(-1000, 1000)
|
||
xPos.setValue(current_pos.getX())
|
||
xPos.valueChanged.connect(lambda v: self._updateLightPosition(light_object, model, 'x', v))
|
||
self._propertyLayout.addRow("相对位置 X:", xPos)
|
||
|
||
yPos = QDoubleSpinBox()
|
||
yPos.setRange(-1000,1000)
|
||
yPos.setValue(current_pos.getY())
|
||
yPos.valueChanged.connect(lambda v:self._updateLightPosition(light_object,model,'y',v))
|
||
self._propertyLayout.addRow("相对位置 Y:",yPos)
|
||
|
||
zPos = QDoubleSpinBox()
|
||
zPos.setRange(-1000,1000)
|
||
zPos.setValue(current_pos.getZ())
|
||
zPos.valueChanged.connect(lambda v:self._updateLightPosition(light_object,model,'z',v))
|
||
self._propertyLayout.addRow("相对位置 Z:",zPos)
|
||
|
||
if hasattr(light_object,'direction'):
|
||
current_hpr = model.getHpr()
|
||
|
||
hRot = QDoubleSpinBox()
|
||
hRot.setRange(-180,180)
|
||
hRot.setValue(current_hpr.getX())
|
||
hRot.valueChanged.connect(lambda v:self._updateLightRotation(light_object,model,'h',v))
|
||
self._propertyLayout.addRow("旋转 H:",hRot)
|
||
|
||
pRot = QDoubleSpinBox()
|
||
pRot.setRange(-180,180)
|
||
pRot.setValue(current_hpr.getY())
|
||
pRot.valueChanged.connect(lambda v:self._updateLightRotation(light_object,model,'p',v))
|
||
self._propertyLayout.addRow("旋转 P:",pRot)
|
||
|
||
rRot = QDoubleSpinBox()
|
||
rRot.setRange(-180,180)
|
||
rRot.setValue(current_hpr.getZ())
|
||
rRot.valueChanged.connect(lambda v:self._updateLightRotation(light_object,model,'r',v))
|
||
self._propertyLayout.addRow("旋转 R:",rRot)
|
||
|
||
energySpinBox = QDoubleSpinBox()
|
||
energySpinBox.setRange(0,10000)
|
||
energySpinBox.setValue(light_object.energy)
|
||
energySpinBox.valueChanged.connect(lambda v:self._updateLightEnergy(light_object,v))
|
||
self._propertyLayout.addRow("能量:",energySpinBox)
|
||
|
||
radiusSpinBox = QDoubleSpinBox()
|
||
radiusSpinBox.setRange(1,2000)
|
||
radiusSpinBox.setValue(light_object.radius)
|
||
radiusSpinBox.valueChanged.connect(lambda v:self._updateLightRadius(light_object,v))
|
||
self._propertyLayout.addRow("半径:",radiusSpinBox)
|
||
|
||
if hasattr(light_object,'fov'):
|
||
fovSpinBox = QDoubleSpinBox()
|
||
fovSpinBox.setRange(1,180)
|
||
fovSpinBox.setValue(light_object.fov)
|
||
fovSpinBox.valueChanged.connect(lambda v:self._updateLightFOV(light_object,v))
|
||
self._propertyLayout.addRow("视野角度:",fovSpinBox)
|
||
|
||
shadowCheckBox = QCheckBox()
|
||
shadowCheckBox.setChecked(light_object.casts_shadows)
|
||
shadowCheckBox.stateChanged.connect(lambda state:self._updateLightCastsShadows(light_object,state==2))
|
||
self._propertyLayout.addRow("投射阴影:",shadowCheckBox)
|
||
|
||
current_scale = model.getScale()
|
||
|
||
xScaleSpinBox = QDoubleSpinBox()
|
||
xScaleSpinBox.setRange(0.01, 100)
|
||
xScaleSpinBox.setSingleStep(0.1)
|
||
xScaleSpinBox.setValue(current_scale.getX())
|
||
xScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'x', v))
|
||
self._propertyLayout.addRow("缩放 X:", xScaleSpinBox)
|
||
|
||
yScaleSpinBox = QDoubleSpinBox()
|
||
yScaleSpinBox.setRange(0.01, 100)
|
||
yScaleSpinBox.setSingleStep(0.1)
|
||
yScaleSpinBox.setValue(current_scale.getY())
|
||
yScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'y', v))
|
||
self._propertyLayout.addRow("缩放 Y:", yScaleSpinBox)
|
||
|
||
zScaleSpinBox = QDoubleSpinBox()
|
||
zScaleSpinBox.setRange(0.01, 100)
|
||
zScaleSpinBox.setSingleStep(0.1)
|
||
zScaleSpinBox.setValue(current_scale.getZ())
|
||
zScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'z', v))
|
||
self._propertyLayout.addRow("缩放 Z:", zScaleSpinBox)
|
||
|
||
|
||
|
||
|
||
|
||
# 获取父节点
|
||
|
||
#parent = model.getParent()
|
||
|
||
# 位置属性(相对于父节点)
|
||
#relativePos = model.getPos(parent) if parent else model.getPos()
|
||
|
||
# xPos = QDoubleSpinBox()
|
||
# xPos.setRange(-1000, 1000)
|
||
# xPos.setValue(relativePos.getX())
|
||
# xPos.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v))
|
||
# self._propertyLayout.addRow("相对位置 X:", xPos)
|
||
#print(f"{model} x :{model.getPos()}")
|
||
|
||
# yPos = QDoubleSpinBox()
|
||
# yPos.setRange(-1000, 1000)
|
||
# yPos.setValue(relativePos.getY())
|
||
# yPos.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v))
|
||
# self._propertyLayout.addRow("相对位置 Y:", yPos)
|
||
#
|
||
# zPos = QDoubleSpinBox()
|
||
# zPos.setRange(-1000, 1000)
|
||
# zPos.setValue(relativePos.getZ())
|
||
# zPos.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v))
|
||
# self._propertyLayout.addRow("相对位置 Z:", zPos)
|
||
|
||
# 世界位置(只读)
|
||
worldPos = model.getPos(self.world.render)
|
||
worldXPos = QDoubleSpinBox()
|
||
worldXPos.setRange(-1000, 1000)
|
||
worldXPos.setValue(worldPos.getX())
|
||
worldXPos.setReadOnly(True)
|
||
self._propertyLayout.addRow("世界位置 X:", worldXPos)
|
||
|
||
worldYPos = QDoubleSpinBox()
|
||
worldYPos.setRange(-1000, 1000)
|
||
worldYPos.setValue(worldPos.getY())
|
||
worldYPos.setReadOnly(True)
|
||
self._propertyLayout.addRow("世界位置 Y:", worldYPos)
|
||
|
||
worldZPos = QDoubleSpinBox()
|
||
worldZPos.setRange(-1000, 1000)
|
||
worldZPos.setValue(worldPos.getZ())
|
||
worldZPos.setReadOnly(True)
|
||
self._propertyLayout.addRow("世界位置 Z:", worldZPos)
|
||
|
||
|
||
|
||
# 旋转属性
|
||
# hRot = QDoubleSpinBox()
|
||
# hRot.setRange(-180, 180)
|
||
# hRot.setValue(model.getH())
|
||
# hRot.valueChanged.connect(lambda v: model.setH(v))
|
||
# self._propertyLayout.addRow("旋转 H:", hRot)
|
||
#
|
||
# pRot = QDoubleSpinBox()
|
||
# pRot.setRange(-180, 180)
|
||
# pRot.setValue(model.getP())
|
||
# pRot.valueChanged.connect(lambda v: model.setP(v))
|
||
# self._propertyLayout.addRow("旋转 P:", pRot)
|
||
#
|
||
# rRot = QDoubleSpinBox()
|
||
# rRot.setRange(-180, 180)
|
||
# rRot.setValue(model.getR())
|
||
# rRot.valueChanged.connect(lambda v: model.setR(v))
|
||
# self._propertyLayout.addRow("旋转 R:", rRot)
|
||
|
||
# 缩放属性
|
||
# xScale = QDoubleSpinBox()
|
||
# xScale.setRange(0.01, 100)
|
||
# xScale.setSingleStep(0.1)
|
||
# xScale.setValue(model.getScale().getX())
|
||
# xScale.valueChanged.connect(lambda v: model.setScale(v, model.getScale().getY(), model.getScale().getZ()))
|
||
# self._propertyLayout.addRow("缩放 X:", xScale)
|
||
#
|
||
# yScale = QDoubleSpinBox()
|
||
# yScale.setRange(0.01, 100)
|
||
# yScale.setSingleStep(0.1)
|
||
# yScale.setValue(model.getScale().getY())
|
||
# yScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), v, model.getScale().getZ()))
|
||
# self._propertyLayout.addRow("缩放 Y:", yScale)
|
||
#
|
||
# zScale = QDoubleSpinBox()
|
||
# zScale.setRange(0.01, 100)
|
||
# zScale.setSingleStep(0.1)
|
||
# zScale.setValue(model.getScale().getZ())
|
||
# zScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v))
|
||
# self._propertyLayout.addRow("缩放 Z:", zScale)
|
||
|
||
def _updateLightPosition(self,light_object,node_path,axis,value):
|
||
current_pos = light_object.pos
|
||
|
||
if axis=='x':
|
||
new_pos = Vec3(value,current_pos.getY(),current_pos.getZ())
|
||
elif axis == 'y':
|
||
new_pos = Vec3(current_pos.getX(), value, current_pos.getZ())
|
||
else: # z
|
||
new_pos = Vec3(current_pos.getX(), current_pos.getY(), value)
|
||
# 更新RenderPipeline光源位置
|
||
light_object.pos = new_pos
|
||
|
||
# 同步更新场景节点位置(用于显示)
|
||
node_path.setPos(new_pos)
|
||
|
||
def _updateLightRotation(self,light_object,node_path,axis,value):
|
||
"""更新光源旋转"""
|
||
from panda3d.core import Vec3
|
||
|
||
current_hpr = node_path.getHpr()
|
||
if axis=='h':
|
||
new_hpr = Vec3(value,current_hpr.getY(),current_hpr.getZ())
|
||
elif axis=='p':
|
||
new_hpr = Vec3(current_hpr.getX(),value,current_hpr.getZ())
|
||
else:
|
||
new_hpr = Vec3(current_hpr.getX(),current_hpr.getY(),value)
|
||
|
||
node_path.setHpr(new_hpr)
|
||
|
||
if hasattr(light_object,'direction'):
|
||
direction_mat = node_path.getMat()
|
||
new_direction = direction_mat.xformVec(Vec3(0,1,0))
|
||
light_object.direction = new_direction
|
||
|
||
print(f"光源旋转已更新:{axis}={value}")
|
||
|
||
def _updateLightEnergy(self,light_object,value):
|
||
"""更新光源强度"""
|
||
light_object.energy = value
|
||
|
||
def _updateLightRadius(self,light_object,value):
|
||
"""更新光源半径"""
|
||
light_object.radius = value
|
||
|
||
def _updateLightFOV(self,light_Object,value):
|
||
"""更新聚光灯视野角度"""
|
||
if hasattr(light_Object,'fov'):
|
||
light_Object.fov = value
|
||
|
||
def _updateLightTemperature(self,light_object,value):
|
||
"""更新光源色温"""
|
||
light_object.set_color_from_temperature(value)
|
||
#保存色温值以便下次显示
|
||
light_object._temperature=value
|
||
|
||
def _updateLightInnerRadius(self,light_object,value):
|
||
"""更新点光源内半径"""
|
||
if hasattr(light_object,'inner_radius'):
|
||
light_object.inner_radius=value
|
||
|
||
def _updateLightShaowResolution(self,light_object,value):
|
||
"""更新阴影分辨率"""
|
||
light_object.shadow_map_resolution = value
|
||
|
||
def _updateLightNearPlane(self,light_object,value):
|
||
"""更新近平面距离"""
|
||
light_object.near_plane = value
|
||
|
||
def _updateLightCastsShadows(self,light_object,casts_shadows):
|
||
"""更新光源是否投射阴影"""
|
||
light_object.casts_shadows = casts_shadows
|
||
|
||
def _updateLightScale(self,node_path,axis,value):
|
||
"""更新光源节点缩放"""
|
||
current_scale = node_path.getScale()
|
||
|
||
if axis=='x':
|
||
new_scale = Vec3(value,current_scale.getY(),current_scale.getZ())
|
||
elif axis=='y':
|
||
new_scale = Vec3(current_scale.getX(),value,current_scale.getZ())
|
||
else:
|
||
new_scale = Vec3(current_scale.getX(),current_scale.getY(),value)
|
||
|
||
node_path.setScale(new_scale)
|
||
|
||
def _generateUniqueMaterialNames(self, materials, model_name):
|
||
"""生成唯一的材质名称,避免重复"""
|
||
material_names = {}
|
||
unique_names = []
|
||
|
||
for i, material in enumerate(materials):
|
||
# 获取材质的原始名称
|
||
base_name = material.get_name() if hasattr(material, 'get_name') and material.get_name() else f"材质{i + 1}"
|
||
full_name = f"{base_name}:{model_name}"
|
||
|
||
# 检查是否重复
|
||
if full_name in material_names:
|
||
# 如果重复,增加计数器
|
||
material_names[full_name] += 1
|
||
unique_name = f"{full_name}_{material_names[full_name]}"
|
||
else:
|
||
# 首次出现,记录并使用原名
|
||
material_names[full_name] = 0
|
||
unique_name = full_name
|
||
|
||
unique_names.append(unique_name)
|
||
|
||
return unique_names
|
||
|
||
|
||
def _updateModelMaterialPanel(self,model):
|
||
"""模型材质属性"""
|
||
materials = model.find_all_materials()
|
||
|
||
if not materials:
|
||
no_material_label=QLabel("无材质")
|
||
no_material_label.setStyleSheet(("color: gray;font-style:italic;"))
|
||
self._propertyLayout.addRow("材质:",no_material_label)
|
||
return
|
||
|
||
model_name=model.getName() or "未命名模型"
|
||
|
||
name_counter = {}
|
||
|
||
# 创建材质到几何节点的映射字典
|
||
self._material_geom_mapping = {}
|
||
self._material_display_names = {}
|
||
|
||
for i,material in enumerate(materials):
|
||
# 查找使用该材质的几何节点,使用几何节点名称作为材质标题
|
||
geom_node = self._findSpecificGeomNodeWithMaterial(model, material)
|
||
|
||
if geom_node:
|
||
# 使用几何节点名称作为材质标题
|
||
geom_node_name = geom_node.getName()
|
||
unique_name = f"{geom_node_name}({model_name})"
|
||
print(f"材质 {i}: 使用几何节点名称 '{geom_node_name}'")
|
||
else:
|
||
# 回退到原有的材质名称逻辑
|
||
material_name = material.get_name() if hasattr(material,'get_name') and material.get_name() else f"材质{i + 1}"
|
||
unique_name = f"{material_name}({model_name})"
|
||
print(f"材质 {i}: 未找到几何节点,使用材质名称 '{material_name}'")
|
||
|
||
# 处理重复名称
|
||
if unique_name in name_counter:
|
||
name_counter[unique_name] += 1
|
||
display_name = f"{unique_name}_{name_counter[unique_name]}"
|
||
else:
|
||
name_counter[unique_name] = 1
|
||
display_name = unique_name
|
||
|
||
# 存储材质和对应的几何节点信息到映射字典中
|
||
material_id = id(material) # 使用材质对象的内存地址作为唯一标识
|
||
self._material_geom_mapping[material_id] = geom_node
|
||
self._material_display_names[material_id] = display_name
|
||
|
||
material_title = QLabel(display_name)
|
||
material_title.setStyleSheet("color:#00AAFF;font-weight:bold;font-size:12px")
|
||
self._propertyLayout.addRow(material_title)
|
||
|
||
# 检查材质类型并显示状态,但允许所有材质进行编辑
|
||
material_status = self._getMaterialStatus(material)
|
||
if material_status != "标准PBR材质":
|
||
status_label = QLabel(material_status)
|
||
status_label.setStyleSheet("color:#FFA500;font-style:italic;font-size:10px;")
|
||
self._propertyLayout.addRow("状态:", status_label)
|
||
|
||
# 移除了continue语句,让所有材质都可以编辑
|
||
|
||
# 基础颜色编辑(智能检查和创建)
|
||
base_color = self._getOrCreateMaterialBaseColor(material)
|
||
|
||
if base_color is not None:
|
||
print(f"材质基础颜色: {base_color}")
|
||
|
||
#R分量
|
||
r_spinbox = QDoubleSpinBox()
|
||
r_spinbox.setRange(0.0,1.0)
|
||
r_spinbox.setSingleStep(0.01)
|
||
r_spinbox.setValue(base_color.x)
|
||
r_spinbox.valueChanged.connect(lambda v,mat = material:self._updateMaterialBaseColor(mat,'r',v))
|
||
self._propertyLayout.addRow("基础颜色 R:",r_spinbox)
|
||
|
||
#G分量
|
||
g_spinbox = QDoubleSpinBox()
|
||
g_spinbox.setRange(0.0, 1.0)
|
||
g_spinbox.setSingleStep(0.01)
|
||
g_spinbox.setValue(base_color.y)
|
||
g_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'g', v))
|
||
self._propertyLayout.addRow("基础颜色 G:", g_spinbox)
|
||
|
||
# B分量
|
||
b_spinbox = QDoubleSpinBox()
|
||
b_spinbox.setRange(0.0, 1.0)
|
||
b_spinbox.setSingleStep(0.01)
|
||
b_spinbox.setValue(base_color.z)
|
||
b_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'b', v))
|
||
self._propertyLayout.addRow("基础颜色 B:", b_spinbox)
|
||
|
||
#Alpha分量(透明度)
|
||
alpha_spinbox = QDoubleSpinBox()
|
||
alpha_spinbox.setRange(0.0, 1.0)
|
||
alpha_spinbox.setSingleStep(0.01)
|
||
alpha_spinbox.setValue(base_color.w) # Alpha是Vec4的w分量
|
||
alpha_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'a', v))
|
||
self._propertyLayout.addRow("透明度 (Alpha):", alpha_spinbox)
|
||
else:
|
||
# 如果无法获取或创建基础颜色,显示提示
|
||
no_base_color_label = QLabel("无法获取材质基础颜色")
|
||
no_base_color_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;")
|
||
self._propertyLayout.addRow("基础颜色:", no_base_color_label)
|
||
|
||
# 粗糙度(安全检查)
|
||
if hasattr(material, 'roughness') and material.roughness is not None:
|
||
try:
|
||
roughness_value = float(material.roughness)
|
||
roughness_spinbox = QDoubleSpinBox()
|
||
roughness_spinbox.setRange(0.0, 1.0)
|
||
roughness_spinbox.setSingleStep(0.01)
|
||
roughness_spinbox.setValue(roughness_value)
|
||
roughness_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialRoughness(mat, v))
|
||
self._propertyLayout.addRow("粗糙度:", roughness_spinbox)
|
||
except (TypeError, ValueError) as e:
|
||
print(f"粗糙度值无效: {material.roughness}, 错误: {e}")
|
||
no_roughness_label = QLabel("粗糙度值无效,无法编辑")
|
||
no_roughness_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;")
|
||
self._propertyLayout.addRow("粗糙度:", no_roughness_label)
|
||
else:
|
||
no_roughness_label = QLabel("此材质不支持粗糙度编辑")
|
||
no_roughness_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;")
|
||
self._propertyLayout.addRow("粗糙度:", no_roughness_label)
|
||
|
||
|
||
|
||
# 金属性(安全检查)
|
||
if hasattr(material, 'metallic') and material.metallic is not None:
|
||
try:
|
||
metallic_value = float(material.metallic)
|
||
metallic_spinbox = QDoubleSpinBox()
|
||
metallic_spinbox.setRange(0.0, 1.0)
|
||
metallic_spinbox.setSingleStep(0.01)
|
||
metallic_spinbox.setValue(metallic_value)
|
||
metallic_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialMetallic(mat, v))
|
||
self._propertyLayout.addRow("金属性:", metallic_spinbox)
|
||
except (TypeError, ValueError) as e:
|
||
print(f"金属性值无效: {material.metallic}, 错误: {e}")
|
||
no_metallic_label = QLabel("金属性值无效,无法编辑")
|
||
no_metallic_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;")
|
||
self._propertyLayout.addRow("金属性:", no_metallic_label)
|
||
else:
|
||
no_metallic_label = QLabel("此材质不支持金属性编辑")
|
||
no_metallic_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;")
|
||
self._propertyLayout.addRow("金属性:", no_metallic_label)
|
||
|
||
# 折射率(安全检查)
|
||
if hasattr(material, 'refractive_index') and material.refractive_index is not None:
|
||
try:
|
||
ior_value = float(material.refractive_index)
|
||
ior_spinbox = QDoubleSpinBox()
|
||
ior_spinbox.setRange(1.0, 3.0)
|
||
ior_spinbox.setSingleStep(0.01)
|
||
ior_spinbox.setValue(ior_value)
|
||
ior_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialIOR(mat, v))
|
||
self._propertyLayout.addRow("折射率:", ior_spinbox)
|
||
except (TypeError, ValueError) as e:
|
||
print(f"折射率值无效: {material.refractive_index}, 错误: {e}")
|
||
no_ior_label = QLabel("折射率值无效,无法编辑")
|
||
no_ior_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;")
|
||
self._propertyLayout.addRow("折射率:", no_ior_label)
|
||
else:
|
||
no_ior_label = QLabel("此材质不支持折射率编辑")
|
||
no_ior_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;")
|
||
self._propertyLayout.addRow("折射率:", no_ior_label)
|
||
|
||
texture_title = QLabel("纹理贴图")
|
||
texture_title.setStyleSheet("color: #4CAF50; font-weight:bold;font-size:11px;margin-top:5px;")
|
||
self._propertyLayout.addRow(texture_title)
|
||
|
||
#漫反射贴图
|
||
diffuse_button = QPushButton("选择漫反射贴图")
|
||
diffuse_button.clicked.connect(lambda checked,title=unique_name:self._selectDiffuseTexture(title))
|
||
self._propertyLayout.addRow("漫反射贴图:",diffuse_button)
|
||
|
||
#法线贴图
|
||
normal_button = QPushButton("选择法线贴图")
|
||
normal_button.clicked.connect(lambda checked,mat=material:self._selectNormalTexture(mat))
|
||
self._propertyLayout.addRow("法线贴图:",normal_button)
|
||
|
||
#粗糙度贴图
|
||
roughness_button = QPushButton("选择粗糙度贴图")
|
||
roughness_button.clicked.connect(lambda checked,mat=material:self._selectRoughnessTexture((mat)))
|
||
self._propertyLayout.addRow("粗糙度贴图:",roughness_button)
|
||
|
||
#金属性贴图
|
||
metallic_button = QPushButton("选择金属性贴图")
|
||
metallic_button.clicked.connect(lambda checked,mat=material:self._selectMetallicTexture(mat))
|
||
self._propertyLayout.addRow("金属性贴图:",metallic_button)
|
||
|
||
#IOR贴图
|
||
ior_button = QPushButton("选择IOR贴图")
|
||
ior_button.clicked.connect(lambda checked,mat = material:self._selectIORTexture(mat))
|
||
self._propertyLayout.addRow("IOR贴图",ior_button)
|
||
|
||
# # 视差贴图
|
||
# parallax_button = QPushButton("选择视差贴图")
|
||
# parallax_button.clicked.connect(lambda checked, mat=material: self._selectParallaxTexture(mat))
|
||
# self._propertyLayout.addRow("视差贴图:", parallax_button)
|
||
#
|
||
# # 自发光贴图
|
||
# emission_button = QPushButton("选择自发光贴图")
|
||
# emission_button.clicked.connect(lambda checked, mat=material: self._selectEmissionTexture(mat))
|
||
# self._propertyLayout.addRow("自发光贴图:", emission_button)
|
||
#
|
||
# 环境光遮蔽贴图
|
||
ao_button = QPushButton("选择AO贴图")
|
||
ao_button.clicked.connect(lambda checked, mat=material: self._selectAOTexture(mat))
|
||
self._propertyLayout.addRow("AO贴图:", ao_button)
|
||
|
||
# # 透明度贴图
|
||
# alpha_button = QPushButton("选择透明度贴图")
|
||
# alpha_button.clicked.connect(lambda checked, mat=material: self._selectAlphaTexture(mat))
|
||
# self._propertyLayout.addRow("透明度贴图:", alpha_button)
|
||
#
|
||
# # 细节贴图
|
||
# detail_button = QPushButton("选择细节贴图")
|
||
# detail_button.clicked.connect(lambda checked, mat=material: self._selectDetailTexture(mat))
|
||
# self._propertyLayout.addRow("细节贴图:", detail_button)
|
||
#
|
||
# # 光泽贴图
|
||
# gloss_button = QPushButton("选择光泽贴图")
|
||
# gloss_button.clicked.connect(lambda checked, mat=material: self._selectGlossTexture(mat))
|
||
# self._propertyLayout.addRow("光泽贴图:", gloss_button)
|
||
|
||
|
||
|
||
# 显示当前贴图信息
|
||
self._displayCurrentTextures(material)
|
||
|
||
self._addShadingModelPanel(material)
|
||
self._addEmissionPanel(material)
|
||
self._addMaterialPresetPanel(material)
|
||
#self._addColorSpacePanel(material)
|
||
|
||
|
||
# 分隔线
|
||
if i < len(materials) - 1:
|
||
separator = QLabel("─" * 30)
|
||
separator.setStyleSheet("color: lightgray;")
|
||
self._propertyLayout.addRow(separator)
|
||
|
||
def _updateMaterialBaseColor(self, material, component, value):
|
||
"""更新材质基础颜色(智能版本)"""
|
||
try:
|
||
from panda3d.core import Vec4
|
||
|
||
# 获取当前颜色
|
||
current_color = self._getOrCreateMaterialBaseColor(material)
|
||
if current_color is None:
|
||
print(f"无法获取材质基础颜色,跳过更新")
|
||
return
|
||
|
||
# 计算新颜色
|
||
if component == 'r':
|
||
new_color = Vec4(value, current_color.y, current_color.z, current_color.w)
|
||
elif component == 'g':
|
||
new_color = Vec4(current_color.x, value, current_color.z, current_color.w)
|
||
elif component == 'b':
|
||
new_color = Vec4(current_color.x, current_color.y, value, current_color.w)
|
||
elif component == 'a': # Alpha分量处理
|
||
new_color = Vec4(current_color.x, current_color.y, current_color.z, value)
|
||
else:
|
||
print(f"未知的颜色分量: {component}")
|
||
return
|
||
|
||
# 尝试多种方式设置颜色
|
||
success = False
|
||
|
||
# 方法1: 使用set_base_color
|
||
if hasattr(material, 'set_base_color'):
|
||
try:
|
||
material.set_base_color(new_color)
|
||
print(f"✓ 通过set_base_color更新: {component}={value}")
|
||
success = True
|
||
except Exception as e:
|
||
print(f"set_base_color失败: {e}")
|
||
|
||
# 方法2: 使用setDiffuse作为备选
|
||
if not success and hasattr(material, 'setDiffuse'):
|
||
try:
|
||
material.setDiffuse(new_color)
|
||
print(f"✓ 通过setDiffuse更新: {component}={value}")
|
||
success = True
|
||
except Exception as e:
|
||
print(f"setDiffuse失败: {e}")
|
||
|
||
# 方法3: 直接设置属性
|
||
if not success and hasattr(material, 'base_color'):
|
||
try:
|
||
material.base_color = new_color
|
||
print(f"✓ 通过直接属性设置更新: {component}={value}")
|
||
success = True
|
||
except Exception as e:
|
||
print(f"直接属性设置失败: {e}")
|
||
|
||
if success:
|
||
self._invalidateRenderState()
|
||
print(f"材质基础颜色已更新: {new_color}")
|
||
else:
|
||
print(f"✗ 所有更新方法都失败了")
|
||
|
||
except Exception as e:
|
||
print(f"更新材质基础颜色失败: {e}")
|
||
|
||
def _updateMaterialRoughness(self, material, value):
|
||
"""更新材质粗糙度(安全版本)"""
|
||
try:
|
||
if not hasattr(material, 'roughness') or material.roughness is None:
|
||
print(f"材质不支持粗糙度属性或值为None,跳过更新")
|
||
return
|
||
material.set_roughness(value)
|
||
self._invalidateRenderState()
|
||
except Exception as e:
|
||
print(f"更新材质粗糙度失败: {e}")
|
||
|
||
def _updateMaterialMetallic(self, material, value):
|
||
"""更新材质金属性(安全版本)"""
|
||
try:
|
||
if not hasattr(material, 'metallic') or material.metallic is None:
|
||
print(f"材质不支持金属性属性或值为None,跳过更新")
|
||
return
|
||
material.set_metallic(value)
|
||
self._invalidateRenderState()
|
||
except Exception as e:
|
||
print(f"更新材质金属性失败: {e}")
|
||
|
||
def _updateMaterialIOR(self, material, value):
|
||
"""更新材质折射率(安全版本)"""
|
||
try:
|
||
if not hasattr(material, 'refractive_index') or material.refractive_index is None:
|
||
print(f"材质不支持折射率属性或值为None,跳过更新")
|
||
return
|
||
material.set_refractive_index(value)
|
||
self._invalidateRenderState()
|
||
except Exception as e:
|
||
print(f"更新材质折射率失败: {e}")
|
||
|
||
def _getMaterialStatus(self, material):
|
||
"""获取材质状态描述"""
|
||
try:
|
||
# 检查材质的各种属性
|
||
has_base_color = hasattr(material, 'has_base_color') and material.has_base_color()
|
||
has_roughness = hasattr(material, 'has_roughness') and material.has_roughness()
|
||
has_metallic = hasattr(material, 'has_metallic') and material.has_metallic()
|
||
has_ior = hasattr(material, 'has_refractive_index') and material.has_refractive_index()
|
||
|
||
# 检查基本属性是否存在
|
||
has_base_color_attr = hasattr(material, 'base_color')
|
||
has_roughness_attr = hasattr(material, 'roughness')
|
||
has_metallic_attr = hasattr(material, 'metallic')
|
||
has_ior_attr = hasattr(material, 'refractive_index')
|
||
|
||
if has_base_color and has_roughness and has_metallic and has_ior:
|
||
return "标准PBR材质"
|
||
elif has_base_color_attr and has_roughness_attr and has_metallic_attr:
|
||
return "PBR材质(部分属性可用)"
|
||
elif has_base_color_attr or has_roughness_attr or has_metallic_attr:
|
||
return "基础材质(支持部分PBR属性)"
|
||
else:
|
||
return "传统材质(可转换为PBR)"
|
||
|
||
except Exception as e:
|
||
print(f"检查材质状态时出错: {e}")
|
||
return "未知材质类型(可尝试编辑)"
|
||
|
||
def _invalidateRenderState(self):
|
||
"""使渲染状态失效以应用材质更改(无闪烁版本)"""
|
||
# 完全不做任何操作,避免闪烁
|
||
# 现代渲染管线会自动检测纹理变化并更新
|
||
print("材质更改已应用,无需手动刷新渲染状态")
|
||
|
||
|
||
|
||
def _getTextureModeString(self, mode):
|
||
"""获取纹理模式的字符串表示"""
|
||
from panda3d.core import TextureStage
|
||
mode_map = {
|
||
TextureStage.MModulate: "Modulate",
|
||
TextureStage.MDecal: "Decal",
|
||
TextureStage.MBlend: "Blend",
|
||
TextureStage.MReplace: "Replace",
|
||
TextureStage.MAdd: "Add",
|
||
TextureStage.MCombine: "Combine",
|
||
TextureStage.MBlendColorScale: "BlendColorScale",
|
||
TextureStage.MModulateGlow: "ModulateGlow",
|
||
TextureStage.MModulateGloss: "ModulateGloss",
|
||
TextureStage.MNormal: "Normal",
|
||
TextureStage.MNormalHeight: "NormalHeight",
|
||
TextureStage.MGlow: "Glow",
|
||
TextureStage.MGloss: "Gloss",
|
||
TextureStage.MHeight: "Height",
|
||
TextureStage.MSelector: "Selector",
|
||
TextureStage.MNormalGloss: "NormalGloss"
|
||
}
|
||
return mode_map.get(mode, f"Unknown({mode})")
|
||
|
||
def _checkAndAdjustMaterialProperty(self, material, property_name, current_value, texture_type):
|
||
"""检查并智能调整材质属性值"""
|
||
if current_value <= 0.01:
|
||
print(f"⚠️ 警告:材质{property_name}过低({current_value}),{texture_type}贴图可能无效果")
|
||
print(f" RenderPipeline使用公式: 最终{property_name} = 材质{property_name} × 贴图值")
|
||
print(f" 当前设置下,即使贴图为白色(1.0),最终效果也只有{current_value}")
|
||
|
||
# 询问用户是否要自动调整(在实际应用中,这里可以弹出对话框)
|
||
# 目前我们采用保守的自动调整策略
|
||
recommended_value = 0.8 # 推荐值
|
||
|
||
if property_name == "粗糙度":
|
||
material.set_roughness(recommended_value)
|
||
elif property_name == "金属性":
|
||
material.set_metallic(recommended_value)
|
||
|
||
print(f"✓ 已自动调整材质{property_name}为 {recommended_value}")
|
||
print(f" 现在{texture_type}贴图的效果范围:白色区域={recommended_value},黑色区域=0.0")
|
||
return recommended_value
|
||
else:
|
||
print(f"✓ 材质{property_name}合适: {current_value}")
|
||
print(f" {texture_type}贴图效果范围:白色区域={current_value:.2f},黑色区域=0.0")
|
||
return current_value
|
||
|
||
def _getOrCreateMaterialBaseColor(self, material):
|
||
"""智能获取或创建材质的基础颜色"""
|
||
from panda3d.core import Vec4
|
||
|
||
try:
|
||
# 方法1: 尝试获取base_color属性
|
||
if hasattr(material, 'base_color') and material.base_color is not None:
|
||
print(f"✓ 找到base_color属性: {material.base_color}")
|
||
return material.base_color
|
||
|
||
# 方法2: 尝试调用get_base_color方法
|
||
if hasattr(material, 'get_base_color'):
|
||
try:
|
||
base_color = material.get_base_color()
|
||
if base_color is not None:
|
||
print(f"✓ 通过get_base_color()获取: {base_color}")
|
||
return base_color
|
||
except:
|
||
pass
|
||
|
||
# 方法3: 尝试从diffuse颜色获取
|
||
if hasattr(material, 'getDiffuse'):
|
||
try:
|
||
diffuse_color = material.getDiffuse()
|
||
if diffuse_color is not None:
|
||
print(f"✓ 从diffuse颜色获取: {diffuse_color}")
|
||
# 同时设置为base_color
|
||
if hasattr(material, 'set_base_color'):
|
||
material.set_base_color(diffuse_color)
|
||
return diffuse_color
|
||
except:
|
||
pass
|
||
|
||
# 方法4: 尝试从ambient颜色获取
|
||
if hasattr(material, 'getAmbient'):
|
||
try:
|
||
ambient_color = material.getAmbient()
|
||
if ambient_color is not None:
|
||
print(f"✓ 从ambient颜色获取: {ambient_color}")
|
||
# 同时设置为base_color
|
||
if hasattr(material, 'set_base_color'):
|
||
material.set_base_color(ambient_color)
|
||
return ambient_color
|
||
except:
|
||
pass
|
||
|
||
# 方法5: 创建默认的基础颜色
|
||
print("⚠️ 未找到现有颜色,创建默认基础颜色")
|
||
default_color = Vec4(0.8, 0.8, 0.8, 1.0) # 默认灰白色
|
||
|
||
# 尝试设置到材质
|
||
if hasattr(material, 'set_base_color'):
|
||
material.set_base_color(default_color)
|
||
print(f"✓ 设置默认base_color: {default_color}")
|
||
elif hasattr(material, 'setDiffuse'):
|
||
material.setDiffuse(default_color)
|
||
print(f"✓ 设置默认diffuse: {default_color}")
|
||
|
||
return default_color
|
||
|
||
except Exception as e:
|
||
print(f"✗ 获取材质基础颜色失败: {e}")
|
||
return None
|
||
|
||
def _selectDiffuseTexture(self,material_title):
|
||
"""漫反射贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
import os
|
||
|
||
file_dialog = QFileDialog(None,"选择漫反射贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyDiffuseTexture(material_title,filename)
|
||
print(f"已选择漫反射贴图:{filename}")
|
||
|
||
def _selectNormalTexture(self,material):
|
||
"""选择法线贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialog = QFileDialog(None,"选择法线贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyNormalTexture(material,filename)
|
||
print(f"已选择法线贴图:{filename}")
|
||
|
||
def _selectRoughnessTexture(self,material):
|
||
"""选择粗糙度贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialog = QFileDialog(None,"选择粗糙度贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyRoughnessTexture_FINAL(material,filename)
|
||
print(f"已选择粗糙度贴图: {filename}")
|
||
|
||
def _selectMetallicTexture(self,material):
|
||
"""选择金属性贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialog = QFileDialog(None,"选择金属性贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyMetallicTexture_NEW(material,filename)
|
||
print(f"已选择金属性贴图: {filename}")
|
||
|
||
#IOR贴图
|
||
def _selectIORTexture(self,material):
|
||
"""选择IOR贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialong = QFileDialog(None,"选择IOR贴图","","图像(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialong.exec_():
|
||
filename = file_dialong.selectedFiles()[0]
|
||
if filename:
|
||
self._applyIORTexture(material,filename)
|
||
print(f"已选择IOR贴图:{filename}")
|
||
|
||
def _selectParallaxTexture(self,material):
|
||
"""选择视差贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialog = QFileDialog(None,"选择视差贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyParallaxTexture(material,filename)
|
||
print(f"已选择视差贴图:{filename}")
|
||
|
||
def _selectEmissionTexture(self,material):
|
||
"""选择自发光贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialog = QFileDialog(None,"选择自发光贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyEmissionTexture(material,filename)
|
||
print(f"已选择自发光贴图:{filename}")
|
||
|
||
def _selectAOTexture(self,material):
|
||
"""选择环境光遮蔽贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialog = QFileDialog(None,"选择AO贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyAOTexture(material,filename)
|
||
print(f"已选择AO贴图:{filename}")
|
||
|
||
def _selectAlphaTexture(self,material):
|
||
"""选择透明度贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialog = QFileDialog(None,"选择透明度贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyAlphaTexture(material,filename)
|
||
print(f"已选择透明度贴图:{filename}")
|
||
|
||
def _selectDetailTexture(self,material):
|
||
"""选择细节贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialog = QFileDialog(None,"选择细节贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyDetailTexture(material,filename)
|
||
print(f"已选择细节贴图:{filename}")
|
||
|
||
def _selectGlossTexture(self,material):
|
||
"""选择光泽贴图"""
|
||
from PyQt5.QtWidgets import QFileDialog
|
||
|
||
file_dialog = QFileDialog(None,"选择光泽贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
|
||
|
||
if file_dialog.exec_():
|
||
filename = file_dialog.selectedFiles()[0]
|
||
if filename:
|
||
self._applyGlossTexture(material,filename)
|
||
print(f"已选择光泽贴图:{filename}")
|
||
|
||
def _applyDiffuseTexture(self,material_title,texture_path):
|
||
"""应用漫反射贴图"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
#加载纹理
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if texture:
|
||
#获取材质所属的节点
|
||
material,node = self._findMaterialAndNodeByTitle(material_title)
|
||
if node and material:
|
||
print(f"正在为节点 {node.getName()} 应用漫反射贴图")
|
||
|
||
# 确保启用PBR效果(简单可靠版本)
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
"effects/default.yaml",
|
||
{
|
||
"normal_mapping": True, # 启用法线映射支持
|
||
"render_gbuffer": True,
|
||
"alpha_testing": True, # 启用透明度测试支持透明贴图
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100
|
||
)
|
||
print("✅ 默认PBR效果已应用(支持透明度)")
|
||
except Exception as e:
|
||
print(f"⚠️ PBR效果应用失败: {e}")
|
||
|
||
# 根据RenderPipeline的gbuffer.frag.glsl模板:
|
||
# p3d_Texture0 用于漫反射贴图 (line 111: texture(p3d_Texture0, texcoord).xyz)
|
||
|
||
# 清理可能存在的漫反射贴图
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if stage.getSort() == 0 or "diffuse" in stage.getName().lower():
|
||
node.clearTexture(stage)
|
||
print(f"清理了现有的漫反射贴图阶段: {stage.getName()}")
|
||
|
||
# 创建漫反射贴图纹理阶段,对应p3d_Texture0
|
||
diffuse_stage = TextureStage("diffuse")
|
||
diffuse_stage.setSort(0) # 对应p3d_Texture0
|
||
diffuse_stage.setMode(TextureStage.MModulate) # 标准的调制模式
|
||
|
||
# 应用漫反射贴图
|
||
node.setTexture(diffuse_stage, texture)
|
||
print("漫反射贴图已应用到p3d_Texture0槽")
|
||
|
||
# 调试信息:显示当前纹理阶段
|
||
print("=== 漫反射贴图应用后的纹理阶段信息 ===")
|
||
all_stages = node.findAllTextureStages()
|
||
for i, stage in enumerate(all_stages):
|
||
tex = node.getTexture(stage)
|
||
mode_name = self._getTextureModeString(stage.getMode())
|
||
print(f"阶段 {i}: {stage.getName()}, Sort: {stage.getSort()}, 模式: {mode_name}, 纹理: {tex.getName() if tex else 'None'}")
|
||
print("==========================================")
|
||
|
||
self._invalidateRenderState()
|
||
print(f"漫反射贴图已成功应用:{texture_path}")
|
||
else:
|
||
print(f"未找到材质标题对应的材质或节点: {material_title}")
|
||
else:
|
||
print("纹理加载失败")
|
||
except Exception as e:
|
||
print(f"应用漫反射贴图失败{e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
# def _applyNormalTexture(self, material, texture_path):
|
||
# """应用法线贴图"""
|
||
# try:
|
||
# from RenderPipelineFile.rpcore.loader import RPLoader
|
||
# from panda3d.core import TextureStage
|
||
#
|
||
# texture = RPLoader.load_texture(texture_path)
|
||
# if texture:
|
||
# node = self._findNodeWithMaterial(material)
|
||
# if node:
|
||
# # 创建法线贴图纹理阶段
|
||
# normal_stage = TextureStage("normal")
|
||
# normal_stage.setSort(1) # 设置排序优先级
|
||
# node.setTexture(normal_stage, texture)
|
||
# self._invalidateRenderState()
|
||
# print(f"法线贴图已应用:{texture_path}")
|
||
# else:
|
||
# print("未找到材质对应的节点")
|
||
# except Exception as e:
|
||
# print(f"应用法线贴图失败:{e}")
|
||
# import traceback
|
||
# traceback.print_exc()
|
||
|
||
def _applyNormalTexture(self, material, texture_path):
|
||
"""应用法线贴图 - Blender风格效果"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage, Vec4
|
||
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if not texture:
|
||
print("❌ 纹理加载失败")
|
||
return
|
||
|
||
# 查找使用该材质的具体几何节点
|
||
node = self._findSpecificGeomNodeForMaterial(material)
|
||
if not node:
|
||
print("❌ 未找到材质对应的节点")
|
||
return
|
||
|
||
# 清理现有的法线贴图,避免冲突
|
||
print("🧹 清理现有纹理阶段...")
|
||
existing_stages = node.findAllTextureStages()
|
||
has_diffuse_texture = False
|
||
|
||
for stage in existing_stages:
|
||
if "normal" in stage.getName().lower() or stage.getSort() == 1:
|
||
node.clearTexture(stage)
|
||
print(f" ✅ 已清理法线纹理阶段: {stage.getName()}")
|
||
elif stage.getSort() == 0: # 检查是否有漫反射贴图
|
||
tex = node.getTexture(stage)
|
||
if tex:
|
||
has_diffuse_texture = True
|
||
print(f" 🔍 发现现有漫反射贴图: {tex.getName()}")
|
||
|
||
# 如果没有漫反射贴图,必须创建白色纹理,否则法线映射会失效
|
||
if not has_diffuse_texture:
|
||
print("⚠️ 没有漫反射贴图,创建白色纹理确保法线映射正常工作...")
|
||
self._createWhiteDiffuseTexture(node)
|
||
|
||
# 使用默认PBR效果,强制启用法线映射
|
||
print("🔧 应用默认PBR效果(强制启用法线映射)...")
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
"effects/default.yaml",
|
||
{
|
||
"normal_mapping": True, # 强制启用法线映射
|
||
"render_gbuffer": True,
|
||
"alpha_testing": False,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100
|
||
)
|
||
print("✅ 默认PBR效果已应用(法线映射已启用)")
|
||
except Exception as e:
|
||
print(f"⚠️ PBR效果应用失败: {e}")
|
||
|
||
# 这里不需要检查roughness_stage,因为这是法线贴图方法
|
||
print("🔍 继续法线贴图处理...")
|
||
|
||
# 创建法线贴图纹理阶段,对应p3d_Texture1
|
||
print("🎨 创建法线纹理阶段...")
|
||
normal_stage = TextureStage("normal_map")
|
||
normal_stage.setSort(1) # 对应shader中的p3d_Texture1
|
||
normal_stage.setMode(TextureStage.MModulate) # 使用标准模式,不是MNormal
|
||
normal_stage.setTexcoordName("texcoord")
|
||
|
||
print(f"📋 法线纹理阶段信息:")
|
||
print(f" • 名称: {normal_stage.getName()}")
|
||
print(f" • 排序: {normal_stage.getSort()} (对应p3d_Texture1)")
|
||
print(f" • 模式: {normal_stage.getMode()} (MModulate)")
|
||
|
||
# 应用纹理到正确的纹理槽
|
||
node.setTexture(normal_stage, texture)
|
||
print(f"🔗 法线纹理已绑定到p3d_Texture1槽")
|
||
|
||
# 设置材质的normalfactor参数(用于法线强度)
|
||
current_emission = material.emission
|
||
if current_emission is None:
|
||
current_emission = Vec4(0, 0, 0, 0)
|
||
print("材质emission为None,使用默认值")
|
||
|
||
# emission.y 用于存储 normalfactor
|
||
new_emission = Vec4(current_emission.x, 1.0, current_emission.z, current_emission.w)
|
||
material.set_emission(new_emission)
|
||
print(f"🔧 设置法线强度参数: normalfactor = {new_emission.y}")
|
||
|
||
# 验证纹理应用
|
||
applied_texture = node.getTexture(normal_stage)
|
||
if applied_texture:
|
||
print(f"✅ 法线贴图成功应用到p3d_Texture1槽")
|
||
print(f"📊 纹理信息:")
|
||
print(f" • 纹理名称: {applied_texture.getName()}")
|
||
print(f" • 纹理尺寸: {applied_texture.getXSize()}x{applied_texture.getYSize()}")
|
||
print(f"📊 Blender风格效果:")
|
||
print(f" • 法线贴图将影响表面细节和光照")
|
||
print(f" • 不会改变材质颜色,只影响表面法线")
|
||
else:
|
||
print("❌ 纹理应用验证失败")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 应用法线贴图失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
|
||
def _applyRoughnessTexture_FINAL(self, material, texture_path):
|
||
"""应用粗糙度贴图 - 先编译后绑定策略"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
import time
|
||
|
||
print(f"🎨 应用粗糙度贴图(先编译后绑定): {texture_path}")
|
||
|
||
# 1. 加载纹理
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if not texture:
|
||
print("❌ 纹理加载失败")
|
||
return
|
||
|
||
# 2. 找到节点
|
||
node = self._findSpecificGeomNodeForMaterial(material)
|
||
if not node:
|
||
print("❌ 未找到材质对应的节点")
|
||
return
|
||
|
||
print(f"🎯 目标节点: {node.getName()}")
|
||
|
||
# 3. 设置材质粗糙度为1.0
|
||
material.set_roughness(1.0)
|
||
print("🔧 材质粗糙度设置为1.0")
|
||
|
||
# 4. 检查该材质有没有应用法线贴图,如果没有就先添加一个默认的法线贴图
|
||
print("🔧 步骤1:检查法线贴图...")
|
||
has_normal = self._hasNormalTexture(node)
|
||
|
||
if not has_normal:
|
||
print("⚠️ 检测到材质没有法线贴图,先添加默认法线贴图...")
|
||
#self._applyDefaultNormalTexture(node)
|
||
self._applyNormalTexture(material,"RenderPipelineFile/Default_NRM_2K.png")
|
||
print("✅ 默认法线贴图已添加")
|
||
else:
|
||
print("✅ 检测到材质已有法线贴图")
|
||
|
||
|
||
|
||
# 5. 检查是否有金属性贴图和透明漫反射贴图,选择合适的PBR效果
|
||
print("🔧 步骤2:检查金属性贴图和透明度设置...")
|
||
has_metallic = self._hasMetallicTexture(node)
|
||
needs_alpha = self._needsAlphaTesting(node)
|
||
|
||
if has_metallic:
|
||
print("✅ 检测到金属性贴图,使用支持金属性的PBR效果")
|
||
effect_file = "effects/pbr_with_metallic.yaml"
|
||
else:
|
||
print("✅ 没有金属性贴图,使用默认PBR效果")
|
||
effect_file = "effects/default.yaml"
|
||
|
||
print(f"🔍 透明度测试: {'需要' if needs_alpha else '不需要'}")
|
||
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
effect_file,
|
||
{
|
||
"normal_mapping": True, # 始终启用法线映射
|
||
"render_gbuffer": True,
|
||
"alpha_testing": needs_alpha, # 根据是否需要透明度决定
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100
|
||
)
|
||
print(f"✅ {effect_file} 效果已应用")
|
||
#print("✅ 着色器预编译完成")
|
||
|
||
# 5. 等待编译完成
|
||
#time.sleep(0.2) # 200ms等待
|
||
#print("⏱️ 等待着色器编译...")
|
||
|
||
# 6. 现在绑定纹理到已编译的着色器
|
||
#print("🔧 步骤2:绑定纹理到编译完成的着色器...")
|
||
# roughness_stage = TextureStage("roughness_map")
|
||
# roughness_stage.setSort(3) # p3d_Texture3
|
||
# roughness_stage.setMode(TextureStage.MModulate)
|
||
# node.setTexture(roughness_stage, texture)
|
||
#print("✅ 纹理已绑定到预编译着色器")
|
||
|
||
print("🧹 清理现有粗糙度贴图...")
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if stage.getSort() == 3: # p3d_Texture3槽
|
||
node.clearTexture(stage)
|
||
print(f" ✅ 已清理现有粗糙度贴图: {stage.getName()}")
|
||
|
||
# 添加验证和重试
|
||
for attempt in range(2): # 最多重试3次
|
||
time.sleep(0.1) # 短暂等待
|
||
roughness_stage = TextureStage("roughness_map")
|
||
roughness_stage.setSort(3) # p3d_Texture3
|
||
roughness_stage.setMode(TextureStage.MModulate)
|
||
node.setTexture(roughness_stage, texture)
|
||
|
||
|
||
# 7. 验证效果
|
||
applied_texture = node.getTexture(roughness_stage)
|
||
if applied_texture:
|
||
print(f"✅ 粗糙度贴图应用成功(先编译后绑定)")
|
||
print(f" 纹理: {applied_texture.getName()}")
|
||
print("🔍 应该立即看到正确的粗糙度效果")
|
||
else:
|
||
print("❌ 纹理绑定验证失败")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 应用粗糙度贴图失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _hasNormalTexture(self, node):
|
||
"""检查节点是否有法线贴图"""
|
||
try:
|
||
all_stages = node.findAllTextureStages()
|
||
for stage in all_stages:
|
||
if stage.getSort() == 1: # p3d_Texture1 是法线贴图槽
|
||
tex = node.getTexture(stage)
|
||
if tex:
|
||
print(f"🔍 发现法线贴图: {tex.getName()}")
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
print(f"⚠️ 检查法线贴图失败: {e}")
|
||
return False
|
||
|
||
def _applySmartPBREffect(self, node, effect_file="effects/default.yaml"):
|
||
"""智能应用PBR效果,自动检测是否需要启用法线映射"""
|
||
try:
|
||
# 检查是否有法线贴图
|
||
has_normal = self._hasNormalTexture(node)
|
||
|
||
print(f"🔧 应用智能PBR效果 ({effect_file})...")
|
||
print(f" 法线映射: {'启用' if has_normal else '禁用'}")
|
||
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
effect_file,
|
||
{
|
||
"normal_mapping": has_normal, # 根据是否有法线贴图决定
|
||
"render_gbuffer": True,
|
||
"alpha_testing": False,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100
|
||
)
|
||
print("✅ 智能PBR效果已应用")
|
||
return True
|
||
except Exception as e:
|
||
print(f"⚠️ 智能PBR效果应用失败: {e}")
|
||
return False
|
||
|
||
def _verifyTextureInShader(self, node, texture_type, expected_sort):
|
||
"""验证纹理是否在shader中正确处理"""
|
||
try:
|
||
print(f"🔍 验证{texture_type}纹理在shader中的处理...")
|
||
|
||
# 检查当前效果
|
||
current_effect = node.getEffect()
|
||
if current_effect:
|
||
print(f" 当前shader效果: {current_effect}")
|
||
else:
|
||
print(" ⚠️ 节点没有shader效果")
|
||
return False
|
||
|
||
# 检查纹理槽
|
||
stages = node.findAllTextureStages()
|
||
target_stage = None
|
||
for stage in stages:
|
||
if stage.getSort() == expected_sort:
|
||
target_stage = stage
|
||
break
|
||
|
||
if target_stage:
|
||
tex = node.getTexture(target_stage)
|
||
if tex:
|
||
print(f" ✅ 找到{texture_type}纹理在sort={expected_sort}槽: {tex.getName()}")
|
||
|
||
# 检查纹理数据
|
||
if tex.hasRamImage():
|
||
print(f" ✅ 纹理有RAM图像数据")
|
||
else:
|
||
print(f" ⚠️ 纹理没有RAM图像数据")
|
||
|
||
return True
|
||
else:
|
||
print(f" ❌ sort={expected_sort}槽没有纹理")
|
||
else:
|
||
print(f" ❌ 没有找到sort={expected_sort}的纹理阶段")
|
||
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f" ❌ 验证失败: {e}")
|
||
return False
|
||
|
||
def _ensureEnhancedPBREffect(self, node):
|
||
"""确保节点使用增强的PBR效果,支持金属性纹理"""
|
||
try:
|
||
print("🔧 应用金属性贴图支持的PBR效果...")
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
"effects/pbr_with_metallic.yaml",
|
||
{
|
||
"normal_mapping": False, # 关闭法线贴图避免干扰
|
||
"render_gbuffer": True, # 必须启用gbuffer渲染
|
||
"alpha_testing": False,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100 # 高优先级确保应用
|
||
)
|
||
print("✅ 金属性贴图PBR效果已应用")
|
||
return True
|
||
except Exception as e:
|
||
print(f"⚠️ 金属性贴图PBR效果失败: {e}")
|
||
# 回退到默认效果
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
"effects/default.yaml",
|
||
{
|
||
"normal_mapping": False,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": False,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100
|
||
)
|
||
print("✅ 已回退到默认PBR效果(不支持金属性贴图)")
|
||
return True
|
||
except Exception as e2:
|
||
print(f"⚠️ 默认效果也失败: {e2}")
|
||
return False
|
||
|
||
def _createWhiteDiffuseTexture(self, node):
|
||
"""创建白色漫反射纹理,确保法线贴图能正常工作"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
print("🎨 尝试加载白色纹理...")
|
||
|
||
# 尝试加载一个白色纹理文件,如果不存在就创建
|
||
white_texture = None
|
||
|
||
# 方法1:尝试从RenderPipeline加载空白基础颜色纹理(应该是白色)
|
||
try:
|
||
white_texture = RPLoader.load_texture("RenderPipelineFile/data/empty_textures/empty_basecolor.png")
|
||
if white_texture:
|
||
print("✅ 从RenderPipeline空白纹理加载白色纹理")
|
||
except Exception as e1:
|
||
print(f"⚠️ 加载空白纹理失败: {e1}")
|
||
pass
|
||
|
||
# 方法2:如果没有默认纹理,创建简单的白色纹理
|
||
if not white_texture:
|
||
try:
|
||
from panda3d.core import Texture
|
||
white_texture = Texture("white_diffuse")
|
||
white_texture.setup2dTexture(1, 1, Texture.TUnsignedByte, Texture.FRgb)
|
||
# 设置白色像素数据
|
||
white_data = b'\xff\xff\xff'
|
||
white_texture.setRamImage(white_data)
|
||
print("✅ 创建了程序生成的白色纹理")
|
||
except Exception as e2:
|
||
print(f"⚠️ 创建程序纹理也失败: {e2}")
|
||
return False
|
||
|
||
if white_texture:
|
||
# 创建漫反射纹理阶段
|
||
diffuse_stage = TextureStage("white_diffuse")
|
||
diffuse_stage.setSort(0) # 对应p3d_Texture0
|
||
diffuse_stage.setMode(TextureStage.MModulate)
|
||
|
||
# 应用白色纹理
|
||
node.setTexture(diffuse_stage, white_texture)
|
||
print("✅ 白色漫反射纹理已应用,材质颜色将正常显示")
|
||
return True
|
||
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 创建白色纹理失败: {e}")
|
||
return False
|
||
|
||
def _applyMetallicTexture(self,material,texture_path):
|
||
"""应用金属性贴图 - Blender风格效果"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
print(f"🎨 应用金属性贴图: {texture_path}")
|
||
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if not texture:
|
||
print("❌ 纹理加载失败")
|
||
return
|
||
|
||
# 查找使用该材质的具体几何节点
|
||
node = self._findSpecificGeomNodeForMaterial(material)
|
||
if not node:
|
||
print("❌ 未找到材质对应的节点")
|
||
return
|
||
|
||
print(f"🎯 目标节点: {node.getName()}")
|
||
|
||
# 保持材质金属性为1.0,让贴图完全控制
|
||
material.set_metallic(1.0)
|
||
print("🔧 材质金属性设置为1.0,启用贴图完全控制")
|
||
|
||
# 清理现有的金属性贴图,避免冲突
|
||
print("🧹 清理现有纹理阶段...")
|
||
existing_stages = node.findAllTextureStages()
|
||
print(f"🔍 发现 {len(existing_stages)} 个现有纹理阶段:")
|
||
|
||
for stage in existing_stages:
|
||
tex = node.getTexture(stage)
|
||
tex_name = tex.getName() if tex else "无纹理"
|
||
print(f" - {stage.getName()} (sort={stage.getSort()}) -> {tex_name}")
|
||
|
||
# 只清理金属性相关的纹理阶段
|
||
if "metallic" in stage.getName().lower() or stage.getSort() == 5:
|
||
node.clearTexture(stage)
|
||
print(f" ✅ 已清理金属性纹理阶段: {stage.getName()}")
|
||
|
||
# 确保没有纹理占用sort=5的槽位
|
||
sort5_stages = [s for s in existing_stages if s.getSort() == 5]
|
||
if sort5_stages:
|
||
print(f"⚠️ 发现占用sort=5槽位的纹理: {[s.getName() for s in sort5_stages]}")
|
||
for stage in sort5_stages:
|
||
node.clearTexture(stage)
|
||
print(f" ✅ 已强制清理: {stage.getName()}")
|
||
|
||
# 使用调试模式来验证金属性纹理是否正确读取
|
||
debug_texture_mode = True # 设为True来调试纹理读取
|
||
|
||
print(f"🔧 应用{'调试' if debug_texture_mode else '增强'}PBR效果...")
|
||
|
||
try:
|
||
if debug_texture_mode:
|
||
# 使用调试效果:将金属性纹理显示为颜色
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
"effects/test_metallic_debug.yaml",
|
||
{
|
||
"normal_mapping": False,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": False,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100
|
||
)
|
||
print("✅ 调试效果已应用 - 金属性纹理将显示为蓝黄色变化")
|
||
print(" 蓝色=非金属区域, 黄色=金属区域")
|
||
print(" 如果看到颜色变化,说明纹理读取正确")
|
||
print(" 如果看到统一颜色,说明纹理没有正确绑定")
|
||
else:
|
||
# 使用专门的金属性效果,避免与漫反射贴图混淆
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
"effects/metallic_only.yaml",
|
||
{
|
||
"normal_mapping": False, # 关闭法线贴图避免干扰
|
||
"render_gbuffer": True, # 必须启用gbuffer渲染
|
||
"alpha_testing": False,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100 # 高优先级确保应用
|
||
)
|
||
print("✅ 专门的金属性贴图效果已应用")
|
||
|
||
# 验证效果是否正确应用
|
||
current_effect = node.getEffect()
|
||
if current_effect:
|
||
print(f"🔍 当前效果: {current_effect}")
|
||
else:
|
||
print("⚠️ 效果应用后为空,可能有问题")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 专门的金属性效果失败: {e}")
|
||
print("🔄 回退到直接控制金属性效果...")
|
||
# 回退到直接控制效果
|
||
try:
|
||
self._ensurePBREffectEnabledWithDirectMetallic(node)
|
||
print("✅ 直接控制金属性效果已应用")
|
||
except Exception as e2:
|
||
print(f"⚠️ 直接控制效果也失败: {e2}")
|
||
# 最后的备选方案:使用稳定效果
|
||
try:
|
||
self._ensurePBREffectEnabledStable(node)
|
||
print("🔄 已应用稳定PBR效果")
|
||
except:
|
||
pass
|
||
|
||
# 创建金属性贴图阶段,对应p3d_Texture5
|
||
print("🎨 创建金属性纹理阶段...")
|
||
metallic_stage = TextureStage("metallic_map")
|
||
metallic_stage.setSort(5) # 对应shader中的p3d_Texture5
|
||
|
||
# 重要:设置正确的纹理模式
|
||
# MModulate模式用于金属性贴图,不会影响颜色
|
||
metallic_stage.setMode(TextureStage.MModulate)
|
||
|
||
# 确保纹理坐标正确
|
||
metallic_stage.setTexcoordName("texcoord")
|
||
|
||
print(f"📋 金属性纹理阶段信息:")
|
||
print(f" • 名称: {metallic_stage.getName()}")
|
||
print(f" • 排序: {metallic_stage.getSort()} (对应p3d_Texture5)")
|
||
print(f" • 模式: {metallic_stage.getMode()} (MReplace)")
|
||
print(f" • 纹理坐标: {metallic_stage.getTexcoordName()}")
|
||
|
||
# 应用纹理到正确的纹理槽
|
||
node.setTexture(metallic_stage, texture)
|
||
print(f"🔗 金属性纹理已绑定到p3d_Texture5槽")
|
||
|
||
# 强制刷新渲染状态
|
||
node.setRenderModeWireframe()
|
||
node.clearRenderMode()
|
||
print(f"🔄 已刷新渲染状态")
|
||
|
||
# 验证纹理应用
|
||
applied_texture = node.getTexture(metallic_stage)
|
||
if applied_texture:
|
||
print(f"✅ 金属性贴图成功应用到p3d_Texture5槽")
|
||
print(f"📊 纹理信息:")
|
||
print(f" • 纹理名称: {applied_texture.getName()}")
|
||
print(f" • 纹理尺寸: {applied_texture.getXSize()}x{applied_texture.getYSize()}")
|
||
print(f"📊 Blender风格效果:")
|
||
print(f" • 白色区域 = 完全金属 (1.0)")
|
||
print(f" • 黑色区域 = 非金属 (0.0)")
|
||
print(f" • 灰色区域 = 部分金属 (0.5)")
|
||
print(f" • 公式: 最终金属性 = 贴图值")
|
||
|
||
# 列出节点上的所有纹理阶段
|
||
print(f"🔍 节点上的所有纹理阶段:")
|
||
all_stages = node.findAllTextureStages()
|
||
for i, stage in enumerate(all_stages):
|
||
tex = node.getTexture(stage)
|
||
tex_name = tex.getName() if tex else "无纹理"
|
||
print(f" {i}: {stage.getName()} (sort={stage.getSort()}) -> {tex_name}")
|
||
|
||
else:
|
||
print("❌ 纹理应用验证失败")
|
||
print("🔍 尝试诊断问题...")
|
||
|
||
# 检查纹理是否有效
|
||
if texture:
|
||
print(f" 纹理对象有效: {texture.getName()}")
|
||
else:
|
||
print(" 纹理对象无效")
|
||
|
||
# 检查节点是否有效
|
||
if node:
|
||
print(f" 节点对象有效: {node.getName()}")
|
||
else:
|
||
print(" 节点对象无效")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 应用金属性贴图失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _applyIORTexture(self,material,texture_path):
|
||
"""应用IOR贴图到特定材质"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if texture:
|
||
# 查找使用该材质的具体几何节点
|
||
node = self._findSpecificGeomNodeForMaterial(material)
|
||
if node:
|
||
print(f"正在为节点 {node.getName()} 应用IOR贴图")
|
||
|
||
# 确保启用PBR效果
|
||
self._ensurePBREffectEnabled(node)
|
||
|
||
# 根据RenderPipeline的gbuffer.frag.glsl模板:
|
||
# p3d_Texture2 用于IOR贴图 (line 87: texture(p3d_Texture2, texcoord).x)
|
||
|
||
# 清理现有的IOR贴图
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if "ior" in stage.getName().lower() or stage.getSort() == 2:
|
||
node.clearTexture(stage)
|
||
print(f"清理了现有的IOR贴图阶段: {stage.getName()}")
|
||
|
||
# 创建IOR贴图纹理阶段,对应p3d_Texture2
|
||
ior_stage = TextureStage("ior")
|
||
ior_stage.setSort(2) # 对应p3d_Texture2
|
||
ior_stage.setMode(TextureStage.MModulate)
|
||
|
||
node.setTexture(ior_stage,texture)
|
||
print("IOR贴图已应用到p3d_Texture2槽")
|
||
|
||
# 不再需要手动刷新渲染状态,避免闪烁
|
||
print(f"IOR贴图已成功应用:{texture_path}")
|
||
else:
|
||
print("未找到材质对应的节点")
|
||
except Exception as e:
|
||
print(f"应用IOR贴图失败:{e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _applyParallaxTexture(self,material,texture_path):
|
||
"""应用视差贴图"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if texture:
|
||
node = self._findNodeWithMaterial(material)
|
||
if node:
|
||
print(f"正在为节点 {node.getName()} 应用视差贴图")
|
||
|
||
# 确保启用PBR效果,包括视差映射
|
||
self._ensurePBREffectEnabledWithParallax(node)
|
||
|
||
# 根据RenderPipeline的gbuffer.frag.glsl模板:
|
||
# p3d_Texture4 用于视差贴图 (line 77: get_parallax_texcoord(p3d_Texture4, mInput.normalfactor))
|
||
|
||
# 清理现有的视差贴图
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if "parallax" in stage.getName().lower() or stage.getSort() == 4:
|
||
node.clearTexture(stage)
|
||
print(f"清理了现有的视差贴图阶段: {stage.getName()}")
|
||
|
||
# 创建视差贴图纹理阶段,对应p3d_Texture4
|
||
parallax_stage = TextureStage("parallax")
|
||
parallax_stage.setSort(4) # 对应p3d_Texture4
|
||
parallax_stage.setMode(TextureStage.MHeight) # 高度贴图模式
|
||
|
||
node.setTexture(parallax_stage,texture)
|
||
print("视差贴图已应用到p3d_Texture4槽")
|
||
|
||
self._invalidateRenderState()
|
||
print(f"视差贴图已成功应用:{texture_path}")
|
||
else:
|
||
print("未找到材质对应节点")
|
||
except Exception as e:
|
||
print(f"应用视差贴图失败:{e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _ensureNormalMappingEnabled(self,model):
|
||
"""确保模型启用了法线映射功能"""
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/default.yaml",
|
||
{
|
||
"normal_mapping":True,
|
||
"render_gbuffer":True,
|
||
"alpha_testing":True
|
||
},
|
||
30
|
||
)
|
||
print(f"已为模型{model.getName()}启用法线映射")
|
||
except Exception as e:
|
||
print(f"设置法线映射效果失败:{e}")
|
||
|
||
def _ensurePBREffectEnabled(self, model):
|
||
"""确保模型启用了完整的PBR效果,包括法线映射"""
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/default.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": True,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
30
|
||
)
|
||
print(f"已为模型{model.getName()}启用PBR效果")
|
||
except Exception as e:
|
||
print(f"设置PBR效果失败:{e}")
|
||
|
||
def _ensurePBREffectEnabledWithParallax(self, model):
|
||
"""确保模型启用了完整的PBR效果,包括视差映射"""
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/default.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": True,
|
||
"parallax_mapping": True, # 启用视差映射
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
30
|
||
)
|
||
print(f"已为模型{model.getName()}启用PBR效果(包括视差映射)")
|
||
except Exception as e:
|
||
print(f"设置PBR效果失败:{e}")
|
||
|
||
def _ensurePBREffectEnabledWithMetallic(self, model):
|
||
"""确保模型启用了支持金属性贴图的PBR效果"""
|
||
try:
|
||
# 首先尝试使用自定义的金属性贴图效果
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/pbr_with_metallic.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": True,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
30
|
||
)
|
||
print(f"已为模型{model.getName()}启用支持金属性贴图的PBR效果")
|
||
except Exception as e1:
|
||
print(f"自定义金属性效果失败,使用标准PBR效果: {e1}")
|
||
# 回退到标准PBR效果
|
||
self._ensurePBREffectEnabled(model)
|
||
except Exception as e:
|
||
print(f"设置PBR效果失败:{e}")
|
||
|
||
def _ensurePBREffectEnabledWithDirectMetallic(self, model):
|
||
"""确保模型启用了支持金属性贴图直接控制的PBR效果"""
|
||
try:
|
||
# 首先尝试使用直接控制金属性贴图的效果
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/pbr_direct_metallic.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": True,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
30
|
||
)
|
||
print(f"✅ 已为模型{model.getName()}启用直接控制金属性贴图的PBR效果")
|
||
except Exception as e1:
|
||
print(f"⚠️ 直接控制金属性效果失败,使用标准金属性效果: {e1}")
|
||
# 回退到标准金属性贴图效果
|
||
self._ensurePBREffectEnabledWithMetallic(model)
|
||
except Exception as e:
|
||
print(f"设置PBR效果失败:{e}")
|
||
|
||
def _onMetallicModeChanged(self, material, mode_index):
|
||
"""处理金属性控制模式变化"""
|
||
try:
|
||
mode_names = ["乘法模式", "直接控制模式", "叠加模式"]
|
||
print(f"🔧 金属性控制模式已切换为: {mode_names[mode_index]}")
|
||
|
||
# 查找使用该材质的节点
|
||
node = self._findSpecificGeomNodeForMaterial(material)
|
||
if node:
|
||
if mode_index == 0:
|
||
# 乘法模式:使用标准金属性贴图效果
|
||
self._ensurePBREffectEnabledWithMetallic(node)
|
||
print(" 📊 效果:最终金属性 = 材质金属性 × 贴图值")
|
||
print(" 💡 适用于:微调现有材质的金属性分布")
|
||
elif mode_index == 1:
|
||
# 直接控制模式:使用直接控制效果
|
||
self._ensurePBREffectEnabledWithDirectMetallic(node)
|
||
print(" 📊 效果:最终金属性 = 贴图值")
|
||
print(" 💡 适用于:贴图完全控制金属性分布")
|
||
elif mode_index == 2:
|
||
# 叠加模式:使用叠加效果
|
||
self._ensurePBREffectEnabledWithAdditiveMetallic(node)
|
||
print(" 📊 效果:最终金属性 = 材质金属性 + 贴图值 (限制在0-1)")
|
||
print(" 💡 适用于:在材质基础上增加金属性区域")
|
||
|
||
print(f"✅ 金属性控制模式已应用到节点: {node.getName()}")
|
||
else:
|
||
print("⚠️ 未找到材质对应的节点")
|
||
|
||
except Exception as e:
|
||
print(f"切换金属性控制模式失败: {e}")
|
||
|
||
def _ensurePBREffectEnabledWithAdditiveMetallic(self, model):
|
||
"""确保模型启用了支持金属性贴图叠加控制的PBR效果"""
|
||
try:
|
||
# 首先尝试使用叠加控制金属性贴图的效果
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/pbr_additive_metallic.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": True,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
30
|
||
)
|
||
print(f"✅ 已为模型{model.getName()}启用叠加控制金属性贴图的PBR效果")
|
||
except Exception as e1:
|
||
print(f"⚠️ 叠加控制金属性效果失败,使用直接控制效果: {e1}")
|
||
# 回退到直接控制金属性贴图效果
|
||
self._ensurePBREffectEnabledWithDirectMetallic(model)
|
||
except Exception as e:
|
||
print(f"设置PBR效果失败:{e}")
|
||
|
||
def _ensurePBREffectEnabledWithEmission(self, model):
|
||
"""确保模型启用了支持自发光贴图的PBR效果"""
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/pbr_with_emission.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": True,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
30
|
||
)
|
||
print(f"已为模型{model.getName()}启用支持自发光贴图的PBR效果")
|
||
except Exception as e:
|
||
print(f"自定义自发光效果失败,使用标准PBR效果: {e}")
|
||
# 回退到标准PBR效果
|
||
self._ensurePBREffectEnabled(model)
|
||
|
||
def _ensurePBREffectEnabledWithAlpha(self, model):
|
||
"""确保模型启用了支持透明度的PBR效果"""
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/default.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": False, # 透明物体不渲染到GBuffer
|
||
"render_forward": True, # 使用前向渲染
|
||
"alpha_testing": True,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
30
|
||
)
|
||
print(f"已为模型{model.getName()}启用支持透明度的PBR效果")
|
||
except Exception as e:
|
||
print(f"设置透明度PBR效果失败: {e}")
|
||
# 回退到标准PBR效果
|
||
self._ensurePBREffectEnabled(model)
|
||
|
||
def _ensurePBREffectEnabledWithRoughness(self, model):
|
||
"""确保模型启用了支持粗糙度贴图的PBR效果"""
|
||
try:
|
||
# 首先尝试使用自定义的粗糙度贴图效果
|
||
try:
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/pbr_with_roughness.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": True,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
30
|
||
)
|
||
print(f"已为模型{model.getName()}启用支持粗糙度贴图的PBR效果")
|
||
except Exception as e1:
|
||
print(f"自定义粗糙度效果失败,使用标准PBR效果: {e1}")
|
||
# 回退到标准PBR效果
|
||
self._ensurePBREffectEnabled(model)
|
||
except Exception as e:
|
||
print(f"设置PBR效果失败:{e}")
|
||
|
||
def _ensurePBREffectEnabledStable(self, model):
|
||
"""确保模型启用了稳定的PBR效果,避免频繁切换导致闪烁"""
|
||
try:
|
||
# 检查是否已经有PBR效果
|
||
current_effect = model.getEffect()
|
||
if current_effect:
|
||
print(f"🔍 当前效果: {current_effect}")
|
||
# 如果已经有PBR相关效果,就不要切换了
|
||
effect_name = str(current_effect)
|
||
if "pbr" in effect_name.lower() or "default" in effect_name.lower():
|
||
print("✅ 已有PBR效果,保持不变避免闪烁")
|
||
return
|
||
|
||
# 使用最稳定的默认PBR效果
|
||
print("🔧 应用稳定的默认PBR效果...")
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/default.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": False,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
50 # 较高优先级
|
||
)
|
||
print(f"✅ 稳定PBR效果已应用到模型: {model.getName()}")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 设置稳定PBR效果失败: {e}")
|
||
# 最后的备选方案:清除所有效果
|
||
try:
|
||
model.clearEffect()
|
||
print("🔄 已清除所有效果,使用默认渲染")
|
||
except:
|
||
pass
|
||
|
||
|
||
|
||
def _applyEmissionTexture(self, material, texture_path):
|
||
"""应用自发光贴图"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if texture:
|
||
node = self._findNodeWithMaterial(material)
|
||
if node:
|
||
print(f"正在为节点 {node.getName()} 应用自发光贴图")
|
||
|
||
# 启用自发光效果
|
||
self._ensurePBREffectEnabledWithEmission(node)
|
||
|
||
# 清理现有的自发光贴图
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if "emission" in stage.getName().lower() or stage.getSort() == 6:
|
||
node.clearTexture(stage)
|
||
print(f"清理了现有的自发光贴图阶段: {stage.getName()}")
|
||
|
||
# 创建自发光贴图纹理阶段,对应p3d_Texture6
|
||
emission_stage = TextureStage("emission")
|
||
emission_stage.setSort(6) # 对应p3d_Texture6
|
||
emission_stage.setMode(TextureStage.MModulate)
|
||
|
||
node.setTexture(emission_stage, texture)
|
||
print("自发光贴图已应用到p3d_Texture6槽")
|
||
|
||
# 设置材质为自发光着色模型
|
||
from panda3d.core import Vec4
|
||
current_emission = material.emission
|
||
if current_emission is None:
|
||
current_emission = Vec4(0, 0, 0, 0)
|
||
|
||
# emission.x 用于存储着色模型,1表示自发光
|
||
new_emission = Vec4(1.0, current_emission.y, current_emission.z, current_emission.w)
|
||
material.set_emission(new_emission)
|
||
print("材质着色模型已设置为自发光")
|
||
|
||
self._invalidateRenderState()
|
||
print(f"自发光贴图已成功应用:{texture_path}")
|
||
else:
|
||
print("未找到材质对应的节点")
|
||
except Exception as e:
|
||
print(f"应用自发光贴图失败:{e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _applyAOTexture(self, material, texture_path):
|
||
"""应用环境光遮蔽贴图"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if texture:
|
||
node = self._findNodeWithMaterial(material)
|
||
if node:
|
||
print(f"正在为节点 {node.getName()} 应用AO贴图")
|
||
|
||
# 确保启用PBR效果
|
||
self._ensurePBREffectEnabled(node)
|
||
|
||
# 清理现有的AO贴图
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if "ao" in stage.getName().lower() or stage.getSort() == 7:
|
||
node.clearTexture(stage)
|
||
print(f"清理了现有的AO贴图阶段: {stage.getName()}")
|
||
|
||
# 创建AO贴图纹理阶段,对应p3d_Texture7
|
||
ao_stage = TextureStage("ao")
|
||
ao_stage.setSort(7) # 对应p3d_Texture7
|
||
ao_stage.setMode(TextureStage.MModulate)
|
||
|
||
node.setTexture(ao_stage, texture)
|
||
print("AO贴图已应用到p3d_Texture7槽")
|
||
print("注意:AO贴图需要自定义shader支持才能正确显示")
|
||
|
||
self._invalidateRenderState()
|
||
print(f"AO贴图已成功应用:{texture_path}")
|
||
else:
|
||
print("未找到材质对应的节点")
|
||
except Exception as e:
|
||
print(f"应用AO贴图失败:{e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _applyAlphaTexture(self, material, texture_path):
|
||
"""应用透明度贴图"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if texture:
|
||
node = self._findNodeWithMaterial(material)
|
||
if node:
|
||
print(f"正在为节点 {node.getName()} 应用透明度贴图")
|
||
|
||
# 启用透明度测试的PBR效果
|
||
self._ensurePBREffectEnabledWithAlpha(node)
|
||
|
||
# 清理现有的透明度贴图
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if "alpha" in stage.getName().lower() or stage.getSort() == 8:
|
||
node.clearTexture(stage)
|
||
print(f"清理了现有的透明度贴图阶段: {stage.getName()}")
|
||
|
||
# 创建透明度贴图纹理阶段,对应p3d_Texture8
|
||
alpha_stage = TextureStage("alpha")
|
||
alpha_stage.setSort(8) # 对应p3d_Texture8
|
||
alpha_stage.setMode(TextureStage.MModulate)
|
||
|
||
node.setTexture(alpha_stage, texture)
|
||
print("透明度贴图已应用到p3d_Texture8槽")
|
||
|
||
# 设置材质为透明着色模型
|
||
from panda3d.core import Vec4
|
||
current_emission = material.emission
|
||
if current_emission is None:
|
||
current_emission = Vec4(0, 0, 0, 0)
|
||
|
||
# emission.x 用于存储着色模型,3表示透明
|
||
new_emission = Vec4(3.0, current_emission.y, current_emission.z, current_emission.w)
|
||
material.set_emission(new_emission)
|
||
print("材质着色模型已设置为透明")
|
||
|
||
self._invalidateRenderState()
|
||
print(f"透明度贴图已成功应用:{texture_path}")
|
||
else:
|
||
print("未找到材质对应的节点")
|
||
except Exception as e:
|
||
print(f"应用透明度贴图失败:{e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _applyDetailTexture(self, material, texture_path):
|
||
"""应用细节贴图"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if texture:
|
||
node = self._findNodeWithMaterial(material)
|
||
if node:
|
||
print(f"正在为节点 {node.getName()} 应用细节贴图")
|
||
|
||
# 确保启用PBR效果
|
||
self._ensurePBREffectEnabled(node)
|
||
|
||
# 清理现有的细节贴图
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if "detail" in stage.getName().lower() or stage.getSort() == 9:
|
||
node.clearTexture(stage)
|
||
print(f"清理了现有的细节贴图阶段: {stage.getName()}")
|
||
|
||
# 创建细节贴图纹理阶段,对应p3d_Texture9
|
||
detail_stage = TextureStage("detail")
|
||
detail_stage.setSort(9) # 对应p3d_Texture9
|
||
detail_stage.setMode(TextureStage.MModulate)
|
||
|
||
node.setTexture(detail_stage, texture)
|
||
print("细节贴图已应用到p3d_Texture9槽")
|
||
print("注意:细节贴图需要自定义shader支持才能正确显示")
|
||
|
||
self._invalidateRenderState()
|
||
print(f"细节贴图已成功应用:{texture_path}")
|
||
else:
|
||
print("未找到材质对应的节点")
|
||
except Exception as e:
|
||
print(f"应用细节贴图失败:{e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _applyGlossTexture(self, material, texture_path):
|
||
"""应用光泽贴图"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if texture:
|
||
node = self._findNodeWithMaterial(material)
|
||
if node:
|
||
print(f"正在为节点 {node.getName()} 应用光泽贴图")
|
||
|
||
# 确保启用PBR效果
|
||
self._ensurePBREffectEnabled(node)
|
||
|
||
# 清理现有的光泽贴图
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if "gloss" in stage.getName().lower() or stage.getSort() == 10:
|
||
node.clearTexture(stage)
|
||
print(f"清理了现有的光泽贴图阶段: {stage.getName()}")
|
||
|
||
# 创建光泽贴图纹理阶段,对应p3d_Texture10
|
||
gloss_stage = TextureStage("gloss")
|
||
gloss_stage.setSort(10) # 对应p3d_Texture10
|
||
gloss_stage.setMode(TextureStage.MGloss) # 光泽模式
|
||
|
||
node.setTexture(gloss_stage, texture)
|
||
print("光泽贴图已应用到p3d_Texture10槽")
|
||
print("注意:光泽贴图需要自定义shader支持才能正确显示")
|
||
|
||
self._invalidateRenderState()
|
||
print(f"光泽贴图已成功应用:{texture_path}")
|
||
else:
|
||
print("未找到材质对应的节点")
|
||
except Exception as e:
|
||
print(f"应用光泽贴图失败:{e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _clearConflictingTextureStages(self, node):
|
||
"""清理可能冲突的纹理阶段"""
|
||
try:
|
||
from panda3d.core import TextureStage
|
||
|
||
# 获取所有纹理阶段
|
||
texture_stages = node.findAllTextureStages()
|
||
|
||
# 检查是否有冲突的纹理阶段
|
||
stages_to_clear = []
|
||
for stage in texture_stages:
|
||
stage_name = stage.getName()
|
||
# 如果发现未命名或冲突的阶段,标记清理
|
||
if stage_name == "" or stage == TextureStage.getDefault():
|
||
# 检查是否有法线贴图在默认阶段
|
||
texture = node.getTexture(stage)
|
||
if texture and "normal" in texture.getName().lower():
|
||
stages_to_clear.append(stage)
|
||
|
||
# 清理冲突的阶段
|
||
for stage in stages_to_clear:
|
||
node.clearTexture(stage)
|
||
print(f"清理了冲突的纹理阶段: {stage.getName()}")
|
||
|
||
except Exception as e:
|
||
print(f"清理纹理阶段时出错: {e}")
|
||
|
||
def _findNodeWithMaterial(self, target_material):
|
||
"""查找使用指定材质的节点"""
|
||
# 这里需要根据你的场景结构来实现
|
||
# 遍历场景中的所有节点,找到使用该材质的节点
|
||
# for model in self.world.scene_manager.models:
|
||
# materials = model.find_all_materials()
|
||
# if target_material in materials:
|
||
# return model
|
||
|
||
"""查找使用指定材质的节点"""
|
||
# 首先尝试在当前选中的模型中查找
|
||
current_item = self.world.treeWidget.currentItem()
|
||
if current_item:
|
||
current_model = current_item.data(0, Qt.UserRole)
|
||
if current_model:
|
||
materials = current_model.find_all_materials()
|
||
if target_material in materials:
|
||
return current_model
|
||
|
||
# 如果在当前选中模型中没找到,再遍历所有模型
|
||
for model in self.world.scene_manager.models:
|
||
materials = model.find_all_materials()
|
||
if target_material in materials:
|
||
return model
|
||
return None
|
||
|
||
def _findMaterialAndNodeByTitle(self, material_title):
|
||
"""根据材质标题查找对应的材质和节点"""
|
||
print(f"正在查找材质标题: '{material_title}'")
|
||
|
||
current_item = self.world.treeWidget.currentItem()
|
||
if not current_item:
|
||
print("未找到当前选中项")
|
||
return None, None
|
||
|
||
current_model = current_item.data(0, Qt.UserRole)
|
||
if not current_model:
|
||
print("未找到当前模型")
|
||
return None, None
|
||
|
||
materials = current_model.find_all_materials()
|
||
model_name = current_model.getName() or "未命名模型"
|
||
print(f"模型名称: '{model_name}', 材质数量: {len(materials)}")
|
||
|
||
name_counter = {}
|
||
for i, material in enumerate(materials):
|
||
material_name = material.get_name() if hasattr(material,
|
||
'get_name') and material.get_name() else f"材质{i + 1}"
|
||
base_name = f"{material_name}({model_name})"
|
||
|
||
if base_name in name_counter:
|
||
name_counter[base_name] += 1
|
||
unique_name = f"{base_name}_{name_counter[base_name]}"
|
||
else:
|
||
name_counter[base_name] = 1
|
||
unique_name = base_name
|
||
|
||
print(f"材质 {i}: 生成标题='{unique_name}'")
|
||
|
||
if unique_name == material_title:
|
||
print(f"找到匹配的材质!")
|
||
geom_node = self._findSpecificGeomNodeWithMaterial(current_model, material)
|
||
if geom_node:
|
||
print(f"找到几何节点: {geom_node.get_name()}")
|
||
return material, geom_node
|
||
else:
|
||
print("未找到对应的几何节点,使用模型节点")
|
||
return material, current_model
|
||
|
||
print("未找到匹配的材质标题")
|
||
return None, None
|
||
|
||
def _findSpecificGeomNodeWithMaterial(self, model, target_material):
|
||
"""查找使用指定材质的具体几何节点"""
|
||
from panda3d.core import MaterialAttrib, GeomNode
|
||
|
||
print(f"查找材质: {target_material.get_name() if hasattr(target_material, 'get_name') else 'unnamed'}")
|
||
|
||
# 首先尝试查找GeomNode
|
||
geom_nodes = model.find_all_matches("**/+GeomNode")
|
||
print(f"找到 {len(geom_nodes)} 个几何节点")
|
||
|
||
# 如果没有找到GeomNode,尝试查找所有子节点
|
||
if len(geom_nodes) == 0:
|
||
print("未找到GeomNode,尝试查找所有子节点...")
|
||
all_nodes = model.find_all_matches("**")
|
||
print(f"找到 {len(all_nodes)} 个子节点")
|
||
|
||
for node_np in all_nodes:
|
||
node = node_np.node()
|
||
if isinstance(node, GeomNode):
|
||
geom_nodes.append(node_np)
|
||
print(f"找到GeomNode: {node_np.getName()}")
|
||
|
||
for geom_np in geom_nodes:
|
||
geom_node = geom_np.node()
|
||
geom_count = geom_node.get_num_geoms()
|
||
print(f"检查几何节点 {geom_node.get_name()}: {geom_count} 个几何体")
|
||
|
||
for i in range(geom_count):
|
||
state = geom_node.get_geom_state(i)
|
||
if state.has_attrib(MaterialAttrib):
|
||
material = state.get_attrib(MaterialAttrib).get_material()
|
||
if material == target_material:
|
||
print(f"找到匹配的几何节点: {geom_np.get_name()}")
|
||
return geom_np
|
||
|
||
print("未找到匹配的几何节点")
|
||
return None
|
||
|
||
def _findSpecificGeomNodeForMaterial(self, target_material):
|
||
"""查找使用指定材质的具体几何节点(统一方法)"""
|
||
material_name = target_material.get_name() if hasattr(target_material, 'get_name') else 'unnamed'
|
||
print(f"查找材质对应的几何节点: {material_name}")
|
||
|
||
# 优先使用存储的几何节点映射
|
||
material_id = id(target_material)
|
||
if hasattr(self, '_material_geom_mapping') and material_id in self._material_geom_mapping:
|
||
geom_node = self._material_geom_mapping[material_id]
|
||
if geom_node: # 确保节点仍然有效
|
||
print(f"✓ 使用存储的几何节点映射: {geom_node.getName()}")
|
||
return geom_node
|
||
|
||
# 如果没有存储的映射,使用传统查找方法
|
||
current_item = self.world.treeWidget.currentItem()
|
||
if current_item:
|
||
current_model = current_item.data(0, Qt.UserRole)
|
||
if current_model:
|
||
# 使用现有的精确查找方法
|
||
geom_node = self._findSpecificGeomNodeWithMaterial(current_model, target_material)
|
||
if geom_node:
|
||
print(f"✓ 找到特定几何节点: {geom_node.getName()}")
|
||
# 存储映射以供后续使用
|
||
if not hasattr(self, '_material_geom_mapping'):
|
||
self._material_geom_mapping = {}
|
||
self._material_geom_mapping[material_id] = geom_node
|
||
return geom_node
|
||
else:
|
||
print("⚠️ 未找到特定几何节点,使用模型节点(可能影响所有材质)")
|
||
return current_model
|
||
|
||
print("❌ 未找到当前选中的模型")
|
||
return None
|
||
|
||
def _findGeomNodeWithMaterial(self, model, target_material):
|
||
"""查找使用指定材质的具体几何节点"""
|
||
from panda3d.core import MaterialAttrib
|
||
|
||
print(f"查找材质: {target_material.get_name() if hasattr(target_material, 'get_name') else 'unnamed'}")
|
||
|
||
# 遍历模型下的所有几何节点
|
||
geom_nodes = model.find_all_matches("**/+GeomNode")
|
||
print(f"找到 {len(geom_nodes)} 个几何节点")
|
||
|
||
for geom_np in geom_nodes:
|
||
geom_node = geom_np.node()
|
||
geom_count = geom_node.get_num_geoms()
|
||
print(f"几何节点 {geom_node.get_name()}: {geom_count} 个几何体")
|
||
|
||
for i in range(geom_count):
|
||
state = geom_node.get_geom_state(i)
|
||
if state.has_attrib(MaterialAttrib):
|
||
material = state.get_attrib(MaterialAttrib).get_material()
|
||
if material == target_material:
|
||
print(f"找到匹配的几何节点: {geom_np.get_name()}")
|
||
return geom_np
|
||
else:
|
||
print(f"几何体 {i} 没有材质属性")
|
||
|
||
print("未找到匹配的几何节点")
|
||
return None
|
||
|
||
def _displayCurrentTextures(self, material):
|
||
"""显示当前材质的贴图信息"""
|
||
node = self._findNodeWithMaterial(material)
|
||
if node:
|
||
# 显示当前应用的纹理信息
|
||
texture = node.getTexture()
|
||
if texture:
|
||
texture_name = texture.getName() or "未命名贴图"
|
||
texture_info = QLabel(f"当前贴图: {texture_name}")
|
||
texture_info.setStyleSheet("color: #666; font-size: 10px;")
|
||
self._propertyLayout.addRow("", texture_info)
|
||
|
||
def _applyToAllMaterials(self, model, property_name, value):
|
||
"""将属性应用到模型的所有材质"""
|
||
materials = model.find_all_materials()
|
||
for material in materials:
|
||
if property_name == "base_color":
|
||
material.set_base_color(value)
|
||
elif property_name == "roughness":
|
||
material.set_roughness(value)
|
||
elif property_name == "metallic":
|
||
material.set_metallic(value)
|
||
elif property_name == "ior":
|
||
material.set_refractive_index(value)
|
||
self._invalidateRenderState()
|
||
|
||
def _addShadingModelPanel(self, material):
|
||
"""添加着色模型选择面板"""
|
||
from PyQt5.QtWidgets import QComboBox
|
||
|
||
# RenderPipeline 支持的着色模型
|
||
SHADING_MODELS = [
|
||
("默认", 0),
|
||
("自发光", 1),
|
||
("透明涂层", 2),
|
||
("透明", 3),
|
||
("皮肤", 4),
|
||
("植物", 5),
|
||
]
|
||
|
||
shading_title = QLabel("着色模型")
|
||
shading_title.setStyleSheet("color: #4CAF50; font-weight:bold;")
|
||
self._propertyLayout.addRow(shading_title)
|
||
|
||
shading_combo = QComboBox()
|
||
for name, value in SHADING_MODELS:
|
||
shading_combo.addItem(name)
|
||
|
||
# 安全地获取当前着色模型
|
||
current_model = 0 # 默认值
|
||
try:
|
||
if hasattr(material, 'emission') and material.emission is not None:
|
||
current_model = int(material.emission.x)
|
||
except (AttributeError, TypeError, ValueError):
|
||
current_model = 0
|
||
|
||
shading_combo.setCurrentIndex(current_model)
|
||
|
||
shading_combo.currentIndexChanged.connect(
|
||
lambda idx: self._onShadingModelChanged(material, idx)
|
||
)
|
||
self._propertyLayout.addRow("着色模型:", shading_combo)
|
||
|
||
# 如果是透明着色模型,添加透明度控制
|
||
if hasattr(material, 'emission') and material.emission is not None and int(material.emission.x) == 3:
|
||
self._addTransparencyPanel(material)
|
||
|
||
def _onShadingModelChanged(self, material, model_index):
|
||
"""处理着色模型变化"""
|
||
print(f"着色模型变化: {model_index}")
|
||
|
||
# 更新着色模型
|
||
self._updateShadingModel(material, model_index)
|
||
|
||
# 如果切换到透明模式,立即添加透明度面板
|
||
if model_index == 3:
|
||
print("切换到透明模式,添加透明度面板...")
|
||
# 延迟一点时间让UI更新完成
|
||
from PyQt5.QtCore import QTimer
|
||
QTimer.singleShot(100, lambda: self._addTransparencyPanel(material))
|
||
|
||
def _updateShadingModel(self, material, model_index):
|
||
"""更新着色模型"""
|
||
from panda3d.core import Vec4
|
||
|
||
# 安全地获取当前 emission 值
|
||
current_emission = Vec4(0, 0, 0, 0)
|
||
if hasattr(material, 'emission') and material.emission is not None:
|
||
current_emission = material.emission
|
||
|
||
# 根据不同的着色模型设置相应的参数
|
||
if model_index == 1: # 自发光模式
|
||
default_emission_strength = 2.0 if current_emission.z == 0 else current_emission.z
|
||
new_emission = Vec4(float(model_index), current_emission.y, default_emission_strength, current_emission.w)
|
||
elif model_index == 3: # 透明模式
|
||
print("设置透明着色模型...")
|
||
|
||
# 设置默认透明度值
|
||
default_opacity = 0.8 # 设置为较高的值,确保模型可见
|
||
|
||
# 同时在emission.y和base_color.w中设置透明度值
|
||
# emission.y可能被RenderPipeline的某些部分使用
|
||
new_emission = Vec4(float(model_index), default_opacity, current_emission.z, current_emission.w)
|
||
|
||
# 透明度通过基础颜色的Alpha通道控制
|
||
self._updateMaterialAlphaForTransparency(material, default_opacity)
|
||
|
||
# 应用透明渲染效果
|
||
self._applyTransparentRenderingEffect()
|
||
|
||
print(f"透明着色模型设置完成")
|
||
print(f" - emission.x = {model_index} (透明着色模型)")
|
||
print(f" - emission.y = {default_opacity} (可能的透明度参数)")
|
||
print(f" - base_color.w = {default_opacity} (Alpha透明度)")
|
||
else:
|
||
new_emission = Vec4(float(model_index), current_emission.y, current_emission.z, current_emission.w)
|
||
|
||
material.set_emission(new_emission)
|
||
self._invalidateRenderState()
|
||
|
||
# 刷新UI以更新相关控件的值
|
||
if model_index in [1, 3]: # 自发光或透明模式
|
||
self._refreshMaterialUI()
|
||
|
||
print(f"着色模型已更新为: {model_index} ({'自发光' if model_index == 1 else '透明' if model_index == 3 else '默认'})")
|
||
|
||
def _addTransparencyPanel(self, material):
|
||
"""添加透明度控制面板"""
|
||
transparency_title = QLabel("透明度属性")
|
||
transparency_title.setStyleSheet("color: #00BFFF; font-weight:bold;")
|
||
self._propertyLayout.addRow(transparency_title)
|
||
|
||
# 不透明度滑块(避免混淆,使用不透明度)
|
||
opacity_spinbox = QDoubleSpinBox()
|
||
opacity_spinbox.setRange(0.1, 1.0) # 最小值0.1,避免完全消失
|
||
opacity_spinbox.setSingleStep(0.01)
|
||
|
||
# 安全地获取当前不透明度值(从基础颜色的Alpha通道)
|
||
current_opacity = 0.3 # 默认值
|
||
try:
|
||
base_color = self._getOrCreateMaterialBaseColor(material)
|
||
if base_color is not None:
|
||
current_opacity = max(0.1, base_color.w) # 从Alpha通道获取,确保不小于0.1
|
||
except Exception as e:
|
||
print(f"获取当前透明度失败,使用默认值: {e}")
|
||
|
||
opacity_spinbox.setValue(current_opacity)
|
||
opacity_spinbox.valueChanged.connect(lambda v: self._updateTransparency(material, v))
|
||
self._propertyLayout.addRow("不透明度:", opacity_spinbox)
|
||
|
||
def _updateTransparency(self, material, opacity_value):
|
||
"""更新不透明度值(同时更新emission.y和base_color.w)"""
|
||
try:
|
||
from panda3d.core import Vec4
|
||
|
||
# 确保不透明度值在合理范围内
|
||
opacity_value = max(0.1, min(1.0, opacity_value))
|
||
|
||
# 同时更新emission.y(可能被RenderPipeline使用)
|
||
current_emission = Vec4(0, 0, 0, 0)
|
||
if hasattr(material, 'emission') and material.emission is not None:
|
||
current_emission = material.emission
|
||
|
||
new_emission = Vec4(current_emission.x, opacity_value, current_emission.z, current_emission.w)
|
||
material.set_emission(new_emission)
|
||
|
||
# 更新基础颜色的Alpha通道
|
||
self._updateMaterialAlphaForTransparency(material, opacity_value)
|
||
|
||
self._invalidateRenderState()
|
||
print(f"透明度已更新:")
|
||
print(f" - emission.y = {opacity_value}")
|
||
print(f" - base_color.w = {opacity_value}")
|
||
print(f" - 视觉透明度 = {1.0 - opacity_value:.2f}")
|
||
|
||
except Exception as e:
|
||
print(f"更新透明度失败: {e}")
|
||
|
||
def _updateMaterialAlphaForTransparency(self, material, transparency_value):
|
||
"""更新材质基础颜色的Alpha通道以匹配透明度"""
|
||
try:
|
||
# 获取当前基础颜色
|
||
current_color = self._getOrCreateMaterialBaseColor(material)
|
||
if current_color is not None:
|
||
from panda3d.core import Vec4
|
||
# 设置Alpha通道为透明度值
|
||
new_color = Vec4(current_color.x, current_color.y, current_color.z, transparency_value)
|
||
|
||
# 尝试多种方式设置颜色
|
||
if hasattr(material, 'set_base_color'):
|
||
material.set_base_color(new_color)
|
||
elif hasattr(material, 'setDiffuse'):
|
||
material.setDiffuse(new_color)
|
||
|
||
print(f"材质基础颜色Alpha已更新为: {transparency_value}")
|
||
except Exception as e:
|
||
print(f"更新材质Alpha通道失败: {e}")
|
||
|
||
def _applyTransparentRenderingEffect(self):
|
||
"""为当前选中的模型应用透明渲染效果(简化版本)"""
|
||
try:
|
||
current_item = self.world.treeWidget.currentItem()
|
||
if current_item:
|
||
model = current_item.data(0, Qt.UserRole)
|
||
if model:
|
||
print(f"正在为模型 {model.getName()} 应用透明渲染效果...")
|
||
|
||
# 只使用最基本的透明度设置,避免冲突
|
||
from panda3d.core import TransparencyAttrib
|
||
|
||
# 启用Alpha混合
|
||
model.setTransparency(TransparencyAttrib.MAlpha)
|
||
print(" - 透明度混合: 已启用 (MAlpha)")
|
||
|
||
# 让RenderPipeline自动处理透明材质
|
||
# 当emission.x=3时,RenderPipeline会自动设置正确的渲染参数
|
||
self.world.render_pipeline.prepare_scene(model)
|
||
print(" - RenderPipeline自动处理: 已完成")
|
||
|
||
print(f"✓ 已为模型 {model.getName()} 应用透明渲染效果")
|
||
print(" 注意: 使用简化设置避免渲染冲突")
|
||
|
||
except Exception as e:
|
||
print(f"✗ 应用透明渲染效果失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _addEmissionPanel(self, material):
|
||
"""添加自发光控制面板"""
|
||
emission_title = QLabel("自发光属性")
|
||
emission_title.setStyleSheet("color: #FF6B6B; font-weight:bold;")
|
||
self._propertyLayout.addRow(emission_title)
|
||
|
||
# 自发光强度
|
||
emission_spinbox = QDoubleSpinBox()
|
||
emission_spinbox.setRange(0.0, 10.0)
|
||
emission_spinbox.setSingleStep(0.1)
|
||
|
||
# 安全地获取当前自发光强度
|
||
current_emission_z = 0.0 # 默认值
|
||
if hasattr(material, 'emission') and material.emission is not None:
|
||
current_emission_z = material.emission.z
|
||
|
||
emission_spinbox.setValue(current_emission_z)
|
||
emission_spinbox.valueChanged.connect(lambda v: self._updateEmissionStrength(material, v))
|
||
self._propertyLayout.addRow("发光强度:", emission_spinbox)
|
||
|
||
def _updateEmissionStrength(self, material, strength):
|
||
"""更新自发光强度"""
|
||
from panda3d.core import Vec4
|
||
|
||
# 安全地获取当前 emission 值
|
||
current_emission = Vec4(0, 0, 0, 0)
|
||
if hasattr(material, 'emission') and material.emission is not None:
|
||
current_emission = material.emission
|
||
|
||
# 更新 emission.z 存储发光强度(用于UI显示)
|
||
new_emission = Vec4(current_emission.x, current_emission.y, strength, current_emission.w)
|
||
material.set_emission(new_emission)
|
||
|
||
# 对于自发光材质,直接调整基础颜色的亮度
|
||
if current_emission.x == 1: # 如果是自发光着色模型
|
||
# 获取原始基础颜色(假设存储在某处,或使用当前值的归一化版本)
|
||
base_intensity = 0.5 # 基础亮度
|
||
intensity_multiplier = strength / 5.0 # 将0-10范围映射到合理的倍数
|
||
|
||
# 设置发光颜色
|
||
emissive_color = Vec4(
|
||
base_intensity * intensity_multiplier,
|
||
base_intensity * intensity_multiplier,
|
||
base_intensity * intensity_multiplier,
|
||
1.0
|
||
)
|
||
material.set_base_color(emissive_color)
|
||
|
||
self._invalidateRenderState()
|
||
print(f"自发光强度已更新为: {strength}")
|
||
|
||
def _addMaterialPresetPanel(self, material):
|
||
"""添加材质预设面板"""
|
||
from PyQt5.QtWidgets import QComboBox
|
||
|
||
preset_title = QLabel("材质预设")
|
||
preset_title.setStyleSheet("color: #9C27B0; font-weight:bold;")
|
||
self._propertyLayout.addRow(preset_title)
|
||
|
||
preset_combo = QComboBox()
|
||
preset_combo.addItems([
|
||
"自定义", "塑料", "金属", "玻璃", "橡胶", "木材", "陶瓷", "皮革"
|
||
])
|
||
|
||
# 优先检查存储的预设名称
|
||
if hasattr(material, '_applied_preset'):
|
||
preset_combo.setCurrentText(material._applied_preset)
|
||
else:
|
||
# 安全地检测当前材质最接近的预设
|
||
try:
|
||
current_preset = self._detectCurrentPreset(material)
|
||
preset_combo.setCurrentText(current_preset)
|
||
except Exception as e:
|
||
print(f"检测材质预设时出错: {e}")
|
||
preset_combo.setCurrentText("自定义")
|
||
|
||
preset_combo.currentTextChanged.connect(
|
||
lambda preset: self._applyMaterialPreset(material, preset)
|
||
)
|
||
self._propertyLayout.addRow("选择预设:", preset_combo)
|
||
|
||
def _detectCurrentPreset(self, material):
|
||
"""检测当前材质最接近的预设"""
|
||
# 定义预设的精确匹配条件
|
||
|
||
presets = {
|
||
"塑料": {"base_color": (0.8, 0.8, 0.8), "roughness": 0.7, "metallic": 0.0, "ior": 1.4},
|
||
"金属": {"base_color": (0.7, 0.7, 0.7), "roughness": 0.1, "metallic": 1.0, "ior": 1.5},
|
||
"玻璃": {"base_color": (0.9, 0.9, 1.0), "roughness": 0.0, "metallic": 0.0, "ior": 1.5},
|
||
"橡胶": {"base_color": (0.2, 0.2, 0.2), "roughness": 0.9, "metallic": 0.0, "ior": 1.3},
|
||
"自发光": {"base_color": (1.0, 1.0, 1.0), "roughness": 0.5, "metallic": 0.0, "ior": 1.0}
|
||
}
|
||
|
||
# 容差值,用于浮点数比较
|
||
tolerance = 0.05
|
||
|
||
for preset_name, preset_values in presets.items():
|
||
# 安全检查基础颜色
|
||
base_color_match = False
|
||
if hasattr(material, 'base_color') and material.base_color is not None:
|
||
try:
|
||
base_color_match = (
|
||
abs(material.base_color.x - preset_values["base_color"][0]) < tolerance and
|
||
abs(material.base_color.y - preset_values["base_color"][1]) < tolerance and
|
||
abs(material.base_color.z - preset_values["base_color"][2]) < tolerance
|
||
)
|
||
except (AttributeError, TypeError):
|
||
base_color_match = False
|
||
|
||
# 安全检查其他属性
|
||
roughness_match = False
|
||
if hasattr(material, 'roughness') and material.roughness is not None:
|
||
try:
|
||
roughness_match = abs(float(material.roughness) - preset_values["roughness"]) < tolerance
|
||
except (AttributeError, TypeError, ValueError):
|
||
roughness_match = False
|
||
|
||
metallic_match = False
|
||
if hasattr(material, 'metallic') and material.metallic is not None:
|
||
try:
|
||
metallic_match = abs(float(material.metallic) - preset_values["metallic"]) < tolerance
|
||
except (AttributeError, TypeError, ValueError):
|
||
metallic_match = False
|
||
|
||
ior_match = False
|
||
if hasattr(material, 'refractive_index') and material.refractive_index is not None:
|
||
try:
|
||
ior_match = abs(float(material.refractive_index) - preset_values["ior"]) < tolerance
|
||
except (AttributeError, TypeError, ValueError):
|
||
ior_match = False
|
||
|
||
# 如果所有属性都匹配,返回预设名称
|
||
if base_color_match and roughness_match and metallic_match and ior_match:
|
||
return preset_name
|
||
|
||
return "自定义" # 如果没有匹配的预设
|
||
|
||
def _applyMaterialPreset(self, material, preset_name):
|
||
"""应用材质预设"""
|
||
presets = {
|
||
"塑料": {"base_color": Vec4(0.8, 0.8, 0.8, 1.0), "roughness": 0.7, "metallic": 0.0, "ior": 1.4},
|
||
"金属": {"base_color": Vec4(0.7, 0.7, 0.7, 1.0), "roughness": 0.1, "metallic": 1.0, "ior": 1.5},
|
||
"玻璃": {"base_color": Vec4(0.9, 0.9, 1.0, 0.2), "roughness": 0.0, "metallic": 0.0, "ior": 1.5,"shading_model":3,"transparency":0.2},
|
||
"橡胶": {"base_color": Vec4(0.2, 0.2, 0.2, 1.0), "roughness": 0.9, "metallic": 0.0, "ior": 1.3},
|
||
"木材": {"base_color": Vec4(0.6, 0.4, 0.2, 1.0), "roughness": 0.8, "metallic": 0.0, "ior": 1.3},
|
||
"陶瓷": {"base_color": Vec4(0.9, 0.9, 0.85, 1.0), "roughness": 0.1, "metallic": 0.0, "ior": 1.6},
|
||
"皮革": {"base_color": Vec4(0.4, 0.3, 0.2, 1.0), "roughness": 0.6, "metallic": 0.0, "ior": 1.4}
|
||
}
|
||
|
||
if preset_name not in presets:
|
||
print(f"未知的材质预设: {preset_name}")
|
||
return
|
||
|
||
|
||
|
||
preset = presets[preset_name]
|
||
|
||
if "shading_model" in preset:
|
||
emission = Vec4(float(preset["shading_model"]), 0, 0, 0)
|
||
if "transparency" in preset:
|
||
emission.y = preset["transparency"]
|
||
material.set_emission(emission)
|
||
|
||
print(f"设置着色模型: {preset['shading_model']}")
|
||
print(f"材质emission值: {material.emission}")
|
||
print(f"基础颜色alpha: {preset['base_color'].w}")
|
||
|
||
|
||
|
||
material.set_base_color(preset["base_color"])
|
||
material.set_roughness(preset["roughness"])
|
||
material.set_metallic(preset["metallic"])
|
||
material.set_refractive_index(preset["ior"])
|
||
|
||
if "shading_model" in preset:
|
||
emission = Vec4(float (preset["shading_model"]),0,0,0)
|
||
if "transparency" in preset:
|
||
emission.y = preset["transparency"]
|
||
material.set_emission(emission)
|
||
|
||
#关键:为透明材质应用正确的渲染效果
|
||
if preset["shading_model"]==3:
|
||
self._apply_transparent_effect()
|
||
|
||
self._invalidateRenderState()
|
||
#material._applied_preset = preset_name
|
||
self._refreshMaterialUI()
|
||
print(f"已应用材质预设: {preset_name}")
|
||
|
||
def _apply_transparent_effect(self):
|
||
"""为当前选中的模型应用透明渲染效果"""
|
||
current_item = self.world.treeWidget.currentItem()
|
||
if current_item:
|
||
model = current_item.data(0, Qt.UserRole)
|
||
if model:
|
||
# 只调用 prepare_scene,让它自动处理透明材质
|
||
self.world.render_pipeline.set_effect(
|
||
model,
|
||
"effects/default.yaml",
|
||
{
|
||
"render_forward": True,
|
||
"render_gbuffer": False,
|
||
"normal_mapping": True # 明确启用法线映射
|
||
},
|
||
100
|
||
)
|
||
self.world.render_pipeline.prepare_scene(model)
|
||
print("已重新准备场景以应用透明效果")
|
||
|
||
def _refreshMaterialUI(self):
|
||
"""刷新材质 UI 显示"""
|
||
# 重新更新当前选中项的属性面板
|
||
if hasattr(self.world, 'treeWidget') and self.world.treeWidget.currentItem():
|
||
current_item = self.world.treeWidget.currentItem()
|
||
# 触发属性面板更新
|
||
self.updatePropertyPanel(current_item)
|
||
|
||
def _addColorSpacePanel(self, material):
|
||
"""添加颜色空间选择面板"""
|
||
from PyQt5.QtWidgets import QButtonGroup, QRadioButton, QHBoxLayout, QWidget
|
||
|
||
# color_space_title = QLabel("颜色空间")
|
||
# color_space_title.setStyleSheet("color: #FF9800; font-weight:bold;")
|
||
# self._propertyLayout.addRow(color_space_title)
|
||
#
|
||
# color_space_widget = QWidget()
|
||
# color_space_layout = QHBoxLayout(color_space_widget)
|
||
# color_space_group = QButtonGroup()
|
||
|
||
# rgb_radio = QRadioButton("RGB")
|
||
# srgb_radio = QRadioButton("sRGB")
|
||
# hsv_radio = QRadioButton("HSV")
|
||
#
|
||
# rgb_radio.setChecked(True)
|
||
#
|
||
# color_space_group.addButton(rgb_radio, 0)
|
||
# color_space_group.addButton(srgb_radio, 1)
|
||
# color_space_group.addButton(hsv_radio, 2)
|
||
#
|
||
# color_space_layout.addWidget(rgb_radio)
|
||
# color_space_layout.addWidget(srgb_radio)
|
||
# color_space_layout.addWidget(hsv_radio)
|
||
#
|
||
# color_space_group.buttonClicked.connect(
|
||
# lambda button: self._updateColorSpace(material, color_space_group.id(button))
|
||
# )
|
||
#
|
||
# self._propertyLayout.addRow("颜色空间:", color_space_widget)
|
||
|
||
def _addBatchOperationsPanel(self, model):
|
||
"""添加批量操作面板"""
|
||
batch_title = QLabel("批量操作")
|
||
batch_title.setStyleSheet("color: #E91E63; font-weight:bold;")
|
||
self._propertyLayout.addRow(batch_title)
|
||
|
||
# 批量设置粗糙度
|
||
batch_roughness_btn = QPushButton("统一粗糙度")
|
||
batch_roughness_btn.clicked.connect(lambda: self._batchSetRoughness(model))
|
||
self._propertyLayout.addRow("批量粗糙度:", batch_roughness_btn)
|
||
|
||
# 批量设置金属性
|
||
batch_metallic_btn = QPushButton("统一金属性")
|
||
batch_metallic_btn.clicked.connect(lambda: self._batchSetMetallic(model))
|
||
self._propertyLayout.addRow("批量金属性:", batch_metallic_btn)
|
||
|
||
def _batchSetRoughness(self, model):
|
||
"""批量设置粗糙度"""
|
||
from PyQt5.QtWidgets import QInputDialog
|
||
|
||
value, ok = QInputDialog.getDouble(None, "批量设置", "粗糙度值:", 0.5, 0.0, 1.0, 2)
|
||
if ok:
|
||
self._applyToAllMaterials(model, "roughness", value)
|
||
print(f"已将所有材质粗糙度设置为: {value}")
|
||
|
||
def _applyMetallicTexture_NEW(self, material, texture_path):
|
||
"""应用金属性贴图 - 简化重写版本"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
print(f"🎨 应用金属性贴图: {texture_path}")
|
||
|
||
# 1. 加载纹理
|
||
texture = RPLoader.load_texture(texture_path)
|
||
if not texture:
|
||
print("❌ 纹理加载失败")
|
||
return
|
||
|
||
# 2. 找到节点
|
||
node = self._findSpecificGeomNodeForMaterial(material)
|
||
if not node:
|
||
print("❌ 未找到材质对应的节点")
|
||
return
|
||
|
||
print(f"🎯 目标节点: {node.getName()}")
|
||
|
||
# 3. 设置材质金属性为1.0
|
||
material.set_metallic(1.0)
|
||
print("🔧 材质金属性设置为1.0")
|
||
|
||
# # 4. 创建纹理阶段
|
||
# metallic_stage = TextureStage("metallic_map")
|
||
# metallic_stage.setSort(5) # p3d_Texture5
|
||
# metallic_stage.setMode(TextureStage.MModulate)
|
||
#
|
||
# # 5. 绑定纹理
|
||
# node.setTexture(metallic_stage, texture)
|
||
# print("🔗 纹理已绑定到p3d_Texture5槽")
|
||
|
||
# 6. 应用金属性PBR效果,检查是否需要保持法线映射
|
||
has_normal = self._hasNormalTexture(node)
|
||
|
||
if not has_normal:
|
||
print("⚠️ 检测到材质没有法线贴图,先添加默认法线贴图...")
|
||
#self._applyDefaultNormalTexture(node)
|
||
self._applyNormalTexture(material,"RenderPipelineFile/Default_NRM_2K.png")
|
||
print("✅ 默认法线贴图已添加")
|
||
else:
|
||
print("✅ 检测到材质已有法线贴图")
|
||
|
||
print("清理现有金属度贴图...")
|
||
existing_stages = node.findAllTextureStages()
|
||
for stage in existing_stages:
|
||
if stage.getSort() == 5: # p3d_Texture3槽
|
||
node.clearTexture(stage)
|
||
print(f"已清理现有金属度贴图: {stage.getName()}")
|
||
|
||
for i in range(4):
|
||
metallic_stage = TextureStage("metallic_map")
|
||
metallic_stage.setSort(5) # p3d_Texture5
|
||
metallic_stage.setMode(TextureStage.MModulate)
|
||
node.setTexture(metallic_stage, texture)
|
||
|
||
try:
|
||
# 检查是否需要透明度测试
|
||
needs_alpha = self._needsAlphaTesting(node)
|
||
print(f"🔍 透明度测试: {'需要' if needs_alpha else '不需要'}")
|
||
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
"effects/pbr_with_metallic.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": needs_alpha, # 根据是否需要透明度决定
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100
|
||
)
|
||
print(f"✅ 金属性PBR效果已应用(法线映射: {'启用' if has_normal else '禁用'})")
|
||
|
||
# 温和的shader刷新方法
|
||
print("🔄 温和刷新shader状态...")
|
||
try:
|
||
# 只重新绑定纹理,不破坏渲染状态
|
||
node.setTexture(metallic_stage, texture)
|
||
|
||
# 重新应用效果,但使用相同优先级
|
||
self.world.render_pipeline.set_effect(
|
||
node,
|
||
"effects/pbr_with_metallic.yaml",
|
||
{
|
||
#"normal_mapping": has_normal,
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": False,
|
||
"parallax_mapping": False,
|
||
"render_shadow": True,
|
||
"render_envmap": True
|
||
},
|
||
100 # 相同优先级
|
||
)
|
||
|
||
print("✅ 金属性PBR效果已应用")
|
||
|
||
# 关键修复:多次重新绑定纹理,确保着色器编译完成后生效
|
||
print("🔧 强化纹理绑定机制...")
|
||
try:
|
||
import time
|
||
|
||
# 多次重新绑定,模拟手动"应用两次"的效果
|
||
for i in range(3):
|
||
print(f" 第{i+1}次绑定...")
|
||
|
||
# 等待更长时间确保着色器编译完成
|
||
time.sleep(0.05) # 50ms延迟
|
||
|
||
# 强制重新绑定纹理
|
||
node.setTexture(metallic_stage, texture)
|
||
|
||
# 强制刷新节点状态
|
||
node.setRenderModeWireframe()
|
||
node.clearRenderMode()
|
||
|
||
# 验证绑定
|
||
applied_texture = node.getTexture(metallic_stage)
|
||
if applied_texture:
|
||
print(f" ✅ 第{i+1}次绑定成功")
|
||
else:
|
||
print(f" ❌ 第{i+1}次绑定失败")
|
||
|
||
# 最终验证
|
||
final_texture = node.getTexture(metallic_stage)
|
||
if final_texture:
|
||
print(f"✅ 强化绑定完成: {final_texture.getName()}")
|
||
print("🔍 现在应该看到正确的金属性贴图效果")
|
||
print("🔍 黑色区域=非金属,白色区域=金属")
|
||
else:
|
||
print("❌ 强化绑定最终失败")
|
||
|
||
except Exception as rebind_error:
|
||
print(f"⚠️ 强化纹理绑定失败: {rebind_error}")
|
||
|
||
except Exception as refresh_error:
|
||
print(f"⚠️ 金属性PBR效果应用失败: {refresh_error}")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ PBR效果应用失败: {e}")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 应用金属性贴图失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _hasMetallicTexture(self, node):
|
||
"""检查节点是否有金属性贴图"""
|
||
try:
|
||
all_stages = node.findAllTextureStages()
|
||
for stage in all_stages:
|
||
if stage.getSort() == 5: # p3d_Texture5 是金属性贴图槽
|
||
tex = node.getTexture(stage)
|
||
if tex:
|
||
print(f"🔍 发现金属性贴图: {tex.getName()}")
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
print(f"⚠️ 检查金属性贴图失败: {e}")
|
||
return False
|
||
|
||
def _applyDefaultNormalTexture(self, node):
|
||
"""应用RenderPipeline的默认法线贴图"""
|
||
try:
|
||
from RenderPipelineFile.rpcore.loader import RPLoader
|
||
from panda3d.core import TextureStage
|
||
|
||
print("🎨 加载RenderPipeline默认法线贴图...")
|
||
|
||
# 使用RenderPipeline自带的默认法线贴图
|
||
default_normal_path = "RenderPipelineFile/Default_NRM_2K.png"
|
||
texture = RPLoader.load_texture(default_normal_path)
|
||
|
||
if not texture:
|
||
print(f"❌ 无法加载默认法线贴图: {default_normal_path}")
|
||
# 回退到虚拟法线贴图
|
||
return
|
||
|
||
print(f"✅ 默认法线贴图加载成功: {texture.getName()}")
|
||
|
||
# 创建法线贴图纹理阶段
|
||
normal_stage = TextureStage("default_normal")
|
||
normal_stage.setSort(1) # p3d_Texture1
|
||
normal_stage.setMode(TextureStage.MModulate)
|
||
normal_stage.setTexcoordName("texcoord")
|
||
|
||
# 应用默认法线贴图
|
||
node.setTexture(normal_stage, texture)
|
||
print("✅ RenderPipeline默认法线贴图已应用")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 应用默认法线贴图失败: {e}")
|
||
# 回退到虚拟法线贴图
|
||
return self._createDummyNormalTexture(node)
|
||
|
||
def _needsAlphaTesting(self, node):
|
||
"""检查节点是否需要透明度测试(检查漫反射贴图是否有透明通道)"""
|
||
try:
|
||
all_stages = node.findAllTextureStages()
|
||
for stage in all_stages:
|
||
if stage.getSort() == 0: # p3d_Texture0 是漫反射贴图槽
|
||
tex = node.getTexture(stage)
|
||
if tex:
|
||
# 检查纹理格式是否支持透明度
|
||
format_name = str(tex.getFormat())
|
||
has_alpha = 'alpha' in format_name.lower() or 'rgba' in format_name.lower()
|
||
if has_alpha:
|
||
print(f"🔍 发现透明漫反射贴图: {tex.getName()}")
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
print(f"⚠️ 检查透明度需求失败: {e}")
|
||
return True # 出错时默认启用透明度测试,更安全 |