Problem with HDR in Panda3D

At the outset, I honestly admit that I’ve already posted this question in a comment on another thread, but since I wasn’t able to get a problem-solving answer, I decided to create a new thread. I apologize for the spam, but I know that new threads usually attract more attention, so perhaps this way I’ll be able to reach a larger number of people.

I have no problem to implement tone mapping in the shader myself. Unfortunately, it seems to me that this HDR just doesn’t work for me.

Let’s take an example.

Here is the Panda3D Python code:

from direct.filter.FilterManager import FilterManager
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (
    LightRampAttrib,
    ColorBlendAttrib,
    Texture,
    Shader,
    loadPrcFileData,
)

loadPrcFileData("", "gl-version 4 1")
base = ShowBase()
base.setBackgroundColor(0.75, 0.75, 0.75)
# noinspection PyArgumentList
base.render.setAttrib(LightRampAttrib.makeHdr0())
# noinspection PyArgumentList
base.render.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd))
model = base.loader.loadModel("ripple")
model.reparentTo(base.render)
model.setPos(0, 2, 0)
manager = FilterManager(base.win, base.cam)
tex = Texture()
quad = manager.renderSceneInto(colortex=tex)
# noinspection PyArgumentList
quad.setShader(
    Shader.load(
        Shader.SL_GLSL,
        vertex="basic.vert",
        fragment="basic.frag",
    )
)
quad.setShaderInput("tex", tex)
base.run()

Here is the vertex shader code in GLSL:

#version 410
uniform mat4 p3d_ModelViewProjectionMatrix;
in vec4 p3d_Vertex;
void main() {
    gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;
}

Here is the shader fragment code in GLSL:

#version 410
uniform sampler2D tex;
out vec4 p3d_FragColor;
void main() {
    p3d_FragColor = texture(tex, gl_FragCoord.xy / textureSize(tex, 0));
    p3d_FragColor.rgb -= 0.75;
}

Now let me explain what’s going on here.

I’m loading one of the standard Panda3D objects, “ripple”. I place the object on a fairly light background: (0.75, 0.75, 0.75). Normally it would look like this:

But this time, what’s more, I render the object in additive mode, i.e. the pixel brightness of the “ripple” itself is added to the already bright background. Naturally, a large part of the pixels are overexposed and looks like this:

Well, yes, but we supposedly have HDR turned on, i.e. we have inactive upper clamping, which means that pixels can take values with > 1.0 components. So if I then add a full-screen quad, which will act as a filter, and in this filter I subtract 0.75 from each component, I would expect the background to become black (because 0.75 - 0.75 = 0.0), and the object “ripple” will have the original colors. However, this is not quite what happens:

Yes, the background becomes black (with HDR not needed here, of course, because we were within the scale), but the colors of the object are distorted. So clearly, somewhere along the way, there was an upper clamping at the value of 1.0, despite the HDR mode being enabled for the entire scene (for base.render).

Where am I making a mistake?

The “hdr” mode in LightRampAttrib just instructs the ShaderGenerator to pre-apply tone mapping, which is almost certainly not what you want - you just want an identity LightRampAttrib to disable clamping, probably.

But, that doesn’t instruct Panda to get a floating-point framebuffer. To achieve that, you need to pass an fbprops parameter to renderSceneInto containing a FrameBufferProperties that specifies setFloatColor(True).

You could use CommonFilters with setHighDynamicRange which does all this for you with a built-in tonemapping shader.

3 Likes