Browse Source

pgraph: Implement new DepthBiasAttrib to replace DepthOffsetAttrib

Fixes #1157
rdb 4 years ago
parent
commit
53741ffa13

+ 52 - 9
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -52,6 +52,7 @@
 #include "alphaTestAttrib.h"
 #include "clipPlaneAttrib.h"
 #include "cullFaceAttrib.h"
+#include "depthBiasAttrib.h"
 #include "depthOffsetAttrib.h"
 #include "depthWriteAttrib.h"
 #include "fogAttrib.h"
@@ -156,6 +157,13 @@ null_glBlendColor(GLclampf, GLclampf, GLclampf, GLclampf) {
 }
 #endif
 
+#ifndef OPENGLES_1
+static void APIENTRY
+null_glPolygonOffsetClamp(GLfloat factor, GLfloat units, GLfloat clamp) {
+  glPolygonOffset(factor, units);
+}
+#endif
+
 #ifndef OPENGLES_1
 // We have a default shader that will be applied when there isn't any shader
 // applied (e.g.  if it failed to compile).  We need this because OpenGL ES
@@ -626,6 +634,7 @@ reset() {
   _inv_state_mask.clear_bit(ColorAttrib::get_class_slot());
   _inv_state_mask.clear_bit(ColorScaleAttrib::get_class_slot());
   _inv_state_mask.clear_bit(CullFaceAttrib::get_class_slot());
+  _inv_state_mask.clear_bit(DepthBiasAttrib::get_class_slot());
   _inv_state_mask.clear_bit(DepthOffsetAttrib::get_class_slot());
   _inv_state_mask.clear_bit(DepthTestAttrib::get_class_slot());
   _inv_state_mask.clear_bit(DepthWriteAttrib::get_class_slot());
@@ -3365,6 +3374,21 @@ reset() {
     _has_attrib_depth_range = false;
   }
 
+#ifndef OPENGLES_1
+#ifndef OPENGLES
+  if (is_at_least_gl_version(4, 6) || has_extension("GL_ARB_polygon_offset_clamp")) {
+    _glPolygonOffsetClamp = (PFNGLPOLYGONOFFSETCLAMPEXTPROC)get_extension_func("glPolygonOffsetClamp");
+  }
+  else
+#endif
+  if (has_extension("GL_EXT_polygon_offset_clamp")) {
+    _glPolygonOffsetClamp = (PFNGLPOLYGONOFFSETCLAMPEXTPROC)get_extension_func("glPolygonOffsetClampEXT");
+  }
+  else {
+    _glPolygonOffsetClamp = null_glPolygonOffsetClamp;
+  }
+#endif
+
   // Set up all the enableddisabled flags to GL's known initial values:
   // everything off.
   _multisample_mode = 0;
@@ -8173,19 +8197,34 @@ do_issue_fog() {
  *
  */
 void CLP(GraphicsStateGuardian)::
-do_issue_depth_offset() {
-  const DepthOffsetAttrib *target_depth_offset = (const DepthOffsetAttrib *)
-     _target_rs->get_attrib_def(DepthOffsetAttrib::get_class_slot());
-
+do_issue_depth_bias() {
+  const DepthOffsetAttrib *target_depth_offset;
+  _target_rs->get_attrib_def(target_depth_offset);
   int offset = target_depth_offset->get_offset();
 
-  if (offset != 0) {
+  const DepthBiasAttrib *target_depth_bias;
+  if (_target_rs->get_attrib(target_depth_bias)) {
+    GLfloat slope_factor = target_depth_bias->get_slope_factor();
+    GLfloat constant_factor = target_depth_bias->get_constant_factor();
+
+    slope_factor -= offset;
+    constant_factor -= offset;
+
+#ifndef OPENGLES_1
+    GLfloat clamp = target_depth_bias->get_clamp();
+    _glPolygonOffsetClamp(slope_factor, constant_factor, clamp);
+#else
+    glPolygonOffset(slope_factor, constant_factor);
+#endif
+    enable_polygon_offset(true);
+  }
+  else if (offset != 0) {
     // The relationship between these two parameters is a little unclear and
     // poorly explained in the GL man pages.
     glPolygonOffset((GLfloat) -offset, (GLfloat) -offset);
     enable_polygon_offset(true);
-
-  } else {
+  }
+  else {
     enable_polygon_offset(false);
   }
 
@@ -11785,11 +11824,15 @@ set_state_and_transform(const RenderState *target,
     _state_mask.set_bit(cull_face_slot);
   }
 
+  int depth_bias_slot = DepthBiasAttrib::get_class_slot();
   int depth_offset_slot = DepthOffsetAttrib::get_class_slot();
-  if (_target_rs->get_attrib(depth_offset_slot) != _state_rs->get_attrib(depth_offset_slot) ||
+  if (_target_rs->get_attrib(depth_bias_slot) != _state_rs->get_attrib(depth_bias_slot) ||
+      _target_rs->get_attrib(depth_offset_slot) != _state_rs->get_attrib(depth_offset_slot) ||
+      !_state_mask.get_bit(depth_bias_slot) ||
       !_state_mask.get_bit(depth_offset_slot)) {
     // PStatGPUTimer timer(this, _draw_set_state_depth_offset_pcollector);
-    do_issue_depth_offset();
+    do_issue_depth_bias();
+    _state_mask.set_bit(depth_bias_slot);
     _state_mask.set_bit(depth_offset_slot);
   }
 

+ 4 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -454,7 +454,7 @@ protected:
 #ifdef SUPPORT_FIXED_FUNCTION
   void do_issue_fog();
 #endif
-  void do_issue_depth_offset();
+  void do_issue_depth_bias();
   void do_issue_shade_model();
 #ifndef OPENGLES_1
   void do_issue_shader();
@@ -772,6 +772,9 @@ public:
   bool _use_remapped_depth_range;
   PFNGLDEPTHRANGEDNVPROC _glDepthRangedNV;
 #endif
+#ifndef OPENGLES_1
+  PFNGLPOLYGONOFFSETCLAMPEXTPROC _glPolygonOffsetClamp;
+#endif
 
   bool _supports_point_parameters;
   PFNGLPOINTPARAMETERFVPROC _glPointParameterfv;

+ 2 - 0
panda/src/pgraph/CMakeLists.txt

@@ -29,6 +29,7 @@ set(P3PGRAPH_HEADERS
   cullTraverserData.I cullTraverserData.h
   cullableObject.I cullableObject.h
   decalEffect.I decalEffect.h
+  depthBiasAttrib.I depthBiasAttrib.h
   depthOffsetAttrib.I depthOffsetAttrib.h
   depthTestAttrib.I depthTestAttrib.h
   depthWriteAttrib.I depthWriteAttrib.h
@@ -131,6 +132,7 @@ set(P3PGRAPH_SOURCES
   cullTraverserData.cxx
   cullableObject.cxx
   decalEffect.cxx
+  depthBiasAttrib.cxx
   depthOffsetAttrib.cxx
   depthTestAttrib.cxx
   depthWriteAttrib.cxx

+ 3 - 0
panda/src/pgraph/config_pgraph.cxx

@@ -33,6 +33,7 @@
 #include "cullTraverser.h"
 #include "cullableObject.h"
 #include "decalEffect.h"
+#include "depthBiasAttrib.h"
 #include "depthOffsetAttrib.h"
 #include "depthTestAttrib.h"
 #include "depthWriteAttrib.h"
@@ -416,6 +417,7 @@ init_libpgraph() {
   CullTraverser::init_type();
   CullableObject::init_type();
   DecalEffect::init_type();
+  DepthBiasAttrib::init_type();
   DepthOffsetAttrib::init_type();
   DepthTestAttrib::init_type();
   DepthWriteAttrib::init_type();
@@ -489,6 +491,7 @@ init_libpgraph() {
   CullBinAttrib::register_with_read_factory();
   CullFaceAttrib::register_with_read_factory();
   DecalEffect::register_with_read_factory();
+  DepthBiasAttrib::register_with_read_factory();
   DepthOffsetAttrib::register_with_read_factory();
   DepthTestAttrib::register_with_read_factory();
   DepthWriteAttrib::register_with_read_factory();

+ 49 - 0
panda/src/pgraph/depthBiasAttrib.I

@@ -0,0 +1,49 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file depthBiasAttrib.I
+ * @author rdb
+ * @date 2021-08-24
+ */
+
+/**
+ * Use DepthBiasAttrib::make() to construct a new DepthBiasAttrib object.
+ */
+INLINE DepthBiasAttrib::
+DepthBiasAttrib(PN_stdfloat slope_factor, PN_stdfloat constant_factor,
+                PN_stdfloat clamp) :
+  _slope_factor(slope_factor),
+  _constant_factor(constant_factor),
+  _clamp(clamp)
+{
+}
+
+/**
+ * Returns the slope factor.
+ */
+INLINE PN_stdfloat DepthBiasAttrib::
+get_slope_factor() const {
+  return _slope_factor;
+}
+
+/**
+ * Returns the constant factor.
+ */
+INLINE PN_stdfloat DepthBiasAttrib::
+get_constant_factor() const {
+  return _constant_factor;
+}
+
+/**
+ * Returns the maximum (or minimum, if negative) value of the bias.  If zero,
+ * no clamping is performed.
+ */
+INLINE PN_stdfloat DepthBiasAttrib::
+get_clamp() const {
+  return _clamp;
+}

+ 180 - 0
panda/src/pgraph/depthBiasAttrib.cxx

@@ -0,0 +1,180 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file depthBiasAttrib.cxx
+ * @author rdb
+ * @date 2021-08-24
+ */
+
+#include "depthBiasAttrib.h"
+#include "graphicsStateGuardianBase.h"
+#include "dcast.h"
+#include "bamReader.h"
+#include "bamWriter.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+
+TypeHandle DepthBiasAttrib::_type_handle;
+int DepthBiasAttrib::_attrib_slot;
+
+/**
+ * Constructs a new DepthBiasAttrib object that indicates the slope factor,
+ * constant factor, and an optional clamping value.
+ */
+CPT(RenderAttrib) DepthBiasAttrib::
+make(PN_stdfloat slope_factor, PN_stdfloat constant_factor, PN_stdfloat clamp) {
+  DepthBiasAttrib *attrib = new DepthBiasAttrib(slope_factor, constant_factor, clamp);
+  return return_new(attrib);
+}
+
+/**
+ * Returns a RenderAttrib that corresponds to whatever the standard default
+ * properties for render attributes of this type ought to be.
+ */
+CPT(RenderAttrib) DepthBiasAttrib::
+make_default() {
+  return return_new(new DepthBiasAttrib(0, 0, 0));
+}
+
+/**
+ *
+ */
+void DepthBiasAttrib::
+output(std::ostream &out) const {
+  out << get_type() << ":(" << get_slope_factor() << ", " << get_constant_factor()
+      << ", " << get_clamp() << ")";
+}
+
+/**
+ * Intended to be overridden by derived DepthBiasAttrib types to return a
+ * unique number indicating whether this DepthBiasAttrib is equivalent to
+ * the other one.
+ *
+ * This should return 0 if the two DepthBiasAttrib objects are equivalent, a
+ * number less than zero if this one should be sorted before the other one,
+ * and a number greater than zero otherwise.
+ *
+ * This will only be called with two DepthBiasAttrib objects whose
+ * get_type() functions return the same.
+ */
+int DepthBiasAttrib::
+compare_to_impl(const RenderAttrib *other) const {
+  const DepthBiasAttrib *ta = (const DepthBiasAttrib *)other;
+
+  if (_slope_factor != ta->_slope_factor) {
+    return _slope_factor < ta->_slope_factor ? -1 : 1;
+  }
+  if (_constant_factor != ta->_constant_factor) {
+    return _constant_factor < ta->_constant_factor ? -1 : 1;
+  }
+  if (_clamp != ta->_clamp) {
+    return _clamp < ta->_clamp ? -1 : 1;
+  }
+  return 0;
+}
+
+/**
+ * Intended to be overridden by derived RenderAttrib types to return a unique
+ * hash for these particular properties.  RenderAttribs that compare the same
+ * with compare_to_impl(), above, should return the same hash; RenderAttribs
+ * that compare differently should return a different hash.
+ */
+size_t DepthBiasAttrib::
+get_hash_impl() const {
+  size_t hash = 0;
+  hash = float_hash().add_hash(hash, _slope_factor);
+  hash = float_hash().add_hash(hash, _constant_factor);
+  hash = float_hash().add_hash(hash, _clamp);
+  return hash;
+}
+
+/**
+ * Intended to be overridden by derived RenderAttrib types to specify how two
+ * consecutive RenderAttrib objects of the same type interact.
+ *
+ * This should return the result of applying the other RenderAttrib to a node
+ * in the scene graph below this RenderAttrib, which was already applied.  In
+ * most cases, the result is the same as the other RenderAttrib (that is, a
+ * subsequent RenderAttrib completely replaces the preceding one).  On the
+ * other hand, some kinds of RenderAttrib (for instance, ColorTransformAttrib)
+ * might combine in meaningful ways.
+ */
+CPT(RenderAttrib) DepthBiasAttrib::
+compose_impl(const RenderAttrib *other) const {
+  const DepthBiasAttrib *ba = (const DepthBiasAttrib *)other;
+
+  return return_new(new DepthBiasAttrib(ba->_slope_factor + _slope_factor,
+                                        ba->_constant_factor + _constant_factor,
+                                        ba->_clamp));
+}
+
+/**
+ * Intended to be overridden by derived RenderAttrib types to specify how two
+ * consecutive RenderAttrib objects of the same type interact.
+ *
+ * See invert_compose() and compose_impl().
+ */
+CPT(RenderAttrib) DepthBiasAttrib::
+invert_compose_impl(const RenderAttrib *other) const {
+  const DepthBiasAttrib *ba = (const DepthBiasAttrib *)other;
+
+  return return_new(new DepthBiasAttrib(ba->_slope_factor - _slope_factor,
+                                        ba->_constant_factor - _constant_factor,
+                                        ba->_clamp));
+}
+
+/**
+ * Tells the BamReader how to create objects of type DepthBiasAttrib.
+ */
+void DepthBiasAttrib::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void DepthBiasAttrib::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  RenderAttrib::write_datagram(manager, dg);
+
+  dg.add_stdfloat(_slope_factor);
+  dg.add_stdfloat(_constant_factor);
+  dg.add_stdfloat(_clamp);
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type DepthBiasAttrib is encountered in the Bam file.  It should create
+ * the DepthBiasAttrib and extract its information from the file.
+ */
+TypedWritable *DepthBiasAttrib::
+make_from_bam(const FactoryParams &params) {
+  DepthBiasAttrib *attrib = new DepthBiasAttrib(0, 0, 0);
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  attrib->fillin(scan, manager);
+
+  return attrib;
+}
+
+/**
+ * This internal function is called by make_from_bam to read in all of the
+ * relevant data from the BamFile for the new DepthBiasAttrib.
+ */
+void DepthBiasAttrib::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  RenderAttrib::fillin(scan, manager);
+
+  _slope_factor = scan.get_stdfloat();
+  _constant_factor = scan.get_stdfloat();
+  _clamp = scan.get_stdfloat();
+}

+ 115 - 0
panda/src/pgraph/depthBiasAttrib.h

@@ -0,0 +1,115 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file depthBiasAttrib.h
+ * @author rdb
+ * @date 2021-08-24
+ */
+
+#ifndef DEPTHBIASATTRIB_H
+#define DEPTHBIASATTRIB_H
+
+#include "pandabase.h"
+
+#include "renderAttrib.h"
+#include "luse.h"
+
+class FactoryParams;
+
+/**
+ * This is a special kind of attribute that instructs the graphics driver to
+ * apply an offset or bias to the generated depth values for rendered
+ * polygons, before they are written to the depth buffer.
+ *
+ * This class replaces the old DepthOffsetAttrib, which had a more limited
+ * parameterization.  The differences are:
+ * - The sign of the factor parameter was inverted.
+ * - The slope and constant factors are specified separately.
+ * - The factors are specified as floating-point instead of integer.
+ * - There is a new clamp parameter.
+ *
+ * Nested DepthBiasAttrib values accumulate; that is, a DepthBiasAttrib
+ * with a value of 1 beneath another DepthBiasAttrib with a value of 2
+ * presents a net offset of 3.  (A DepthBiasAttrib will not, however,
+ * combine with any other DepthBiasAttribs with a lower override parameter.)
+ */
+class EXPCL_PANDA_PGRAPH DepthBiasAttrib : public RenderAttrib {
+private:
+  INLINE DepthBiasAttrib(PN_stdfloat slope_factor, PN_stdfloat constant_factor,
+                         PN_stdfloat clamp = 0);
+
+PUBLISHED:
+  static CPT(RenderAttrib) make(PN_stdfloat slope_factor, PN_stdfloat constant_factor,
+                                PN_stdfloat clamp = 0);
+  static CPT(RenderAttrib) make_default();
+
+public:
+  INLINE PN_stdfloat get_slope_factor() const;
+  INLINE PN_stdfloat get_constant_factor() const;
+  INLINE PN_stdfloat get_clamp() const;
+
+PUBLISHED:
+  MAKE_PROPERTY(slope_factor, get_slope_factor);
+  MAKE_PROPERTY(constant_factor, get_constant_factor);
+  MAKE_PROPERTY(clamp, get_clamp);
+
+public:
+  virtual void output(std::ostream &out) const;
+
+protected:
+  virtual int compare_to_impl(const RenderAttrib *other) const;
+  virtual size_t get_hash_impl() const;
+  virtual CPT(RenderAttrib) compose_impl(const RenderAttrib *other) const;
+  virtual CPT(RenderAttrib) invert_compose_impl(const RenderAttrib *other) const;
+
+private:
+  PN_stdfloat _slope_factor;
+  PN_stdfloat _constant_factor;
+  PN_stdfloat _clamp;
+
+PUBLISHED:
+  static int get_class_slot() {
+    return _attrib_slot;
+  }
+  virtual int get_slot() const {
+    return get_class_slot();
+  }
+  MAKE_PROPERTY(class_slot, get_class_slot);
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    RenderAttrib::init_type();
+    register_type(_type_handle, "DepthBiasAttrib",
+                  RenderAttrib::get_class_type());
+    _attrib_slot = register_slot(_type_handle, 100,
+                                 new DepthBiasAttrib(0, 0, 0));
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+  static int _attrib_slot;
+};
+
+#include "depthBiasAttrib.I"
+
+#endif

+ 2 - 0
panda/src/pgraph/depthOffsetAttrib.h

@@ -46,6 +46,8 @@ class FactoryParams;
  * Also, and only tangentially related, the DepthOffsetAttrib can be used to
  * constrain the Z output value to a subset of the usual [0, 1] range (or
  * reversing its direction) by specifying a new min_value and max_value.
+ *
+ * @deprecated See DepthBiasAttrib and DisplayRegion::set_depth_range() instead.
  */
 class EXPCL_PANDA_PGRAPH DepthOffsetAttrib : public RenderAttrib {
 private:

+ 38 - 1
panda/src/pgraph/nodePath.cxx

@@ -34,9 +34,10 @@
 #include "renderModeAttrib.h"
 #include "cullFaceAttrib.h"
 #include "alphaTestAttrib.h"
+#include "depthBiasAttrib.h"
+#include "depthOffsetAttrib.h"
 #include "depthTestAttrib.h"
 #include "depthWriteAttrib.h"
-#include "depthOffsetAttrib.h"
 #include "shaderAttrib.h"
 #include "billboardEffect.h"
 #include "compassEffect.h"
@@ -4684,6 +4685,8 @@ get_depth_write() const {
  * bias is always an integer number, and each integer increment represents the
  * smallest possible increment in Z that is sufficient to completely resolve
  * two coplanar polygons.  Positive numbers are closer towards the camera.
+ *
+ * @deprecated See set_depth_bias() instead, which provides more controls.
  */
 void NodePath::
 set_depth_offset(int bias, int priority) {
@@ -4730,6 +4733,40 @@ get_depth_offset() const {
   return 0;
 }
 
+/**
+ * This instructs the graphics driver to apply an offset or bias to the
+ * generated depth values for rendered polygons, before they are written to
+ * the depth buffer.  This can be used to shift polygons forward slightly, to
+ * resolve depth conflicts, or self-shadowing artifacts on thin objects.
+ * Positive numbers are further away from the camera.
+ */
+void NodePath::
+set_depth_bias(PN_stdfloat slope_factor, PN_stdfloat constant_factor, PN_stdfloat clamp, int priority) {
+  nassertv_always(!is_empty());
+  node()->set_attrib(DepthBiasAttrib::make(slope_factor, constant_factor, clamp), priority);
+}
+
+/**
+ * Completely removes any depth-bias adjustment that may have been set on
+ * this node via set_depth_bias().
+ */
+void NodePath::
+clear_depth_bias() {
+  nassertv_always(!is_empty());
+  node()->clear_attrib(DepthBiasAttrib::get_class_slot());
+}
+
+/**
+ * Returns true if a depth-bias adjustment has been explicitly set on this
+ * particular node via set_depth_bias().  If this returns true, then
+ * get_depth_bias() may be called to determine which has been set.
+ */
+bool NodePath::
+has_depth_bias() const {
+  nassertr_always(!is_empty(), false);
+  return node()->has_attrib(DepthBiasAttrib::get_class_slot());
+}
+
 /**
  * Performs a billboard-type rotate to the indicated camera node, one time
  * only, and leaves the object rotated.  This is similar in principle to

+ 5 - 0
panda/src/pgraph/nodePath.h

@@ -811,6 +811,11 @@ PUBLISHED:
   bool has_depth_offset() const;
   int get_depth_offset() const;
 
+  void set_depth_bias(PN_stdfloat slope_factor, PN_stdfloat constant_factor,
+                      PN_stdfloat clamp = 0.0, int priority = 0);
+  void clear_depth_bias();
+  bool has_depth_bias() const;
+
   void do_billboard_axis(const NodePath &camera, PN_stdfloat offset);
   void do_billboard_point_eye(const NodePath &camera, PN_stdfloat offset);
   void do_billboard_point_world(const NodePath &camera, PN_stdfloat offset);

+ 1 - 0
panda/src/pgraph/p3pgraph_composite2.cxx

@@ -9,6 +9,7 @@
 #include "cullTraverserData.cxx"
 #include "cullableObject.cxx"
 #include "decalEffect.cxx"
+#include "depthBiasAttrib.cxx"
 #include "depthOffsetAttrib.cxx"
 #include "depthTestAttrib.cxx"
 #include "depthWriteAttrib.cxx"

+ 40 - 0
tests/display/test_depth_buffer.py

@@ -179,3 +179,43 @@ def test_depth_range(depth_region):
         assert z == pytest.approx(0.25, rel=0.01)
     finally:
         depth_region.set_depth_range(0, 1)
+
+
+def test_depth_bias(depth_region):
+    # Without depth bias
+    z_ref = render_depth_pixel(depth_region, 5, near=1, far=10)
+
+    # With constant positive depth bias
+    state = core.RenderState.make(core.DepthBiasAttrib.make(0, 1))
+    z = render_depth_pixel(depth_region, 5, near=1, far=10, state=state)
+    assert z > z_ref
+
+    # With constant negative depth bias
+    state = core.RenderState.make(core.DepthBiasAttrib.make(0, -1))
+    z = render_depth_pixel(depth_region, 5, near=1, far=10, state=state)
+    assert z < z_ref
+
+    # With slope-scaled depth bias (our quad has no slope)
+    state = core.RenderState.make(core.DepthBiasAttrib.make(10, 0))
+    z = render_depth_pixel(depth_region, 5, near=1, far=10, state=state)
+    assert z == z_ref
+
+    # Same, but negative
+    state = core.RenderState.make(core.DepthBiasAttrib.make(-10, 0))
+    z = render_depth_pixel(depth_region, 5, near=1, far=10, state=state)
+    assert z == z_ref
+
+
+def test_depth_offset(depth_region):
+    # Without depth offset
+    z_ref = render_depth_pixel(depth_region, 5, near=1, far=10)
+
+    # With constant positive depth offset
+    state = core.RenderState.make(core.DepthOffsetAttrib.make(1))
+    z = render_depth_pixel(depth_region, 5, near=1, far=10, state=state)
+    assert z < z_ref
+
+    # With constant negative depth offset
+    state = core.RenderState.make(core.DepthOffsetAttrib.make(-1))
+    z = render_depth_pixel(depth_region, 5, near=1, far=10, state=state)
+    assert z > z_ref