OpenSceneGraph 0.1.2__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- OpenSceneGraph/OpenThreads.lib +0 -0
- OpenSceneGraph/_OpenSceneGraph.cp313-win_amd64.pyd +0 -0
- OpenSceneGraph/__init__.py +52 -0
- OpenSceneGraph/aipython/00-index.md +44 -0
- OpenSceneGraph/aipython/01-core.md +255 -0
- OpenSceneGraph/aipython/02-inspect.md +81 -0
- OpenSceneGraph/aipython/03-headless-frames.md +149 -0
- OpenSceneGraph/aipython/05-camera-manipulator.md +68 -0
- OpenSceneGraph/aipython/06-camera-effects.md +132 -0
- OpenSceneGraph/aipython/07-camera-manual.md +88 -0
- OpenSceneGraph/aipython/08-lighting.md +121 -0
- OpenSceneGraph/aipython/09-picking.md +90 -0
- OpenSceneGraph/aipython/10-rtt.md +199 -0
- OpenSceneGraph/aipython/11-mrt.md +104 -0
- OpenSceneGraph/aipython/12-gbuffer.md +48 -0
- OpenSceneGraph/aipython/15-shader-hotswap.md +72 -0
- OpenSceneGraph/aipython/17-particles.md +189 -0
- OpenSceneGraph/aipython/18-deterministic-captures.md +157 -0
- OpenSceneGraph/aipython/20-object-lifetime.md +77 -0
- OpenSceneGraph/aipython/25-async-osgpy.md +228 -0
- OpenSceneGraph/aipython/29-material.md +109 -0
- OpenSceneGraph/aipython/30-pbribl.md +162 -0
- OpenSceneGraph/aipython/40-typed-lights-gizmos.md +218 -0
- OpenSceneGraph/examples/__init__.py +24 -0
- OpenSceneGraph/examples/__main__.py +143 -0
- OpenSceneGraph/examples/blur.py +370 -0
- OpenSceneGraph/examples/info.py +126 -0
- OpenSceneGraph/examples/mrt.py +493 -0
- OpenSceneGraph/examples/pyosg_async.py +467 -0
- OpenSceneGraph/examples/pyosg_example.py +120 -0
- OpenSceneGraph/examples/pyosg_repl.py +842 -0
- OpenSceneGraph/examples/pyosg_visitor.py +138 -0
- OpenSceneGraph/ktx.dll +0 -0
- OpenSceneGraph/ktx.lib +0 -0
- OpenSceneGraph/osg.lib +0 -0
- OpenSceneGraph/osg161-osg.dll +0 -0
- OpenSceneGraph/osg161-osgAnimation.dll +0 -0
- OpenSceneGraph/osg161-osgDB.dll +0 -0
- OpenSceneGraph/osg161-osgFX.dll +0 -0
- OpenSceneGraph/osg161-osgGA.dll +0 -0
- OpenSceneGraph/osg161-osgText.dll +0 -0
- OpenSceneGraph/osg161-osgUtil.dll +0 -0
- OpenSceneGraph/osg161-osgViewer.dll +0 -0
- OpenSceneGraph/osg161-osgWidget.dll +0 -0
- OpenSceneGraph/osgAnimation.lib +0 -0
- OpenSceneGraph/osgDB.lib +0 -0
- OpenSceneGraph/osgFX.lib +0 -0
- OpenSceneGraph/osgGA.lib +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/jpeg62-ebb2f26be87097e77bafd1e9095b4820.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/ktx.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/liblzma-b9cff3753c4848b9ff1350ba5a053c6e.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/libpng16-36b7e1e8d185cb21280e5688f99660c0.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/msvcp140.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_bmp.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_dds.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_gltf.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_hdr.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_jpeg.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_ktx2.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_obj.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_osg.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_png.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_pnm.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_rgb.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_serializers_osg.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_stl.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_tga.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/osgdb_tiff.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/tiff-1defa8059e5ba115a3ab4b120de6f4bb.dll +0 -0
- OpenSceneGraph/osgPlugins-3.6.5/z.dll +0 -0
- OpenSceneGraph/osgText.lib +0 -0
- OpenSceneGraph/osgUtil.lib +0 -0
- OpenSceneGraph/osgViewer.lib +0 -0
- OpenSceneGraph/osgWidget.lib +0 -0
- OpenSceneGraph/osgx.cp313-win_amd64.pyd +0 -0
- OpenSceneGraph/osgx_static.lib +0 -0
- OpenSceneGraph/ot21-OpenThreads.dll +0 -0
- openscenegraph-0.1.2.dist-info/DELVEWHEEL +2 -0
- openscenegraph-0.1.2.dist-info/METADATA +563 -0
- openscenegraph-0.1.2.dist-info/RECORD +90 -0
- openscenegraph-0.1.2.dist-info/WHEEL +5 -0
- openscenegraph-0.1.2.dist-info/entry_points.txt +6 -0
- openscenegraph-0.1.2.dist-info/licenses/LICENSE +21 -0
- openscenegraph.libs/jpeg62-ebb2f26be87097e77bafd1e9095b4820.dll +0 -0
- openscenegraph.libs/liblzma-b9cff3753c4848b9ff1350ba5a053c6e.dll +0 -0
- openscenegraph.libs/libpng16-36b7e1e8d185cb21280e5688f99660c0.dll +0 -0
- openscenegraph.libs/msvcp140.dll +0 -0
- openscenegraph.libs/tiff-1defa8059e5ba115a3ab4b120de6f4bb.dll +0 -0
- openscenegraph.libs/z.dll +0 -0
- osgx.py +7 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# MRT (Multiple Render Targets) proof-of-concept.
|
|
4
|
+
#
|
|
5
|
+
# pyosg-rtt.py proves COLOR_BUFFER + DEPTH_BUFFER -- two different attachment
|
|
6
|
+
# *types*, one color slot. pyosg-blur.py proves chained multi-pass (several
|
|
7
|
+
# separate single-output passes feeding each other). Neither proves the thing
|
|
8
|
+
# a deferred G-buffer actually needs: ONE geometry pass writing to MULTIPLE
|
|
9
|
+
# color attachments SIMULTANEOUSLY (COLOR_BUFFER0 + COLOR_BUFFER1) from a
|
|
10
|
+
# single shader invocation per pixel, via GLSL `layout(location = n) out`.
|
|
11
|
+
#
|
|
12
|
+
# This example proves that, then goes one step further and actually USES the
|
|
13
|
+
# extra normal buffer: the toon lighting from pyosg-rtt.py's scene shader is
|
|
14
|
+
# moved OUT of the geometry pass entirely (deferred) into the composite pass,
|
|
15
|
+
# computed from the color+normal+depth G-buffer instead -- the same shape
|
|
16
|
+
# Step 11 of the lighting-class series (see ai/context-todo-lighting-class.md)
|
|
17
|
+
# will need for its SSAO/SSR post-processing stack. The composite outline is
|
|
18
|
+
# also upgraded to combine depth-edge AND normal-edge detection (silhouette
|
|
19
|
+
# vs. crease/grazing-angle edges), which is measurably more robust than
|
|
20
|
+
# pyosg-rtt.py's depth-only Sobel-ish outline.
|
|
21
|
+
#
|
|
22
|
+
# Press 1/2/3 to visualize the raw color/depth/normal G-buffer channels;
|
|
23
|
+
# press 0 to return to the deferred-lit composite (the default).
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
|
|
28
|
+
os.environ.update({
|
|
29
|
+
"OSG_WINDOW": "50 50 800 600",
|
|
30
|
+
"OSG_THREADING": "SingleThreaded",
|
|
31
|
+
"OSG_GL_CONTEXT_PROFILE_MASK": "1",
|
|
32
|
+
"OSG_GL_VERSION": "4.6",
|
|
33
|
+
"OSG_GL_CONTEXT_VERSION": "4.6"
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
from pyosg_example import label
|
|
37
|
+
|
|
38
|
+
from OpenSceneGraph import *
|
|
39
|
+
from OpenSceneGraph.GL import *
|
|
40
|
+
|
|
41
|
+
from pyosg_visitor import GatherVisitor
|
|
42
|
+
|
|
43
|
+
W, H = 800, 600
|
|
44
|
+
|
|
45
|
+
# --------------------------------------------------------------------------- #
|
|
46
|
+
# Shaders
|
|
47
|
+
# --------------------------------------------------------------------------- #
|
|
48
|
+
|
|
49
|
+
SCENE_VERTEX_SHADER = """
|
|
50
|
+
#version 330 core
|
|
51
|
+
|
|
52
|
+
in vec4 osg_Vertex;
|
|
53
|
+
in vec4 osg_Color;
|
|
54
|
+
in vec3 osg_Normal;
|
|
55
|
+
|
|
56
|
+
uniform mat4 osg_ModelViewProjectionMatrix;
|
|
57
|
+
uniform mat3 osg_NormalMatrix;
|
|
58
|
+
|
|
59
|
+
out vec4 vColor;
|
|
60
|
+
out vec3 vNormal;
|
|
61
|
+
|
|
62
|
+
void main() {
|
|
63
|
+
vNormal = normalize(osg_NormalMatrix * osg_Normal);
|
|
64
|
+
vColor = osg_Color;
|
|
65
|
+
|
|
66
|
+
gl_Position = osg_ModelViewProjectionMatrix * osg_Vertex;
|
|
67
|
+
}
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# G-buffer write pass: NO lighting happens here at all -- just raw albedo and
|
|
71
|
+
# view-space normal, written to two color attachments SIMULTANEOUSLY (true
|
|
72
|
+
# MRT, via the two `layout(location = n) out` declarations below). Depth
|
|
73
|
+
# comes "for free" from this camera's own DEPTH_BUFFER attachment/z-test.
|
|
74
|
+
GBUFFER_FRAGMENT_SHADER = """
|
|
75
|
+
#version 330 core
|
|
76
|
+
|
|
77
|
+
in vec4 vColor;
|
|
78
|
+
in vec3 vNormal;
|
|
79
|
+
|
|
80
|
+
layout(location = 0) out vec4 outColor; // COLOR_BUFFER0: unlit albedo
|
|
81
|
+
layout(location = 1) out vec4 outNormal; // COLOR_BUFFER1: view-space normal
|
|
82
|
+
|
|
83
|
+
void main() {
|
|
84
|
+
outColor = vColor;
|
|
85
|
+
outNormal = vec4(normalize(vNormal), 1.0);
|
|
86
|
+
}
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
FULLSCREEN_VERTEX_SHADER = """
|
|
90
|
+
#version 330 core
|
|
91
|
+
|
|
92
|
+
in vec4 osg_Vertex;
|
|
93
|
+
in vec2 osg_MultiTexCoord0;
|
|
94
|
+
|
|
95
|
+
out vec2 uv;
|
|
96
|
+
|
|
97
|
+
void main() {
|
|
98
|
+
uv = osg_MultiTexCoord0;
|
|
99
|
+
gl_Position = osg_Vertex;
|
|
100
|
+
}
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
# Deferred composite: reads the G-buffer (color/normal/depth) back and does
|
|
104
|
+
# ALL of the actual lighting HERE instead of in the geometry pass -- the same
|
|
105
|
+
# cel-shaded diffuse + rim light as pyosg-rtt.py, just computed from the
|
|
106
|
+
# G-buffer instead of inline per-vertex/per-fragment during the geometry
|
|
107
|
+
# pass. View-space position is reconstructed from depth + the inverse
|
|
108
|
+
# projection matrix (the same reconstruction technique a real SSAO/SSR pass
|
|
109
|
+
# needs the depth buffer for).
|
|
110
|
+
COMPOSITE_FRAGMENT_SHADER = """
|
|
111
|
+
#version 330 core
|
|
112
|
+
|
|
113
|
+
uniform sampler2D colorTex;
|
|
114
|
+
uniform sampler2D normalTex;
|
|
115
|
+
uniform sampler2D depthTex;
|
|
116
|
+
uniform mat4 invProjectionMatrix;
|
|
117
|
+
uniform float znear;
|
|
118
|
+
uniform float zfar;
|
|
119
|
+
uniform int visualizeMode; // 0=lit composite, 1=color, 2=depth, 3=normal
|
|
120
|
+
|
|
121
|
+
in vec2 uv;
|
|
122
|
+
|
|
123
|
+
out vec4 fragColor;
|
|
124
|
+
|
|
125
|
+
// Convert depth buffer value -> camera-space Z distance.
|
|
126
|
+
float linearizeDepth(float d, float near, float far) {
|
|
127
|
+
float z = d * 2.0 - 1.0;
|
|
128
|
+
|
|
129
|
+
return (2.0 * near * far) / (far + near - z * (far - near));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Reconstruct view-space position from a screen UV + depth-buffer sample.
|
|
133
|
+
vec3 reconstructViewPos(vec2 uv, float d) {
|
|
134
|
+
vec4 clip = vec4(vec3(uv, d) * 2.0 - 1.0, 1.0);
|
|
135
|
+
vec4 viewPos = invProjectionMatrix * clip;
|
|
136
|
+
|
|
137
|
+
return viewPos.xyz / viewPos.w;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
void main() {
|
|
141
|
+
vec4 albedo = texture(colorTex, uv);
|
|
142
|
+
vec3 rawNormal = texture(normalTex, uv).rgb;
|
|
143
|
+
float d = texture(depthTex, uv).r;
|
|
144
|
+
|
|
145
|
+
// --- Raw G-buffer visualize modes (bypass lighting entirely) -------- //
|
|
146
|
+
if (visualizeMode == 1) {
|
|
147
|
+
fragColor = albedo;
|
|
148
|
+
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (visualizeMode == 2) {
|
|
153
|
+
float lin = linearizeDepth(d, znear, zfar);
|
|
154
|
+
float t = clamp((lin - znear) / (zfar - znear), 0.0, 1.0);
|
|
155
|
+
|
|
156
|
+
fragColor = vec4(vec3(t), 1.0);
|
|
157
|
+
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (visualizeMode == 3) {
|
|
162
|
+
// Raw (unnormalized) sample -- background pixels the G-buffer pass
|
|
163
|
+
// never touched read back as (0,0,0) -> mid-gray here, which is a
|
|
164
|
+
// harmless "no data" tell rather than a NaN from normalizing a zero
|
|
165
|
+
// vector.
|
|
166
|
+
fragColor = vec4(rawNormal * 0.5 + 0.5, 1.0);
|
|
167
|
+
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// --- Deferred lit composite (default) -------------------------------- //
|
|
172
|
+
// A cleared-but-never-written background pixel has a zero-length normal
|
|
173
|
+
// (real written normals are always unit length) -- use that as a sentinel
|
|
174
|
+
// for "no geometry here" instead of trusting the color clear value, which
|
|
175
|
+
// would otherwise get relit as if it were a real (very dark) surface.
|
|
176
|
+
if (dot(rawNormal, rawNormal) < 0.0001) {
|
|
177
|
+
fragColor = vec4(0.1, 0.5, 0.2, 1.0); // same green as pyosg-rtt.py's clear color
|
|
178
|
+
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
vec3 N = normalize(rawNormal);
|
|
183
|
+
|
|
184
|
+
const vec3 L = vec3(0.268, 0.358, 0.894);
|
|
185
|
+
const vec3 rimColor = vec3(0.9, 0.6, 0.0);
|
|
186
|
+
const float rimPower = 5.0;
|
|
187
|
+
const float rimBase = 0.3;
|
|
188
|
+
const float rimTint = 0.4;
|
|
189
|
+
|
|
190
|
+
float diffuse = max(dot(N, L), 0.0);
|
|
191
|
+
diffuse = floor(diffuse * 3.0) / 3.0;
|
|
192
|
+
|
|
193
|
+
float ambient = 0.25;
|
|
194
|
+
float light = ambient + diffuse;
|
|
195
|
+
|
|
196
|
+
vec3 viewPos = reconstructViewPos(uv, d);
|
|
197
|
+
vec3 viewDir = normalize(-viewPos);
|
|
198
|
+
float rim = pow(1.0 - clamp(dot(N, viewDir), 0.0, 1.0), rimPower);
|
|
199
|
+
vec3 rimLight = rim * (vec3(rimBase) + rimColor * rimTint);
|
|
200
|
+
|
|
201
|
+
vec3 color = albedo.rgb * light + rimLight;
|
|
202
|
+
|
|
203
|
+
// --- Combined depth-edge + normal-edge outline ----------------------- //
|
|
204
|
+
// Depth-edge alone (pyosg-rtt.py's original technique) catches silhouette
|
|
205
|
+
// edges where geometry meets background/other geometry, but misses edges
|
|
206
|
+
// where depth barely changes yet the surface is turning sharply (grazing
|
|
207
|
+
// angles, or two coplanar-ish faces meeting at a crease) -- normal-edge
|
|
208
|
+
// catches exactly those. Neighbor normal taps are deliberately left
|
|
209
|
+
// UNnormalized: a background neighbor's zero vector then contributes a
|
|
210
|
+
// clean "maximum edge" via dot(N, vec3(0)) == 0, with no normalize(0) NaN
|
|
211
|
+
// risk, which conveniently reinforces the silhouette outline for free.
|
|
212
|
+
vec2 texel = 1.0 / vec2(textureSize(depthTex, 0));
|
|
213
|
+
|
|
214
|
+
float dL = texture(depthTex, uv + vec2(-texel.x, 0.0)).r;
|
|
215
|
+
float dR = texture(depthTex, uv + vec2( texel.x, 0.0)).r;
|
|
216
|
+
float dU = texture(depthTex, uv + vec2(0.0, texel.y)).r;
|
|
217
|
+
float dD = texture(depthTex, uv + vec2(0.0, -texel.y)).r;
|
|
218
|
+
|
|
219
|
+
float z = linearizeDepth(d, znear, zfar);
|
|
220
|
+
float zL = linearizeDepth(dL, znear, zfar);
|
|
221
|
+
float zR = linearizeDepth(dR, znear, zfar);
|
|
222
|
+
float zU = linearizeDepth(dU, znear, zfar);
|
|
223
|
+
float zD = linearizeDepth(dD, znear, zfar);
|
|
224
|
+
|
|
225
|
+
float edgeH = abs(zL - zR);
|
|
226
|
+
float edgeV = abs(zU - zD);
|
|
227
|
+
float depthEdge = sqrt(edgeH * edgeH + edgeV * edgeV);
|
|
228
|
+
float depthThreshold = z * 0.03;
|
|
229
|
+
float depthOutline = smoothstep(depthThreshold * 0.5, depthThreshold * 1.5, depthEdge);
|
|
230
|
+
|
|
231
|
+
vec3 nL = texture(normalTex, uv + vec2(-texel.x, 0.0)).rgb;
|
|
232
|
+
vec3 nR = texture(normalTex, uv + vec2( texel.x, 0.0)).rgb;
|
|
233
|
+
vec3 nU = texture(normalTex, uv + vec2(0.0, texel.y)).rgb;
|
|
234
|
+
vec3 nD = texture(normalTex, uv + vec2(0.0, -texel.y)).rgb;
|
|
235
|
+
|
|
236
|
+
float normalEdge = (1.0 - dot(N, nL)) + (1.0 - dot(N, nR)) + (1.0 - dot(N, nU)) + (1.0 - dot(N, nD));
|
|
237
|
+
float normalOutline = smoothstep(0.05, 0.3, normalEdge);
|
|
238
|
+
|
|
239
|
+
float outline = max(depthOutline, normalOutline);
|
|
240
|
+
|
|
241
|
+
color = mix(color, vec3(0.0), outline);
|
|
242
|
+
|
|
243
|
+
fragColor = vec4(color, 1.0);
|
|
244
|
+
}
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
# --------------------------------------------------------------------------- #
|
|
248
|
+
# Scene / camera setup
|
|
249
|
+
# --------------------------------------------------------------------------- #
|
|
250
|
+
|
|
251
|
+
# Same four spheres as pyosg-rtt.py -- this example is about proving/using
|
|
252
|
+
# MRT, not about the scene content, so keep that constant for a fair
|
|
253
|
+
# comparison against the existing example.
|
|
254
|
+
def create_scene():
|
|
255
|
+
g = None
|
|
256
|
+
|
|
257
|
+
if len(sys.argv) >= 2:
|
|
258
|
+
g = osgDB.readNodeFile(sys.argv[1])
|
|
259
|
+
|
|
260
|
+
else:
|
|
261
|
+
g = osg.Geode(drawables=(
|
|
262
|
+
osg.ShapeDrawable(osg.Sphere(osg.Vec3(0, 2.0, 0), 1.0)),
|
|
263
|
+
osg.ShapeDrawable(osg.Sphere(osg.Vec3(0, 5.0, 0), 1.5)),
|
|
264
|
+
osg.ShapeDrawable(osg.Sphere(osg.Vec3(0, 8.0, 0), 2.0)),
|
|
265
|
+
osg.ShapeDrawable(osg.Sphere(osg.Vec3(0, 12.0, 0), 3.0))
|
|
266
|
+
))
|
|
267
|
+
|
|
268
|
+
p = osg.Program(name="gbufferProgram", shaders=(
|
|
269
|
+
osg.Shader(osg.Shader.VERTEX, SCENE_VERTEX_SHADER),
|
|
270
|
+
osg.Shader(osg.Shader.FRAGMENT, GBUFFER_FRAGMENT_SHADER)
|
|
271
|
+
))
|
|
272
|
+
|
|
273
|
+
# g.stateSet.setAttributeAndModes(p)
|
|
274
|
+
g.stateSet.attributes.append(p)
|
|
275
|
+
|
|
276
|
+
return g
|
|
277
|
+
|
|
278
|
+
# Creates the G-buffer RTT camera: THREE simultaneous attachments (color,
|
|
279
|
+
# normal, depth) from one geometry pass. Returns the camera plus all three
|
|
280
|
+
# `Texture` instances, which the composite HUD camera samples from directly.
|
|
281
|
+
def create_gbuffer_camera(w=W, h=H):
|
|
282
|
+
color_tex = osg.Texture2D(
|
|
283
|
+
size=(w, h),
|
|
284
|
+
internalFormat=GL_RGBA,
|
|
285
|
+
filter=(osg.Texture.LINEAR, osg.Texture.LINEAR),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Float format so signed [-1, 1] normal components need no encode/decode
|
|
289
|
+
# remap -- same convention as the lighting-class series (env_tex/cube_tex/
|
|
290
|
+
# prefilter_tex all use GL_RGB16F for the same reason).
|
|
291
|
+
normal_tex = osg.Texture2D(
|
|
292
|
+
size=(w, h),
|
|
293
|
+
internalFormat=GL_RGB16F,
|
|
294
|
+
filter=(osg.Texture.NEAREST, osg.Texture.NEAREST),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
depth_tex = osg.Texture2D(
|
|
298
|
+
size=(w, h),
|
|
299
|
+
internalFormat=GL_DEPTH_COMPONENT24,
|
|
300
|
+
sourceFormat=GL_DEPTH_COMPONENT,
|
|
301
|
+
sourceType=GL_FLOAT,
|
|
302
|
+
filter=(osg.Texture.NEAREST, osg.Texture.NEAREST),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
cam = osg.Camera(
|
|
306
|
+
renderOrder=osg.Camera.PRE_RENDER,
|
|
307
|
+
renderTargetImplementation=osg.Camera.FRAME_BUFFER_OBJECT,
|
|
308
|
+
clearMask=GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT,
|
|
309
|
+
clearColor=osg.Vec4(0.0, 0.0, 0.0, 0.0),
|
|
310
|
+
viewport=osg.Viewport(0, 0, w, h),
|
|
311
|
+
name="G-Buffer Camera",
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# True MRT: two SIMULTANEOUS color attachments from a single geometry
|
|
315
|
+
# pass, plus depth -- the thing neither pyosg-rtt.py (COLOR+DEPTH, one
|
|
316
|
+
# color slot) nor pyosg-blur.py (chained single-output passes) proves.
|
|
317
|
+
cam.attach(osg.Camera.COLOR_BUFFER0, color_tex)
|
|
318
|
+
cam.attach(osg.Camera.COLOR_BUFFER1, normal_tex)
|
|
319
|
+
cam.attach(osg.Camera.DEPTH_BUFFER, depth_tex)
|
|
320
|
+
|
|
321
|
+
return cam, color_tex, normal_tex, depth_tex
|
|
322
|
+
|
|
323
|
+
# Creates the composite/HUD camera: samples all three G-buffer textures and
|
|
324
|
+
# either runs the deferred toon-lighting pass or dumps one raw buffer to the
|
|
325
|
+
# screen, depending on `visualizeMode` (see VisualizeModeHandler below).
|
|
326
|
+
def create_hud_camera(color_tex, normal_tex, depth_tex):
|
|
327
|
+
cam = osg.Camera(
|
|
328
|
+
referenceFrame=osg.Transform.ABSOLUTE_RF,
|
|
329
|
+
renderOrder=osg.Camera.POST_RENDER,
|
|
330
|
+
clearMask=0,
|
|
331
|
+
allowEventFocus=False,
|
|
332
|
+
projectionMatrix=osg.Matrix.identity(),
|
|
333
|
+
viewMatrix=osg.Matrix.identity(),
|
|
334
|
+
name="Composite HUD",
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
g = osg.Geode()
|
|
338
|
+
g.drawables.append(osg.createTexturedQuadGeometry(
|
|
339
|
+
osg.Vec3(-1.0, -1.0, -1.0),
|
|
340
|
+
osg.Vec3(2.0, 0.0, 0.0),
|
|
341
|
+
osg.Vec3(0.0, 2.0, 0.0)
|
|
342
|
+
))
|
|
343
|
+
|
|
344
|
+
cam.children.append(g)
|
|
345
|
+
|
|
346
|
+
cam.stateSet.textureAttributes[0] = color_tex
|
|
347
|
+
cam.stateSet.textureAttributes[1] = normal_tex
|
|
348
|
+
cam.stateSet.textureAttributes[2] = depth_tex
|
|
349
|
+
|
|
350
|
+
cam.stateSet.uniforms["colorTex"] = 0
|
|
351
|
+
cam.stateSet.uniforms["normalTex"] = 1
|
|
352
|
+
cam.stateSet.uniforms["depthTex"] = 2
|
|
353
|
+
|
|
354
|
+
p = osg.Program(name="compositeProgram", shaders=(
|
|
355
|
+
osg.Shader(osg.Shader.VERTEX, FULLSCREEN_VERTEX_SHADER),
|
|
356
|
+
osg.Shader(osg.Shader.FRAGMENT, COMPOSITE_FRAGMENT_SHADER)
|
|
357
|
+
))
|
|
358
|
+
|
|
359
|
+
# g.stateSet.setAttributeAndModes(p)
|
|
360
|
+
g.stateSet.attributes.append(p)
|
|
361
|
+
|
|
362
|
+
return cam
|
|
363
|
+
|
|
364
|
+
# --------------------------------------------------------------------------- #
|
|
365
|
+
# Input
|
|
366
|
+
# --------------------------------------------------------------------------- #
|
|
367
|
+
class VisualizeModeHandler(osgGA.GUIEventHandler):
|
|
368
|
+
"""Press 1/2/3 to visualize the raw color/depth/normal G-buffer channels; 0 for the lit composite."""
|
|
369
|
+
|
|
370
|
+
def __init__(self, mode_uniform):
|
|
371
|
+
super().__init__()
|
|
372
|
+
|
|
373
|
+
self.mode_uniform = mode_uniform
|
|
374
|
+
|
|
375
|
+
def handle(self, ea, aa):
|
|
376
|
+
if ea.handled or ea.type != osgGA.GUIEventAdapter.KEYUP:
|
|
377
|
+
return False
|
|
378
|
+
|
|
379
|
+
if ea.key == ord("0"):
|
|
380
|
+
self.mode_uniform.value = 0
|
|
381
|
+
|
|
382
|
+
elif ea.key == ord("1"):
|
|
383
|
+
self.mode_uniform.value = 1
|
|
384
|
+
|
|
385
|
+
elif ea.key == ord("2"):
|
|
386
|
+
self.mode_uniform.value = 2
|
|
387
|
+
|
|
388
|
+
elif ea.key == ord("3"):
|
|
389
|
+
self.mode_uniform.value = 3
|
|
390
|
+
|
|
391
|
+
else:
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
return True
|
|
395
|
+
|
|
396
|
+
# The real pipeline-assembly entrypoint -- returns the root Node, no viewer/window side
|
|
397
|
+
# effects. This is what external tooling (e.g. etc/pyside6-glsl.py's shader-editor scaffold,
|
|
398
|
+
# ../pyosg-cli) imports and calls directly, so it MUST stay side-effect-free w.r.t. any
|
|
399
|
+
# global viewer state. Takes (w, h) explicitly rather than reading module-level W/H.
|
|
400
|
+
def build_scene(w, h):
|
|
401
|
+
gbuffer_cam, color_tex, normal_tex, depth_tex = create_gbuffer_camera(w, h)
|
|
402
|
+
hud_cam = create_hud_camera(color_tex, normal_tex, depth_tex)
|
|
403
|
+
|
|
404
|
+
gbuffer_cam.children.append(create_scene())
|
|
405
|
+
|
|
406
|
+
znear_u = osg.Uniform("znear", 0.0)
|
|
407
|
+
zfar_u = osg.Uniform("zfar", 0.0)
|
|
408
|
+
inv_proj_u = osg.Uniform("invProjectionMatrix", osg.Matrixf.identity())
|
|
409
|
+
visualize_mode_u = osg.Uniform("visualizeMode", 0)
|
|
410
|
+
|
|
411
|
+
hud_cam.stateSet.uniforms.extend((znear_u, zfar_u, inv_proj_u, visualize_mode_u))
|
|
412
|
+
|
|
413
|
+
# Same idea as pyosg-rtt.py's update_uniforms: OSG recomputes znear/zfar every frame
|
|
414
|
+
# based on the CameraManipulator, so depth linearization and view-space reconstruction
|
|
415
|
+
# both need fresh values every frame, not just at startup.
|
|
416
|
+
#
|
|
417
|
+
# ri.state.projectionMatrix is a SHARED per-context value, not scoped to whichever
|
|
418
|
+
# camera's callback reads it -- it reflects whatever was last applied to the GL state,
|
|
419
|
+
# not necessarily this camera's own matrix. gbuffer_cam is PRE_RENDER (draws FIRST each
|
|
420
|
+
# frame), so its preDrawCallback fires before anything THIS frame has applied a fresh
|
|
421
|
+
# projection -- it read a STALE leftover from the END of the PREVIOUS frame instead:
|
|
422
|
+
# hud_cam's own identity projectionMatrix (hud_cam draws last). That silently broke
|
|
423
|
+
# znear/zfar (decomposing an identity matrix as a perspective gives garbage near/far,
|
|
424
|
+
# killing the depth/normal-edge outline) and invProjectionMatrix (inverse(identity) =
|
|
425
|
+
# identity, breaking reconstructViewPos() and blowing out the rim-light term instead).
|
|
426
|
+
# Confirmed visually 2026-08-19: outline vanished, image washed out toward rimColor's
|
|
427
|
+
# orange/yellow.
|
|
428
|
+
#
|
|
429
|
+
# hud_cam is POST_RENDER (draws LAST), so by the time ITS preDrawCallback fires, the
|
|
430
|
+
# real viewer camera has already drawn in between (PRE_RENDER -> viewer's own NESTED_RENDER
|
|
431
|
+
# -> POST_RENDER) and applied its real, this-frame-fresh projection -- the same timing
|
|
432
|
+
# guarantee the original code got directly from attaching to the caller's own
|
|
433
|
+
# v.camera.preDrawCallback, without build_scene() needing a viewer reference at all.
|
|
434
|
+
def update_uniforms(ri):
|
|
435
|
+
pm = ri.state.projectionMatrix
|
|
436
|
+
fovy, aspect, near, far = pm.getPerspective()
|
|
437
|
+
|
|
438
|
+
znear_u.value = float(near)
|
|
439
|
+
zfar_u.value = float(far)
|
|
440
|
+
inv_proj_u.value = osg.Matrixf(osg.Matrix.inverse(pm))
|
|
441
|
+
|
|
442
|
+
hud_cam.preDrawCallback = update_uniforms
|
|
443
|
+
|
|
444
|
+
root = osg.Group()
|
|
445
|
+
root.children.extend((gbuffer_cam, hud_cam, label("1 color | 2 depth | 3 normal | 0 default", w, h)))
|
|
446
|
+
|
|
447
|
+
return root
|
|
448
|
+
|
|
449
|
+
# Optional second hook, beyond build_scene(): a Node factory has no Viewer to attach
|
|
450
|
+
# osgGA.GUIEventHandlers to, so any example wanting real interactivity (custom key bindings,
|
|
451
|
+
# debug visitors, ...) beyond the default TrackballManipulator defines this too. Purely
|
|
452
|
+
# optional -- an example with nothing interactive just doesn't define it. Called with the
|
|
453
|
+
# SAME (viewer, root) shape by both __main__ below and ../pyosg-cli, so standalone and
|
|
454
|
+
# runner-driven runs behave identically instead of the runner silently dropping this example's
|
|
455
|
+
# interactivity (confirmed missing 2026-08-19, before this hook existed).
|
|
456
|
+
def configure_viewer(viewer, root):
|
|
457
|
+
gbuffer_cam, hud_cam = root.children[:2]
|
|
458
|
+
|
|
459
|
+
# build_scene() doesn't return the visualizeMode Uniform directly (its contract is just
|
|
460
|
+
# "return a Node") -- pull it back out of the HUD camera's StateSet, the same place
|
|
461
|
+
# build_scene() put it.
|
|
462
|
+
visualize_mode_u = hud_cam.stateSet.uniforms["visualizeMode"]
|
|
463
|
+
|
|
464
|
+
viewer.eventHandlers.append(VisualizeModeHandler(visualize_mode_u))
|
|
465
|
+
|
|
466
|
+
# GatherVisitor bound as a default arg -- this file doesn't drive its loop
|
|
467
|
+
# through aipython/pyosg_repl today, but a callback's free variables resolve
|
|
468
|
+
# unpredictably the moment it IS invoked from that bridge (confirmed live in
|
|
469
|
+
# 11-sketchfab.py's --repl session). See aipython/01-core.md rule 2.
|
|
470
|
+
def gather(ea, aa, GatherVisitor=GatherVisitor, v=viewer):
|
|
471
|
+
if ea.handled or ea.type != osgGA.GUIEventAdapter.KEYUP:
|
|
472
|
+
return False
|
|
473
|
+
|
|
474
|
+
if ea.key == ord("g"):
|
|
475
|
+
v.sceneData.accept(GatherVisitor(namespace=globals()))
|
|
476
|
+
|
|
477
|
+
viewer.eventHandlers.append(gather)
|
|
478
|
+
|
|
479
|
+
print("Press 1=color 2=depth 3=normal 0=lit composite (default)", flush=True)
|
|
480
|
+
|
|
481
|
+
if __name__ == "__main__":
|
|
482
|
+
osg.setNotifyLevel(osg.NotifySeverity.NOTICE)
|
|
483
|
+
|
|
484
|
+
v = osgViewer.Viewer()
|
|
485
|
+
root = build_scene(W, H)
|
|
486
|
+
|
|
487
|
+
v.sceneData = root
|
|
488
|
+
v.cameraManipulator = osgGA.TrackballManipulator()
|
|
489
|
+
|
|
490
|
+
configure_viewer(v, root)
|
|
491
|
+
|
|
492
|
+
while not v.done:
|
|
493
|
+
v.frame()
|