Skip to content

Commit 9eb6817

Browse files
committed
singlepass sph baker and fast path
1 parent 530c865 commit 9eb6817

6 files changed

Lines changed: 222 additions & 64 deletions

File tree

jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import java.util.function.Predicate;
3737
import com.jme3.asset.AssetManager;
3838
import com.jme3.environment.baker.IBLGLEnvBakerLight;
39-
import com.jme3.environment.baker.IBLHybridEnvBakerLight;
4039
import com.jme3.export.InputCapsule;
4140
import com.jme3.export.JmeExporter;
4241
import com.jme3.export.JmeImporter;
@@ -49,7 +48,6 @@
4948
import com.jme3.scene.Node;
5049
import com.jme3.scene.Spatial;
5150
import com.jme3.scene.control.Control;
52-
import com.jme3.texture.Image.Format;
5351

5452
/**
5553
* A control that automatically handles environment bake and rebake including
@@ -87,6 +85,8 @@ public class EnvironmentProbeControl extends LightProbe implements Control {
8785
private float frustumNear = 0.001f, frustumFar = 1000f;
8886
private String uuid = "none";
8987
private boolean enabled = true;
88+
private IBLGLEnvBakerLight.SphericalHarmonicsMode sphericalHarmonicsMode =
89+
IBLGLEnvBakerLight.SphericalHarmonicsMode.AUTO;
9090

9191
private Predicate<Geometry> filter = (s) -> {
9292
return s.getUserData("tags.env") != null || s.getUserData("tags.env.env" + uuid) != null;
@@ -187,7 +187,18 @@ public static void untagGlobal(Spatial s) {
187187

188188
@Override
189189
public Control cloneForSpatial(Spatial spatial) {
190-
throw new UnsupportedOperationException();
190+
EnvironmentProbeControl control = new EnvironmentProbeControl();
191+
control.setAssetManager(assetManager);
192+
control.setFrustumFar(frustumFar);
193+
control.setFrustumNear(frustumNear);
194+
control.setRequiredSavableResults(requiredSavableResults);
195+
control.setEnabled(enabled);
196+
control.setSphericalHarmonicsMode(sphericalHarmonicsMode);
197+
control.envMapSize = envMapSize;
198+
control.uuid = uuid;
199+
control.filter = filter;
200+
control.spatial = spatial;
201+
return control;
191202
}
192203

193204
/**
@@ -211,6 +222,38 @@ public boolean isRequiredSavableResults() {
211222
return requiredSavableResults;
212223
}
213224

225+
/**
226+
* Sets how spherical harmonics coefficients are baked by this control.
227+
*
228+
* @param mode the spherical harmonics bake mode
229+
*/
230+
public void setSphericalHarmonicsMode(IBLGLEnvBakerLight.SphericalHarmonicsMode mode) {
231+
if (mode == null) {
232+
throw new IllegalArgumentException("mode cannot be null");
233+
}
234+
sphericalHarmonicsMode = mode;
235+
}
236+
237+
/**
238+
* Returns the spherical harmonics bake mode used by this control.
239+
*
240+
* @return the spherical harmonics bake mode
241+
*/
242+
public IBLGLEnvBakerLight.SphericalHarmonicsMode getSphericalHarmonicsMode() {
243+
return sphericalHarmonicsMode;
244+
}
245+
246+
/**
247+
* Enables or disables the spherical harmonics fast path explicitly.
248+
*
249+
* @param enabled true to use the fast path, false to use the quality path
250+
*/
251+
public void setSphericalHarmonicsFastPathEnabled(boolean enabled) {
252+
setSphericalHarmonicsMode(enabled
253+
? IBLGLEnvBakerLight.SphericalHarmonicsMode.FAST
254+
: IBLGLEnvBakerLight.SphericalHarmonicsMode.QUALITY);
255+
}
256+
214257
@Override
215258
public void setSpatial(Spatial spatial) {
216259
if (this.spatial != null && spatial != null && spatial != this.spatial) {
@@ -287,8 +330,9 @@ public void setAssetManager(AssetManager assetManager) {
287330
}
288331

289332
void rebakeNow(RenderManager renderManager) {
290-
IBLHybridEnvBakerLight baker = new IBLGLEnvBakerLight(renderManager, assetManager, null,
333+
IBLGLEnvBakerLight baker = new IBLGLEnvBakerLight(renderManager, assetManager, null,
291334
null, envMapSize, envMapSize);
335+
baker.setSphericalHarmonicsMode(sphericalHarmonicsMode);
292336

293337
baker.setTexturePulling(isRequiredSavableResults());
294338
baker.bakeEnvironment(spatial, getPosition(), frustumNear, frustumFar, filter);
@@ -331,6 +375,8 @@ public void write(JmeExporter ex) throws IOException {
331375
oc.write(frustumFar, "frustumFar", 1000f);
332376
oc.write(frustumNear, "frustumNear", 0.001f);
333377
oc.write(uuid, "envProbeControlUUID", "none");
378+
oc.write(sphericalHarmonicsMode, "sphericalHarmonicsMode",
379+
IBLGLEnvBakerLight.SphericalHarmonicsMode.AUTO);
334380
}
335381

336382
@Override
@@ -346,6 +392,9 @@ public void read(JmeImporter im) throws IOException {
346392
frustumFar = ic.readFloat("frustumFar", 1000f);
347393
frustumNear = ic.readFloat("frustumNear", 0.001f);
348394
uuid = ic.readString("envProbeControlUUID", "none");
395+
sphericalHarmonicsMode = ic.readEnum("sphericalHarmonicsMode",
396+
IBLGLEnvBakerLight.SphericalHarmonicsMode.class,
397+
IBLGLEnvBakerLight.SphericalHarmonicsMode.AUTO);
349398
}
350399

351400
}

jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java

Lines changed: 97 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import com.jme3.renderer.RenderManager;
4545
import com.jme3.scene.Geometry;
4646
import com.jme3.scene.shape.Box;
47+
import com.jme3.system.JmeSystem;
48+
import com.jme3.system.Platform;
4749
import com.jme3.texture.FrameBuffer;
4850
import com.jme3.texture.Image;
4951
import com.jme3.texture.Texture2D;
@@ -63,8 +65,31 @@
6365
*/
6466
public class IBLGLEnvBakerLight extends IBLHybridEnvBakerLight {
6567
private static final int NUM_SH_COEFFICIENT = 9;
68+
private static final int DEFAULT_FAST_SH_SAMPLE_COUNT = 8192;
6669
private static final Logger LOG = Logger.getLogger(IBLGLEnvBakerLight.class.getName());
6770

71+
/**
72+
* Selects how spherical harmonics coefficients are baked on the GPU.
73+
*/
74+
public enum SphericalHarmonicsMode {
75+
/**
76+
* Use full cubemap integration on desktop and the Hammersley fast path on
77+
* Android and iOS.
78+
*/
79+
AUTO,
80+
/**
81+
* Use Hammersley sampling.
82+
*/
83+
FAST,
84+
/**
85+
* Integrate every cubemap texel in a single shader pass.
86+
*/
87+
QUALITY
88+
}
89+
90+
private SphericalHarmonicsMode sphericalHarmonicsMode = SphericalHarmonicsMode.AUTO;
91+
private int sphericalHarmonicsFastPathSampleCount = DEFAULT_FAST_SH_SAMPLE_COUNT;
92+
6893
/**
6994
* Create a new IBL env baker
7095
*
@@ -91,6 +116,49 @@ public boolean isTexturePulling() {
91116
return this.texturePulling;
92117
}
93118

119+
/**
120+
* Sets how spherical harmonics coefficients are baked.
121+
*
122+
* @param mode the spherical harmonics bake mode
123+
*/
124+
public void setSphericalHarmonicsMode(SphericalHarmonicsMode mode) {
125+
if (mode == null) {
126+
throw new IllegalArgumentException("mode cannot be null");
127+
}
128+
this.sphericalHarmonicsMode = mode;
129+
}
130+
131+
/**
132+
* Returns the current spherical harmonics bake mode.
133+
*
134+
* @return the spherical harmonics bake mode
135+
*/
136+
public SphericalHarmonicsMode getSphericalHarmonicsMode() {
137+
return sphericalHarmonicsMode;
138+
}
139+
140+
/**
141+
* Sets the sample count used by the Hammersley spherical harmonics fast path.
142+
*
143+
* @param sampleCount the number of samples, must be positive
144+
*/
145+
public void setSphericalHarmonicsFastPathSampleCount(int sampleCount) {
146+
if (sampleCount <= 0) {
147+
throw new IllegalArgumentException("sampleCount must be greater than zero");
148+
}
149+
this.sphericalHarmonicsFastPathSampleCount = sampleCount;
150+
}
151+
152+
/**
153+
* Returns the sample count used by the Hammersley spherical harmonics fast path.
154+
*
155+
* @return the Hammersley sample count
156+
*/
157+
public int getSphericalHarmonicsFastPathSampleCount() {
158+
return sphericalHarmonicsFastPathSampleCount;
159+
}
160+
161+
94162
@Override
95163
public void bakeSphericalHarmonicsCoefficients() {
96164
Box boxm = new Box(1, 1, 1);
@@ -99,8 +167,25 @@ public void bakeSphericalHarmonicsCoefficients() {
99167
Material mat = new Material(assetManager, "Common/IBLSphH/IBLSphH.j3md");
100168
mat.setTexture("Texture", envMap);
101169
mat.setVector2("Resolution", new Vector2f(envMap.getImage().getWidth(), envMap.getImage().getHeight()));
170+
mat.setInt("SampleCount", sphericalHarmonicsFastPathSampleCount);
102171
screen.setMaterial(mat);
103172

173+
switch (sphericalHarmonicsMode) {
174+
case FAST: {
175+
mat.setBoolean("UseFastSphericalHarmonics", true);
176+
break;
177+
}
178+
case QUALITY: {
179+
mat.setBoolean("UseFastSphericalHarmonics", false);
180+
break;
181+
}
182+
case AUTO: {
183+
Platform.Os os = JmeSystem.getPlatform().getOs();
184+
mat.setBoolean("UseFastSphericalHarmonics", os == Platform.Os.Android || os == Platform.Os.iOS);
185+
break;
186+
}
187+
}
188+
104189
float remapMaxValue = 0;
105190
Format format = Format.RGBA32F;
106191
if (!renderManager.getRenderer().getCaps().contains(Caps.FloatColorBufferRGBA)) {
@@ -117,38 +202,21 @@ public void bakeSphericalHarmonicsCoefficients() {
117202
mat.clearParam("RemapMaxValue");
118203
}
119204

120-
Texture2D shCoefTx[] = { new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format), new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format) };
121-
122-
FrameBuffer shbaker[] = { new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1), new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1) };
123-
shbaker[0].setSrgb(false);
124-
shbaker[0].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[0]));
125-
126-
shbaker[1].setSrgb(false);
127-
shbaker[1].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[1]));
205+
Texture2D shCoefTx = new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format);
128206

