I want to make my models conditionally semi-transparent, so I could see other objects through them.
I split this big task into two parts, the second part should be trivial, esp. with SphereCast introduced, I will cast a sphere from camera and see if it hits continuously desired objects, and initiate the material transparency change.
And for transparency change I have a question on how I might approach this:
example from Heroes 5
However, for models in general I need to change all the materials (in this particular model 3 materials with 3 textures, and also this is a complex model combined from some separate parts), so it would be a burden to change it all recursively, and also Iâm not sure if it represented in code in x3d/classes properties fully.
Thatâs what I want to ask, how could I approach this ? If only recursively, then I would go for it, but itâs always good to have easier alternative
Ability to render models as âghostsâ would be nice, indeed. Iâm not CGE expert, and with my current knowledge of the engine Iâd go for these:
recursive, as you propose, because itâs fast enough and easy enough. Itâll also work on every model that you throw into the game. But⌠if you have other models using the same materials, I guess all of them would change the appearance. That would mean you canât use cache, or reference (clones). I may be wrong, though.
make the materials semi-transparent from design, and then - from code - change the RenderingOptions.Blending. It should be false for all, and true only for the models you want. Which would be simple & fast.
Make custom property âghost: booleanâ and hack the renderer to apply transparency on-the-go⌠but even Iâm not that crazy. However some option like this could be in the engine, as âghostsâ are not uncommon (e.g. in construction games itâs heavily used to project the hologram before placing / modifying objects).
Iâd go for 2. But I only quickly tested it just right now.
A picture showing the option 2. Inside Blender I used simple cube with a texture that has alpha channel. In the code, I generated a few of these with 50% of them having Blending := false; and another 50% Blending := true
for i...
Model.RenderOptions.Blending := ((i mod 2) = 0);
Model.RenderOptions.BlendingDestinationFactor := bdDstAlpha;
Model.RenderOptions.BlendingSourceFactor := bsDstAlpha;
All the material parameters that we take into account when rendering, from glTF or X3D or any other format, are exposed in CGE and you can change it by finding and tweaking the TPhysicalMaterialNode properties in your models.
Use Scene.RootNode.EnumerateNodes with a callback to find all occurrences of the given node type in your model. To change AlphaMode, you would do sthg like this:
type
TMyView = ...
private
DesiredAlphaModeOfPhysicalMaterial: TAlphaMode;
procedure SetAlphaModeOfPhysicalMaterial(Node: TX3DNode);
procedure SetAlphaModeOfScene(const Scene: TCastleScene; const DesiredAlphaMode: TAlphaMode);
end;
procedure TMyView.SetAlphaModeOfScene(const Scene: TCastleScene; const DesiredAlphaMode: TAlphaMode);
begin
DesiredAlphaModeOfPhysicalMaterial := DesiredAlphaMode;
if Scene.RootNode <> nil then // RootNode can be nil if nothing loaded
Scene.RootNode.EnumerateNodes(TPhysicalMaterialNode,
{$ifdef FPC}@{$endif} SetAlphaModeOfPhysicalMaterial, false);
end;
procedure TMyView.SetAlphaModeOfPhysicalMaterial(Node: TX3DNode);
var
PhysicalMaterialNode: TPhysicalMaterialNode;
begin
// safe to typecast, because EnumerateNodes only calls this for TPhysicalMaterialNode
PhysicalMaterialNode := Node as TPhysicalMaterialNode;
// tweak the material properties as you wish.
// As a demo, we change the AlphaMode of the material to the desired value.
PhysicalMaterialNode.AlphaMode := DesiredAlphaModeOfPhysicalMaterial;
end;
(above is untested, you can grep our examples/ for various usage of EnumerateNodes( and TPhysicalMaterialNode though we donât have exact example of tweaking AlphaMode).
And now you can use this like SetAlphaModeOfScene(MyScene, amBlend) and SetAlphaModeOfScene(MyScene, amOpaque).
An alternative solution is to just change MyScene.RenderOptions.Blending. When itâs false, we treat all materials as opaque. Itâs faster and easier, but itâs also a very specific solution to the exact case you describe (toggle opaque / blending). The solution above, with EnumerateNodes is fully generic so you can tweak any node property to anything this way
Our cache (like TCastleScene.Cache and some automatic GPU resource caching) does not make materials âsharedâ. So, no, material parameters of TPhysicalMaterialNode are not shared between TCastleScene instances, and TCastleScene.Cache or other mechanism doesnât change it. Maybe the resulting material GPU resources are shared (like shader is shared now if hash of shader logic is equal), but this is an implementation detail that you donât need to worry about.
Only if you use TCastleTransformReference than 100% of the scene is shared, so including material nodes. IOW, we always very clearly document if some CGE mechanism causes âsharingâ meaning that changing 1 thing changes many things
I show above the ârecursiveâ approach, this is basically what Scene.RootNode.EnumerateNodes does.
Indeed, this is also an option.
I agree itâs a common use-case, but I donât think we need anything special for this. And every design may need a different definition âhow goes a ghost exactly look likeâ. Better to leave this to each application, we give you tools â RenderOptions.Blending, EnumerateNodes, more options to tweak look by Shader Effects (Compositing Shaders) | Castle Game Engine etc.
here we see that changing the blending property of sub-model changes transparency of the material, but this material is the one I edited manually in GLTF file (remember there are 3 materials in this file, for each texture, wood on the edges, for example, which is not transparent), so I would need to edit all models and all materials to have baseColorFactor with value that I need, and for it I donât think any property is exposed to be changed via code, or ?
and also here is the whole model:
setting blending for ârootâ model doesnât affect the blending property of its children, so for this I would need to do again some special processing to traverse all children.
I donât mind of doing these changes manually, I would find a way, just publishing my results, and maybe you will have some ideas or outcome
I see your point. The blending solution is only good when you have few shapes inside one model, instead of few separate models. Then you could (considering the scale) have just 1 bigger texture baked instead of 3. But it wouldnât wok if you need the 3 parts separate.
So I guess enumerating remains the only option? Unless you want to consolidate the models. I will try with shaders. I need similar âghostâ stuff for me myself anyway, but canât use transparency for it. And if I succeed Iâll get you updated.
Indeed in my/our answers we focused on a solution for a single TCastleScene. There is no special solution that would change material properties of many scenes (unless they are the same scenes using TCastleTransformReference, but thatâs not your case). Simply enumerate scenes. Itâs easy
procedure DoSomething;
procedure DoSomethingRecursively(const T: TCastleTransform);
var
Child: TCastleTransform;
begin
for Child in T do
begin
if Child is TCastleScene then
// just an example, referring to SetAlphaModeOfScene from my previous post
SetAlphaModeOfScene(TCastleScene(Child), ...);
DoSomethingRecursively(Child);
end;
end;
begin
DoSomethingRecursively(MyViewport.Items);
end;
Above I start from MyViewport.Items, so I do something on all scenes in the viewport. You could of course also start from any specific TCastleTransform.
If you want to iterate over all transformations that have a certain âtraitâ, our behaviors are a great way to âtagâ your transformations. Then you can use SomeParent.FindAllBehaviors(TMyBehavior) to find all transforms with TMyBehavior. For every behavior, you can look at its parent (TCastleBehavior). E.g.
var
AllHighlightable: TCastleBehaviorList;
B: TCastleBehavior;
begin
AllHighlightable := MyViewport.Items.FindAllBehaviors(THighlightableBehavior);
try
for B in AllHighlightable do
begin
Assert(B is THighlightableBehavior);
// casting, assuming we only added THighlightableBehavior to TCastleScene
Scene := B.Parent as TCastleScene;
// again using SetAlphaModeOfScene from my previous post as just an example
SetAlphaModeOfScene(Scene, ...);
end;
finally FreeAndNil(AllHighlightable) end;
end;
In bad chess tutorial I discusssed a bit more âusing behaviors to tag your TCastleTransform instancesâ.
So, Thanks, guys, I ended up with a mix of the code presented here in the topic (I will post if needed by someone) and it works smoothly, and what is important - no manual adjustments needed, neither in gltf files, nor design files.
For now I made a test solution with mouse hover:
But Iâm gonna work on SphereCasts onto warriors and check if sphere hits the model in front (before) of warrior
Here is the video of final result in game - I used raycasts (sphere casts are not suitable for me as they require colliders/physics, which I donât use), and the code of method that I used, with it I have an array of Transforms of desired type hit by ray.
function CView.GetTransformsFromRayByClass(AViewport: TCastleViewport;
AClass: TCastleTransformClass; AOrigin, ADirection: TVector3): TArray<TCastleTransform>;
var
LRay: TRayCollision;
LRayNode: TRayCollisionNode;
LCounter, LChildIndex: Integer;
begin
Result := nil;
LCounter := 0;
repeat
LRay := AViewport.Items.WorldRay(AOrigin, ADirection);
if not Assigned(LRay) then
Exit;
for LChildIndex := 0 to LRay.Count - 1 do
begin
LRayNode := LRay.Items[LChildIndex];
if LRayNode.Item is AClass then
begin
SetLength(Result, Length(Result) + 1);
Result[High(Result)] := LRayNode.Item;
end;
end;
AOrigin := LRayNode.Point + ADirection.AdjustToLength(0.2);
until PostInc(LCounter) > 30;
end;
for real use case you might need to adjust Counter limit and ray origin adjustment scalar (0.2 in this code), and Iâm calling it so:
procedure CView.SetTransparencyOfPhysicalMaterial(Node: TX3DNode);
begin
(Node as TPhysicalMaterialNode).Transparency := IFF(FIsBlendOn, 0.5, 1.0);
end;
procedure CView.SetAlphaModeOfAppearance(Node: TX3DNode);
begin
(Node as TAppearanceNode).AlphaMode := IfThen<TAlphaMode>(FIsBlendOn, amBlend, amOpaque);
end;
procedure CView.SetAlphaModeOfScene(const Scene: TCastleScene; AIsBlendOn: Boolean);
var
LChild: TCastleTransform;
begin
FIsBlendOn := AIsBlendOn;
for LChild in Scene do
if (LChild is TCastleScene) and Assigned(TCastleScene(LChild).RootNode) then
with TCastleScene(LChild).RootNode do
begin
EnumerateNodes(TAppearanceNode, {$ifdef FPC_OBJFPC}@{$endif} SetAlphaModeOfAppearance, False);
EnumerateNodes(TPhysicalMaterialNode, {$ifdef FPC_OBJFPC}@{$endif} SetTransparencyOfPhysicalMaterial, False);
end;
end;