Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Differences in texture blending with ShaderGenerator vs. fixed-function pipeline #189

Closed
Epihaius opened this issue Nov 5, 2017 · 3 comments
Assignees
Labels

Comments

@Epihaius
Copy link
Contributor

Epihaius commented Nov 5, 2017

In short, the problematic settings seem to be the following:

  1. NodePath color scale (affects M_modulate, M_replace, M_blend_color_scale, M_add, M_decal, CM_replace, CM_add, CM_add_signed; not sure about CM_subtract and CM_interpolate);
  2. CM_subtract;
  3. CM_interpolate;
  4. CS_constant_color_scale;
  5. CO_one_minus_src_color;
  6. rgb scale;

while CM_dot3_rgb and CM_dot3_rgba raise an assertion error:

AssertionError: shader != nullptr at line 1565 of c:\buildslave\sdk-windows-i386\build\panda\src\pgraphnodes\shaderGenerator.cxx

Here are some example settings that cause different results when using the ShaderGenerator, compared to those when using the fixed-function pipeline; you can test these settings with the code sample given below.

  1. NodePath color scale:

stage 1: M_modulate
stage 2: M_blend_color_scale with color scale white

stage 1: M_modulate
stage 2: M_add with color scale (1., .3, .1, 1.)

  1. CM_subtract:

stage 1: M_modulate
stage 2: CM_subtract, CS_texture, CO_src_color, CS_previous, CO_src_color

  1. CM_interpolate:

stage 1: M_modulate
stage 2: CM_interpolate, CS_texture, CO_src_color, CS_previous, CO_src_color, CS_previous, CO_src_color

  1. CS_constant_color_scale:

stage 1: M_modulate
stage 2: CM_modulate, CS_constant_color_scale, CO_src_color, CS_previous, CO_src_color

  1. CO_one_minus_src_color:

stage 1: M_modulate
stage 2: CM_modulate, CS_texture, CO_one_minus_src_color, CS_previous, CO_src_color

stage 1: M_add
stage 2: CM_modulate, CS_previous, CO_src_color, CS_texture, CO_one_minus_src_color

  1. rgb scale:

stage 1: M_modulate
stage 2: CM_add_signed, CS_texture, CO_src_color, CS_previous, CO_src_color
stage 2 rgb scale: 2

Here is a code sample that should allow for easy testing of the various combinations of blending modes, combine modes, sources and operands:

from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from direct.showbase.ShowBase import ShowBase
from math import sin, pi