129-
int renderOnT = -1;
207+
FrameBuffer shbaker = new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1);
208+
shbaker.setSrgb(false);
209+
shbaker.addColorTarget(FrameBufferTarget.newTarget(shCoefTx));
130210

131-
for (int faceId = 0; faceId < 6; faceId++) {
132-
if (renderOnT != -1) {
133-
int s = renderOnT;
134-
renderOnT = renderOnT == 0 ? 1 : 0;
135-
mat.setTexture("ShCoef", shCoefTx[s]);
136-
} else {
137-
renderOnT = 0;
138-
}
139-
140-
mat.setInt("FaceId", faceId);
211+
screen.updateLogicalState(0);
212+
screen.updateGeometricState();
141213

142-
screen.updateLogicalState(0);
143-
screen.updateGeometricState();
144-
145-
renderManager.setCamera(updateAndGetInternalCamera(0, shbaker[renderOnT].getWidth(), shbaker[renderOnT].getHeight(), Vector3f.ZERO, 1, 1000), false);
146-
renderManager.getRenderer().setFrameBuffer(shbaker[renderOnT]);
147-
renderManager.renderGeometry(screen);
148-
}
214+
renderManager.setCamera(updateAndGetInternalCamera(0, shbaker.getWidth(), shbaker.getHeight(), Vector3f.ZERO, 1, 1000), false);
215+
renderManager.getRenderer().setFrameBuffer(shbaker);
216+
renderManager.renderGeometry(screen);
149217

