|
1 | 1 | /* |
2 | | - * Copyright (c) 2009-2024 jMonkeyEngine |
| 2 | + * Copyright (c) 2009-2025 jMonkeyEngine |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
|
37 | 37 | import com.jme3.export.JmeImporter; |
38 | 38 | import com.jme3.export.OutputCapsule; |
39 | 39 | import com.jme3.light.PointLight; |
| 40 | +import com.jme3.material.Material; |
| 41 | +import com.jme3.math.Vector3f; |
| 42 | +import com.jme3.renderer.Camera; |
| 43 | +import com.jme3.renderer.queue.GeometryList; |
| 44 | +import com.jme3.renderer.queue.RenderQueue; |
| 45 | +import com.jme3.scene.Geometry; |
| 46 | +import com.jme3.scene.Spatial; |
| 47 | +import com.jme3.util.TempVars; |
| 48 | +import com.jme3.util.clone.Cloner; |
| 49 | + |
40 | 50 | import java.io.IOException; |
41 | 51 |
|
42 | 52 | /** |
43 | | - * This Filter does basically the same as a PointLightShadowRenderer except it |
44 | | - * renders the post shadow pass as a fullscreen quad pass instead of a geometry |
45 | | - * pass. It's mostly faster than PointLightShadowRenderer as long as you have |
46 | | - * more than about ten shadow receiving objects. The expense is the drawback |
47 | | - * that the shadow Receive mode set on spatial is ignored. So basically all and |
48 | | - * only objects that render depth in the scene receive shadows. |
49 | | - * |
50 | | - * API is basically the same as the PssmShadowRenderer. |
| 53 | + * Renders shadows for a {@link PointLight}. This renderer uses six cameras, |
| 54 | + * one for each face of a cube map, to capture shadows from the point light's |
| 55 | + * perspective. |
51 | 56 | * |
52 | 57 | * @author Rémy Bouquet aka Nehon |
53 | 58 | */ |
54 | | -public class PointLightShadowFilter extends AbstractShadowFilter<PointLightShadowRenderer> { |
| 59 | +public class PointLightShadowRenderer extends AbstractShadowRenderer { |
| 60 | + |
| 61 | + /** |
| 62 | + * The fixed number of cameras used for rendering point light shadows (6 for a cube map). |
| 63 | + */ |
| 64 | + public static final int CAM_NUMBER = 6; |
| 65 | + |
| 66 | + protected PointLight light; |
| 67 | + protected Camera[] shadowCams; |
| 68 | + protected Geometry[] frustums = null; |
| 69 | + protected final Vector3f X_NEG = Vector3f.UNIT_X.mult(-1f); |
| 70 | + protected final Vector3f Y_NEG = Vector3f.UNIT_Y.mult(-1f); |
| 71 | + protected final Vector3f Z_NEG = Vector3f.UNIT_Z.mult(-1f); |
55 | 72 |
|
56 | 73 | /** |
57 | 74 | * For serialization only. Do not use. |
58 | | - * |
59 | | - * @see #PointLightShadowFilter(AssetManager assetManager, int shadowMapSize) |
60 | 75 | */ |
61 | | - protected PointLightShadowFilter() { |
| 76 | + protected PointLightShadowRenderer() { |
62 | 77 | super(); |
63 | 78 | } |
64 | 79 |
|
65 | 80 | /** |
66 | | - * Creates a PointLightShadowFilter. |
| 81 | + * Creates a new {@code PointLightShadowRenderer} instance. |
67 | 82 | * |
68 | | - * @param assetManager the application's asset manager |
69 | | - * @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc...) |
| 83 | + * @param assetManager The application's asset manager. |
| 84 | + * @param shadowMapSize The size of the rendered shadow maps (e.g., 512, 1024, 2048). |
| 85 | + * Higher values produce better quality shadows but may impact performance. |
70 | 86 | */ |
71 | | - public PointLightShadowFilter(AssetManager assetManager, int shadowMapSize) { |
72 | | - super(assetManager, shadowMapSize, new PointLightShadowRenderer(assetManager, shadowMapSize)); |
| 87 | + public PointLightShadowRenderer(AssetManager assetManager, int shadowMapSize) { |
| 88 | + super(assetManager, shadowMapSize, CAM_NUMBER); |
| 89 | + init(shadowMapSize); |
| 90 | + } |
| 91 | + |
| 92 | + private void init(int shadowMapSize) { |
| 93 | + shadowCams = new Camera[CAM_NUMBER]; |
| 94 | + for (int i = 0; i < shadowCams.length; i++) { |
| 95 | + shadowCams[i] = new Camera(shadowMapSize, shadowMapSize); |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + @Override |
| 100 | + protected void initFrustumCam() { |
| 101 | + Camera viewCam = viewPort.getCamera(); |
| 102 | + frustumCam = viewCam.clone(); |
| 103 | + frustumCam.setFrustum(viewCam.getFrustumNear(), zFarOverride, |
| 104 | + viewCam.getFrustumLeft(), viewCam.getFrustumRight(), viewCam.getFrustumTop(), viewCam.getFrustumBottom()); |
| 105 | + } |
| 106 | + |
| 107 | + @Override |
| 108 | + protected void updateShadowCams(Camera viewCam) { |
| 109 | + |
| 110 | + if (light == null) { |
| 111 | + logger.warning("The light can't be null for a " + getClass().getName()); |
| 112 | + return; |
| 113 | + } |
| 114 | + |
| 115 | + // Configure axes for each of the six cube map cameras (positive/negative X, Y, Z) |
| 116 | + shadowCams[0].setAxes(X_NEG, Z_NEG, Y_NEG); // -Y (bottom) |
| 117 | + shadowCams[1].setAxes(X_NEG, Vector3f.UNIT_Z, Vector3f.UNIT_Y); // +Y (top) |
| 118 | + shadowCams[2].setAxes(X_NEG, Vector3f.UNIT_Y, Z_NEG); // +Z (forward) |
| 119 | + shadowCams[3].setAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z); // -Z (backward) |
| 120 | + shadowCams[4].setAxes(Vector3f.UNIT_Z, Vector3f.UNIT_Y, X_NEG); // -X (left) |
| 121 | + shadowCams[5].setAxes(Z_NEG, Vector3f.UNIT_Y, Vector3f.UNIT_X); // +X (right) |
| 122 | + |
| 123 | + // Set perspective and location for all shadow cameras |
| 124 | + for (Camera shadowCam : shadowCams) { |
| 125 | + shadowCam.setFrustumPerspective(90f, 1f, 0.1f, light.getRadius()); |
| 126 | + shadowCam.setLocation(light.getPosition()); |
| 127 | + shadowCam.update(); |
| 128 | + shadowCam.updateViewProjection(); |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + @Override |
| 133 | + protected GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders) { |
| 134 | + for (Spatial scene : viewPort.getScenes()) { |
| 135 | + ShadowUtil.getGeometriesInCamFrustum(scene, shadowCams[shadowMapIndex], RenderQueue.ShadowMode.Cast, shadowMapOccluders); |
| 136 | + } |
| 137 | + return shadowMapOccluders; |
| 138 | + } |
| 139 | + |
| 140 | + @Override |
| 141 | + protected void getReceivers(GeometryList lightReceivers) { |
| 142 | + lightReceivers.clear(); |
| 143 | + for (Spatial scene : viewPort.getScenes()) { |
| 144 | + ShadowUtil.getLitGeometriesInViewPort(scene, viewPort.getCamera(), shadowCams, RenderQueue.ShadowMode.Receive, lightReceivers); |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + @Override |
| 149 | + protected Camera getShadowCam(int shadowMapIndex) { |
| 150 | + return shadowCams[shadowMapIndex]; |
73 | 151 | } |
74 | 152 |
|
| 153 | + @Override |
| 154 | + protected void doDisplayFrustumDebug(int shadowMapIndex) { |
| 155 | + if (frustums == null) { |
| 156 | + frustums = new Geometry[CAM_NUMBER]; |
| 157 | + Vector3f[] points = new Vector3f[8]; |
| 158 | + for (int i = 0; i < points.length; i++) { |
| 159 | + points[i] = new Vector3f(); |
| 160 | + } |
| 161 | + for (int i = 0; i < CAM_NUMBER; i++) { |
| 162 | + ShadowUtil.updateFrustumPoints2(shadowCams[i], points); |
| 163 | + frustums[i] = createFrustum(points, i); |
| 164 | + } |
| 165 | + } |
| 166 | + Geometry geo = frustums[shadowMapIndex]; |
| 167 | + if (geo.getParent() == null) { |
| 168 | + getMainScene().attachChild(geo); |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + @Override |
| 173 | + protected void setMaterialParameters(Material material) { |
| 174 | + material.setVector3("LightPos", light == null ? new Vector3f() : light.getPosition()); |
| 175 | + } |
| 176 | + |
| 177 | + @Override |
| 178 | + protected void clearMaterialParameters(Material material) { |
| 179 | + material.clearParam("LightPos"); |
| 180 | + } |
| 181 | + |
75 | 182 | /** |
76 | | - * Returns the light used to cast shadows. |
| 183 | + * gets the point light used to cast shadows with this processor |
77 | 184 | * |
78 | | - * @return the PointLight |
| 185 | + * @return the point light |
79 | 186 | */ |
80 | 187 | public PointLight getLight() { |
81 | | - return shadowRenderer.getLight(); |
| 188 | + return light; |
82 | 189 | } |
83 | 190 |
|
84 | 191 | /** |
85 | | - * Sets the light to use to cast shadows. |
| 192 | + * sets the light to use for casting shadows with this processor |
86 | 193 | * |
87 | | - * @param light the PointLight |
| 194 | + * @param light the point light |
88 | 195 | */ |
89 | 196 | public void setLight(PointLight light) { |
90 | | - shadowRenderer.setLight(light); |
| 197 | + this.light = light; |
91 | 198 | } |
92 | 199 |
|
93 | 200 | @Override |
94 | | - public void write(JmeExporter ex) throws IOException { |
95 | | - super.write(ex); |
96 | | - OutputCapsule oc = ex.getCapsule(this); |
97 | | - oc.write(shadowRenderer, "shadowRenderer", null); |
| 201 | + public void cloneFields(final Cloner cloner, final Object original) { |
| 202 | + light = cloner.clone(light); |
| 203 | + init((int) shadowMapSize); |
| 204 | + frustums = null; |
| 205 | + super.cloneFields(cloner, original); |
98 | 206 | } |
99 | 207 |
|
100 | 208 | @Override |
101 | 209 | public void read(JmeImporter im) throws IOException { |
102 | 210 | super.read(im); |
103 | 211 | InputCapsule ic = im.getCapsule(this); |
104 | | - shadowRenderer = (PointLightShadowRenderer) ic.readSavable("shadowRenderer", null); |
| 212 | + light = (PointLight) ic.readSavable("light", null); |
| 213 | + init((int) shadowMapSize); |
| 214 | + } |
| 215 | + |
| 216 | + @Override |
| 217 | + public void write(JmeExporter ex) throws IOException { |
| 218 | + super.write(ex); |
| 219 | + OutputCapsule oc = ex.getCapsule(this); |
| 220 | + oc.write(light, "light", null); |
105 | 221 | } |
106 | 222 |
|
| 223 | + /** |
| 224 | + * |
| 225 | + * @param viewCam a Camera to define the view frustum |
| 226 | + * @return true if intersects |
| 227 | + */ |
| 228 | + @Override |
| 229 | + protected boolean checkCulling(Camera viewCam) { |
| 230 | + |
| 231 | + if (light == null) { |
| 232 | + return false; |
| 233 | + } |
| 234 | + |
| 235 | + Camera cam = viewCam; |
| 236 | + if (frustumCam != null) { |
| 237 | + cam = frustumCam; |
| 238 | + cam.setLocation(viewCam.getLocation()); |
| 239 | + cam.setRotation(viewCam.getRotation()); |
| 240 | + } |
| 241 | + TempVars vars = TempVars.get(); |
| 242 | + boolean intersects = light.intersectsFrustum(cam, vars); |
| 243 | + vars.release(); |
| 244 | + return intersects; |
| 245 | + } |
107 | 246 | } |
0 commit comments