class MyApp(ShowBase):

    def __init__(self):

        ShowBase.__init__(self)

        self.disable_mouse()

        TS = TextureStage
        self._modes = {
            "M_modulate": TS.M_modulate,
            "M_replace": TS.M_replace,
            "M_blend": TS.M_blend,
            "M_blend_color_scale": TS.M_blend_color_scale,
            "M_add": TS.M_add,
            "M_decal": TS.M_decal,
            "CM_dot3_rgb": TS.CM_dot3_rgb,
            "CM_dot3_rgba": TS.CM_dot3_rgba,
            "CM_modulate": TS.CM_modulate,
            "CM_replace": TS.CM_replace,
            "CM_interpolate": TS.CM_interpolate,
            "CM_add": TS.CM_add,
            "CM_add_signed": TS.CM_add_signed,
            "CM_subtract": TS.CM_subtract
        }
        self._combine_sources = {
            "CS_texture": TS.CS_texture,
            "CS_constant": TS.CS_constant,
            "CS_constant_color_scale": TS.CS_constant_color_scale,
            "CS_primary_color": TS.CS_primary_color,
            "CS_previous": TS.CS_previous
        }
        self._combine_operands = {
            "CO_src_color": TS.CO_src_color,
            "CO_one_minus_src_color": TS.CO_one_minus_src_color
        }

        self._ffp_node = self.render.attach_new_node("fixed_function_pipeline")
        self._ffp_node.set_pos(-2.1, 15., 1.65)
        self._shader_node = self.render.attach_new_node("shader_generator")
        self._shader_node.set_shader_auto()
        self._shader_node.set_pos(2.1, 15., 1.65)

        # Create the texture stages

        self._tex_stages = {}

        # the first texture stage should contain a color gradient
        self._tex_stages["ts1"] = ts1 = TextureStage("layer1")
        ts1.set_color((1., 0., 0., .5))
        ts1.set_sort(0)
        ts1.set_combine_rgb(TextureStage.CM_modulate,
                            TextureStage.CS_texture, TextureStage.CO_src_color,
                            TextureStage.CS_previous, TextureStage.CO_src_color)
        # the second texture stage should contain a checker pattern
        self._tex_stages["ts2"] = ts2 = TextureStage("layer2")
        ts2.set_color((0., 1., 1., .5))
        ts2.set_sort(1)

        self._rgb_scales = {}
        self._mode_ids = {}
        self._combine_source_ids = {}
        self._combine_operand_ids = {}

        for ts_id in ("ts1", "ts2"):
            self._rgb_scales[ts_id] = 1
            self._mode_ids[ts_id] = "M_modulate"
            self._combine_source_ids[ts_id] = ["CS_texture", "CS_previous", "CS_previous"]
            self._combine_operand_ids[ts_id] = ["CO_src_color", "CO_src_color", "CO_src_color"]

        OnscreenText(text="fixed-function pipeline", pos=(-.5, .93), scale=.07)
        OnscreenText(text="shader generator", pos=(.5, .93), scale=.07)

        # Create the color gradient

        w = 256
        h = 256
        w_ = 256
        h_ = 256
        rng = h_ / 3.
        img1 = PNMImage(w, h, 4)
        img1.alpha_fill(1.)

        for x in range(w):

            for y in range(h):

                if 0. <= x < rng:
                    # between red and green
                    b = 0.
                    g = x / rng
                    r = 1. - g
                    factor = 1. + sin(g * pi)
                    r *= factor
                    g *= factor
                elif rng <= x < 2. * rng:
                    # between green and blue
                    r = 0.
                    b = (x - rng) / rng
                    g = 1. - b
                    factor = 1. + sin(b * pi)
                    g *= factor
                    b *= factor
                elif 2. * rng <= x < h:
                    # between blue and red
                    g = 0.
                    r = (x - 2. * rng) / rng
                    b = 1. - r
                    factor = 1. + sin(r * pi)
                    b *= factor
                    r *= factor

                img1.set_xel(x, y, r, g, b)

        img_tmp = PNMImage(w, h, 4)
        img_tmp.fill(.5, .5, .5)

        for y in range(h):

            a = 1. * y / h_

            for x in range(w):
                img_tmp.set_alpha(x, y, a)

        img1.blend_sub_image(img_tmp, 0, 0, 0, 0)

        # Create the checker pattern

        w = 256
        h = 256
        w_ = 256
        h_ = 256
        rng = h_ / 4
        img2 = PNMImage(w, h, 4)
        img2.alpha_fill(1.)

        for x in range(w):

            if (x // rng) % 2:

                for y in range(h):

                    if (y // rng) % 2:
                        # blue
                        b = 1.
                        g = r = 0.
                    else:
                        # red
                        r = 1.
                        b = g = 0.

                    img2.set_xel(x, y, r, g, b)

            else:

                for y in range(h):

                    if (y // rng) % 2:
                        # green
                        g = 1.
                        r = b = 0.
                    else:
                        # white
                        r = b = g = 1.

                    img2.set_xel(x, y, r, g, b)

        # Create the geometry and add textures

        cm = CardMaker("quad")
        cm.set_frame(-2., 2., -2., 2.)
        quads = []
        self._has_color_scale = False

        for node in (self._ffp_node, self._shader_node):
            node.set_transparency(TransparencyAttrib.M_alpha)
            quad = node.attach_new_node(cm.generate())
            tex1 = Texture("layer1")
            tex1.load(img1)
            tex2 = Texture("layer2")
            tex2.load(img2)
            quad.set_texture(ts1, tex1)
            quad.set_texture(ts2, tex2)
            quad.set_color((1., 1., 1., 1.))
            quads.append(quad)

        def toggle_color_scale():

            scale = (1., 1., 1., 1.) if self._has_color_scale else (1., .3, .1, 1.)
            self._has_color_scale = not self._has_color_scale

            for quad in quads:
                quad.set_color_scale(scale)

        DirectButton(text="Toggle color scale", pos=(1.1, 0., -.95), scale=.05,
                     command=toggle_color_scale)

        OnscreenText(text="Stage", pos=(-1.18, -.2), scale=.1)
        OnscreenText(text="Mode", pos=(-.65, -.2), scale=.1)
        OnscreenText(text="Sources", pos=(.05, -.2), scale=.1)
        OnscreenText(text="Operands", pos=(.85, -.2), scale=.1)
        OnscreenText(text="1", pos=(-1.17, -.3), scale=.07)
        OnscreenText(text="2", pos=(-1.17, -.55), scale=.07)
        initial_items = ((0, 4, 4))

        for i in range(2):

            def get_command(ts_id):

                def command(mode_id):
                    self._mode_ids[ts_id] = mode_id

                return command

            ts_id = "ts%d" % (i + 1)

            DirectOptionMenu(
                text="M_modulate", pos=(-1., 0., -.3 - .3 * i), scale=.06,
                items=["M_modulate", "M_replace", "M_blend", "M_blend_color_scale",
                       "M_add", "M_decal", "CM_modulate", "CM_replace", "CM_interpolate",
                       "CM_add", "CM_add_signed", "CM_subtract", "CM_dot3_rgb", "CM_dot3_rgba"],
                initialitem=0, highlightColor=(.65, .65, .65, 1.),
                command=get_command(ts_id), textMayChange=1
            )

            for j in range(3):
     
                def get_command(ts_id, j):
     
                    def command(source_id):
                        self._combine_source_ids[ts_id][j] = source_id
     
                    return command

                DirectOptionMenu(
                    text="CS_texture", pos=(-.3, 0., -.3 - .3 * i - j * .1), scale=.06,
                    items=["CS_texture", "CS_constant", "CS_constant_color_scale",
                           "CS_primary_color", "CS_previous"],
                    initialitem=initial_items[j], highlightColor=(.65, .65, .65, 1.),
                    command=get_command(ts_id, j), textMayChange=1
                )

                def get_command(ts_id, j):
     
                    def command(source_id):
                        self._combine_operand_ids[ts_id][j] = source_id
     
                    return command

                DirectOptionMenu(
                    text="CO_src_color", pos=(.5, 0., -.3 - .3 * i - j * .1), scale=.06,
                    items=["CO_src_color", "CO_one_minus_src_color"],
                    initialitem=0, highlightColor=(.65, .65, .65, 1.),
                    command=get_command(ts_id, j), textMayChange=1
                )

            def get_command(ts_id):

                def command(scale_str):
                    self._rgb_scales[ts_id] = int(scale_str)

                return command

            DirectOptionMenu(
                text="1", pos=(-.4 + .5 * i, 0., -.95), scale=.08,
                items=["1", "2", "4"],
                initialitem=0, highlightColor=(.65, .65, .65, 1.),
                command=get_command(ts_id), textMayChange=1
            )

        OnscreenText(text="RGB scale:", pos=(-1., -.95), scale=.09)
        OnscreenText(text="stage 1", pos=(-.6, -.95), scale=.07)
        OnscreenText(text="stage 2", pos=(-.1, -.95), scale=.07)

        def command():

            for i in range(2):

                ts_id = "ts%d" % (i + 1)

                scale = self._rgb_scales[ts_id]
                self._tex_stages[ts_id].set_rgb_scale(scale)

                mode_id = self._mode_ids[ts_id]
                mode = self._modes[mode_id]

                if mode_id.startswith("M_"):

                    self._tex_stages[ts_id].set_mode(mode)

                else:

                    if mode_id == "CM_replace":
                        source_ids = self._combine_source_ids[ts_id][:1]
                        operand_ids = self._combine_operand_ids[ts_id][:1]
                    elif mode_id == "CM_interpolate":
                        source_ids = self._combine_source_ids[ts_id]
                        operand_ids = self._combine_operand_ids[ts_id]
                    else:
                        source_ids = self._combine_source_ids[ts_id][:2]
                        operand_ids = self._combine_operand_ids[ts_id][:2]

                    sources = [self._combine_sources[source_id]
                               for source_id in source_ids]
                    operands = [self._combine_operands[operand_id]
                                for operand_id in operand_ids]
                    args = sum(zip(sources, operands), ())
                    self._tex_stages[ts_id].set_combine_rgb(mode, *args)

        DirectButton(text="Apply", pos=(.75, 0., -.95), scale=.07, command=command)


app = MyApp()
app.run()

tex_blend_bugs

Testing was done using version 0343dbc of Panda3D on Windows 10 64-bit.

@rdb
Copy link
Member

rdb commented Nov 16, 2017

Thanks for the comprehensive test case! I've fixed this now for the cases you described.

@Epihaius
Copy link
Contributor Author

Epihaius commented Nov 17, 2017

Thank you for the fixes! It is indeed working quite well now, except for one (hopefully last) remaining difference: any combine mode using CS_constant_color_scale as one of its sources still gives a different result; see e.g. example 4 I gave earlier:

stage 1: M_modulate
stage 2: CM_modulate, CS_constant_color_scale, CO_src_color, CS_previous, CO_src_color

The M_blend_color_scale mode does work correctly now, but perhaps the combine source was overlooked?
Then again, could it be that CS_constant_color_scale is not that well supported by the fixed-function pipeline, while the ShaderGenerator gives the expected results?

Apart from this, do you think it's worth investigating the alpha combine modes as well, or is it safe to assume that a correctly working rgb combine mode guarantees that the corresponding alpha combine mode works correctly also?

@Epihaius
Copy link
Contributor Author

After editing the code sample to take alpha into account, it looks like the FFP ignores alpha when setting the mode of either or both texture stages to M_blend_color_scale; the result looks identical when setting the mode to M_blend (using the FFP).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants