Repository: ManimCommunity/manim

Issue:
Setting colors and opacities in OpenGLVMobject doesn't change attributes
## Description of bug / unexpected behavior
<!-- Add a clear and concise description of the problem you encountered. -->

Calling `set_color`, `set_fill` and `set_stroke` on `OpenGLVMobject` doesn't change attributes like `self.opacity`, `self.fill_color` etc. It might not have been noticed because it does set rgbas which is used by the shaders, however having these attributes and not ever setting them is confusing and could cause other issues if some logic is trying to utilize them.

All the below assertions fail

```py
class Tests(Scene):
    def construct(self):
        m = OpenGLVMobject()
        m.set_stroke(color=ORANGE, width=2, opacity=0.8)
        m.set_fill(color=ORANGE, opacity=0.8)
        m.set_color(color=ORANGE)
        assert m.stroke_opacity == 0.8
        assert m.stroke_color == ORANGE
        assert m.fill_opacity == 0.8
        assert m.stroke_color == ORANGE
        assert m.color == ORANGE
```


## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->

When passing colors and opacities, the rgbas should be set as it is, however the other corresponding color / opacity attributes should also be set.


## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->

Run above code with the opengl renderer and check the assertions, all the assertions fail.

<details><summary>Code for reproducing the problem</summary>

```py
class Tests(Scene):
    def construct(self):
        m = OpenGLVMobject()
        m.set_stroke(color=ORANGE, width=2, opacity=0.8)
        m.set_fill(color=ORANGE, opacity=0.8)
        m.set_color(color=ORANGE)
        assert m.stroke_opacity == 0.8
        assert m.stroke_color == ORANGE
        assert m.fill_opacity == 0.8
        assert m.stroke_color == ORANGE
        assert m.color == ORANGE
```

</details>


## Additional media files
<!-- Paste in the files manim produced on rendering the code above. -->

<details><summary>Images/GIFs</summary>

<!-- PASTE MEDIA HERE -->

</details>


## Logs
<details><summary>Terminal output</summary>
<!-- Add "-v DEBUG" when calling manim to generate more detailed logs -->

```
──────────────────────────────────────────────────────────────────────────────╮
│ File                                                                         │
│"/Users/ryan/PycharmProjects/manim-community/manim/cli/render/commands.py",   │
│line 108, in render                                                           │
│    105             while keep_running:                                       │
│    106                 for SceneClass in scene_classes_from_file(file):      │
│    107                     scene = SceneClass(renderer)                      │
│  ❱ 108                     rerun = scene.render()                            │
│    109                     if rerun or config["write_all"]:                  │
│    110                         renderer.num_plays = 0                        │
│    111                         continue                                      │
│ File "/Users/ryan/PycharmProjects/manim-community/manim/scene/scene.py", line│
│215, in render                                                                │
│     212         """                                                          │
│     213         self.setup()                                                 │
│     214         try:                                                         │
│  ❱  215             self.construct()                                         │
│     216         except EndSceneEarlyException:                               │
│     217             pass                                                     │
│     218         except RerunSceneException as e:                             │
│ File "../scenes/playground.py", line 74, in construct                        │
│     71         m.set_stroke(color=ORANGE, width=2, opacity=0.8)              │
│     72         m.set_fill(color=ORANGE, opacity=0.8)                         │
│     73         m.set_color(color=ORANGE)                                     │
│  ❱  74         assert m.stroke_opacity == 0.8                                │
│     75         assert m.stroke_color == ORANGE                               │
│     76         assert m.fill_opacity == 0.8                                  │
│     77         assert m.stroke_color == ORANGE                               │
╰──────────────────────────────────────────────────────────────────────────────╯
AssertionError: 
```

<!-- Insert screenshots here (only when absolutely necessary, we prefer copy/pasted output!) -->

</details>


## System specifications

<details><summary>System Details</summary>

- OS (with version, e.g Windows 10 v2004 or macOS 10.15 (Catalina)):
- RAM:
- Python version (`python/py/python3 --version`):
- Installed modules (provide output from `pip list`):
```
PASTE HERE
```
</details>

<details><summary>LaTeX details</summary>

+ LaTeX distribution (e.g. TeX Live 2020):
+ Installed LaTeX packages:
<!-- output of `tlmgr list --only-installed` for TeX Live or a screenshot of the Packages page for MikTeX -->
</details>

<details><summary>FFMPEG</summary>

Output of `ffmpeg -version`:

```
PASTE HERE
```
</details>

## Additional comments
<!-- Add further context that you think might be relevant for this issue here. -->



Discussion:

Resolving patch:
diff --git a/manim/mobject/opengl_mobject.py b/manim/mobject/opengl_mobject.py
--- a/manim/mobject/opengl_mobject.py
+++ b/manim/mobject/opengl_mobject.py
@@ -1326,6 +1326,10 @@ def set_color(self, color, opacity=None, recurse=True):
         self.set_rgba_array(color, opacity, recurse=False)
         # Recurse to submobjects differently from how set_rgba_array
         # in case they implement set_color differently
+        if color is not None:
+            self.color = Color(color)
+        if opacity is not None:
+            self.opacity = opacity
         if recurse:
             for submob in self.submobjects:
                 submob.set_color(color, recurse=True)
diff --git a/manim/mobject/types/opengl_vectorized_mobject.py b/manim/mobject/types/opengl_vectorized_mobject.py
--- a/manim/mobject/types/opengl_vectorized_mobject.py
+++ b/manim/mobject/types/opengl_vectorized_mobject.py
@@ -5,6 +5,7 @@
 
 import moderngl
 import numpy as np
+from colour import Color
 
 from ... import config
 from ...constants import *
@@ -152,6 +153,14 @@ def init_colors(self):
         return self
 
     def set_fill(self, color=None, opacity=None, recurse=True):
+        if color is not None:
+            self.fill_color = Color(color)
+        if opacity is not None:
+            self.fill_opacity = opacity
+        if recurse:
+            for submobject in self.submobjects:
+                submobject.set_fill(color, opacity, recurse)
+
         self.set_rgba_array(color, opacity, "fill_rgba", recurse)
         return self
 
@@ -163,6 +172,20 @@ def set_stroke(
         background=None,
         recurse=True,
     ):
+        if color is not None:
+            self.stroke_color = Color(color)
+        if opacity is not None:
+            self.stroke_opacity = opacity
+        if recurse:
+            for submobject in self.submobjects:
+                submobject.set_stroke(
+                    color=color,
+                    width=width,
+                    opacity=opacity,
+                    background=background,
+                    recurse=recurse,
+                )
+
         self.set_rgba_array(color, opacity, "stroke_rgba", recurse)
 
         if width is not None:
@@ -235,9 +258,12 @@ def match_style(self, vmobject, recurse=True):
                 sm1.match_style(sm2)
         return self
 
-    def set_color(self, color, recurse=True):
-        self.set_fill(color, recurse=recurse)
-        self.set_stroke(color, recurse=recurse)
+    def set_color(self, color, opacity=None, recurse=True):
+        self.color = Color(color)
+        if opacity is not None:
+            self.opacity = opacity
+        self.set_fill(color, opacity=opacity, recurse=recurse)
+        self.set_stroke(color, opacity=opacity, recurse=recurse)
         return self
 
     def set_opacity(self, opacity, recurse=True):
diff --git a/manim/renderer/opengl_renderer.py b/manim/renderer/opengl_renderer.py
--- a/manim/renderer/opengl_renderer.py
+++ b/manim/renderer/opengl_renderer.py
@@ -234,6 +234,8 @@ def __init__(self, file_writer_class=SceneFileWriter, skip_animations=False):
         # Initialize texture map.
         self.path_to_texture_id = {}
 
+        self._background_color = color_to_rgba(config["background_color"], 1.0)
+
     def init_scene(self, scene):
         self.partial_movie_files = []
         self.file_writer = self._file_writer_class(
@@ -405,8 +407,7 @@ def play(self, scene, *args, **kwargs):
             scene.play_internal()
 
     def clear_screen(self):
-        window_background_color = color_to_rgba(config["background_color"])
-        self.frame_buffer_object.clear(*window_background_color)
+        self.frame_buffer_object.clear(*self.background_color)
         self.window.swap_buffers()
 
     def render(self, scene, frame_offset, moving_mobjects):
@@ -424,8 +425,7 @@ def render(self, scene, frame_offset, moving_mobjects):
                 self.window.swap_buffers()
 
     def update_frame(self, scene):
-        window_background_color = color_to_rgba(config["background_color"])
-        self.frame_buffer_object.clear(*window_background_color)
+        self.frame_buffer_object.clear(*self.background_color)
         self.refresh_perspective_uniforms(scene.camera)
 
         for mobject in scene.mobjects:
@@ -531,3 +531,11 @@ def pixel_coords_to_space_coords(self, px, py, relative=False):
             # Only scale wrt one axis
             scale = fh / ph
             return fc + scale * np.array([(px - pw / 2), (py - ph / 2), 0])
+
+    @property
+    def background_color(self):
+        return self._background_color
+
+    @background_color.setter
+    def background_color(self, value):
+        self._background_color = color_to_rgba(value, 1.0)


Test patch:
diff --git a/tests/opengl/test_color_opengl.py b/tests/opengl/test_color_opengl.py
new file mode 100644
--- /dev/null
+++ b/tests/opengl/test_color_opengl.py
@@ -0,0 +1,95 @@
+import numpy as np
+
+from manim import BLACK, PURE_GREEN, Scene
+from manim.mobject.opengl_mobject import OpenGLMobject
+from manim.mobject.types.opengl_vectorized_mobject import OpenGLVMobject
+
+
+def test_import_color(using_opengl_renderer):
+    import manim.utils.color as C
+
+    C.WHITE
+
+
+def test_background_color(using_opengl_renderer):
+    S = Scene()
+    S.renderer.background_color = "#ff0000"
+    S.renderer.update_frame(S)
+    assert np.all(S.renderer.get_frame()[0, 0] == np.array([255, 0, 0, 255]))
+
+    S.renderer.background_color = "#436f80"
+    S.renderer.update_frame(S)
+    assert np.all(S.renderer.get_frame()[0, 0] == np.array([67, 111, 128, 255]))
+
+    S.renderer.background_color = "#fff"
+    S.renderer.update_frame(S)
+    assert np.all(S.renderer.get_frame()[0, 0] == np.array([255, 255, 255, 255]))
+
+
+def test_set_color(using_opengl_renderer):
+    m = OpenGLMobject()
+    assert m.color.hex == "#fff"
+    np.alltrue(m.rgbas == np.array((0.0, 0.0, 0.0, 1.0)))
+
+    m.set_color(BLACK)
+    assert m.color.hex == "#000"
+    np.alltrue(m.rgbas == np.array((1.0, 1.0, 1.0, 1.0)))
+
+    m.set_color(PURE_GREEN, opacity=0.5)
+    assert m.color.hex == "#0f0"
+    np.alltrue(m.rgbas == np.array((0.0, 1.0, 0.0, 0.5)))
+
+    m = OpenGLVMobject()
+    assert m.color.hex == "#fff"
+    np.alltrue(m.fill_rgba == np.array((0.0, 0.0, 0.0, 1.0)))
+    np.alltrue(m.stroke_rgba == np.array((0.0, 0.0, 0.0, 1.0)))
+
+    m.set_color(BLACK)
+    assert m.color.hex == "#000"
+    np.alltrue(m.fill_rgba == np.array((1.0, 1.0, 1.0, 1.0)))
+    np.alltrue(m.stroke_rgba == np.array((1.0, 1.0, 1.0, 1.0)))
+
+    m.set_color(PURE_GREEN, opacity=0.5)
+    assert m.color.hex == "#0f0"
+    np.alltrue(m.fill_rgba == np.array((0.0, 1.0, 0.0, 0.5)))
+    np.alltrue(m.stroke_rgba == np.array((0.0, 1.0, 0.0, 0.5)))
+
+
+def test_set_fill_color(using_opengl_renderer):
+    m = OpenGLVMobject()
+    assert m.fill_color.hex == "#fff"
+    np.alltrue(m.fill_rgba == np.array((0.0, 1.0, 0.0, 0.5)))
+
+    m.set_fill(BLACK)
+    assert m.fill_color.hex == "#000"
+    np.alltrue(m.fill_rgba == np.array((1.0, 1.0, 1.0, 1.0)))
+
+    m.set_fill(PURE_GREEN, opacity=0.5)
+    assert m.fill_color.hex == "#0f0"
+    np.alltrue(m.fill_rgba == np.array((0.0, 1.0, 0.0, 0.5)))
+
+
+def test_set_stroke_color(using_opengl_renderer):
+    m = OpenGLVMobject()
+    assert m.stroke_color.hex == "#fff"
+    np.alltrue(m.stroke_rgba == np.array((0.0, 1.0, 0.0, 0.5)))
+
+    m.set_stroke(BLACK)
+    assert m.stroke_color.hex == "#000"
+    np.alltrue(m.stroke_rgba == np.array((1.0, 1.0, 1.0, 1.0)))
+
+    m.set_stroke(PURE_GREEN, opacity=0.5)
+    assert m.stroke_color.hex == "#0f0"
+    np.alltrue(m.stroke_rgba == np.array((0.0, 1.0, 0.0, 0.5)))
+
+
+def test_set_fill(using_opengl_renderer):
+    m = OpenGLMobject()
+    assert m.color.hex == "#fff"
+    m.set_color(BLACK)
+    assert m.color.hex == "#000"
+
+    m = OpenGLVMobject()
+    assert m.color.hex == "#fff"
+    m.set_color(BLACK)
+    assert m.color.hex == "#000"