150-
ByteBuffer shCoefRaw = BufferUtils.createByteBuffer(NUM_SH_COEFFICIENT * 1 * (shbaker[renderOnT].getColorTarget().getFormat().getBitsPerPixel() / 8));
151-
renderManager.getRenderer().readFrameBufferWithFormat(shbaker[renderOnT], shCoefRaw, shbaker[renderOnT].getColorTarget().getFormat());
218+
ByteBuffer shCoefRaw = BufferUtils.createByteBuffer(NUM_SH_COEFFICIENT * 1 * (shbaker.getColorTarget().getFormat().getBitsPerPixel() / 8));
219+
renderManager.getRenderer().readFrameBufferWithFormat(shbaker, shCoefRaw, shbaker.getColorTarget().getFormat());
152220
shCoefRaw.rewind();
153221

154222
Image img = new Image(format, NUM_SH_COEFFICIENT, 1, shCoefRaw, ColorSpace.Linear);
@@ -176,6 +244,7 @@ else if (weightAccum != c.a) {
176244
}
177245
EnvMapUtils.prepareShCoefs(shCoef);
178246
img.dispose();
247+
shbaker.dispose();
179248

180249
}
181250
}

jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ in vec3 LocalPos;
1515

1616

1717
uniform samplerCube m_Texture;
18-
#ifdef SH_COEF
19-
uniform sampler2D m_ShCoef;
18+
#ifdef FAST_SPHERICAL_HARMONICS
19+
uniform int m_SampleCount;
2020
#endif
2121
uniform vec2 m_Resolution;
22-
uniform int m_FaceId;
2322

2423
const float sqrtPi = sqrt(PI);
2524
const float sqrt3Pi = sqrt(3. / PI);
@@ -30,7 +29,6 @@ const float sqrt15Pi = sqrt(15. / PI);
3029
uniform float m_RemapMaxValue;
3130
#endif
3231

33-
3432
vec3 getVectorFromCubemapFaceTexCoord(float x, float y, float mapSize, int face) {
3533
float u;
3634
float v;
@@ -145,6 +143,37 @@ vec3 pixelFaceToV(int faceId, float pixelX, float pixelY, float cubeMapSize) {
145143
return normalize(direction);
146144
}
147145

146+
#ifdef FAST_SPHERICAL_HARMONICS
147+
void sphHammersleyKernel(int coefficientIndex, out vec3 shCoef, out float weightAccum) {
148+
vec3 texelVect = vec3(0.0);
149+
float shDir = 0.0;
150+
vec4 color = vec4(0.0);
151+
152+
shCoef = vec3(0.0);
153+
weightAccum = 0.0;
154+
155+
for(int sampleIndex = 0; sampleIndex < m_SampleCount; sampleIndex++) {
156+
vec4 xi = Hammersley(uint(sampleIndex), uint(m_SampleCount));
157+
float z = 1.0 - 2.0 * xi.x;
158+
float r = sqrt(max(0.0, 1.0 - z * z));
159+
float phi = 2.0 * PI * xi.y;
160+
texelVect = vec3(r * cos(phi), z, r * sin(phi));
161+
evalShBasis(texelVect, coefficientIndex, shDir);
162+
color = texture(m_Texture, texelVect);
163+
shCoef.x = (shCoef.x + color.r * shDir);
164+
shCoef.y = (shCoef.y + color.g * shDir);
165+
shCoef.z = (shCoef.z + color.b * shDir);
166+
weightAccum += 1.0;
167+
}
168+
169+
#ifdef REMAP_MAX_VALUE
170+
float sampleWeight = 4.0 * PI / float(m_SampleCount);
171+
shCoef.xyz = shCoef.xyz * sampleWeight;
172+
weightAccum = weightAccum * sampleWeight;
173+
#endif
174+
}
175+
#endif
176+
148177
void sphKernel() {
149178
int width = int(m_Resolution.x);
150179
int height = int(m_Resolution.y);
@@ -155,28 +184,26 @@ void sphKernel() {
155184

156185
int i=int(gl_FragCoord.x);
157186

158-
#ifdef SH_COEF
159-
vec4 r=texelFetch(m_ShCoef, ivec2(i, 0), 0);
160-
vec3 shCoef=r.rgb;
161-
float weightAccum = r.a;
162-
#else
163-
vec3 shCoef=vec3(0.0);
164-
float weightAccum = 0.0;
165-
#endif
187+
vec3 shCoef=vec3(0.0);
188+
float weightAccum = 0.0;
166189

167-
for(int y = 0; y < height; y++) {
168-
for(int x = 0; x < width; x++) {
169-
weight = getSolidAngleAndVector(float(x), float(y), float(width), m_FaceId, texelVect);
170-
evalShBasis(texelVect, i, shDir);
171-
color = texture(m_Texture, texelVect);
172-
shCoef.x = (shCoef.x + color.r * shDir * weight);
173-
shCoef.y = (shCoef.y + color.g * shDir * weight);
174-
shCoef.z = (shCoef.z + color.b * shDir * weight);
175-
weightAccum += weight;
190+
#ifdef FAST_SPHERICAL_HARMONICS
191+
sphHammersleyKernel(i, shCoef, weightAccum);
192+
#else
193+
for(int faceId = 0; faceId < 6; faceId++) {
194+
for(int y = 0; y < height; y++) {
195+
for(int x = 0; x < width; x++) {
196+
weight = getSolidAngleAndVector(float(x), float(y), float(width), faceId, texelVect);
197+
evalShBasis(texelVect, i, shDir);
198+
color = texture(m_Texture, texelVect);
199+
shCoef.x = (shCoef.x + color.r * shDir * weight);
200+
shCoef.y = (shCoef.y + color.g * shDir * weight);
201+
shCoef.z = (shCoef.z + color.b * shDir * weight);
202+
weightAccum += weight;
203+
}
204+
}
176205
}
177-
}
178-
179-
206+
#endif
180207

181208
#ifdef REMAP_MAX_VALUE
182209
shCoef.xyz=shCoef.xyz*m_RemapMaxValue;

0 commit comments

Comments
 (0)