Created
October 14, 2017 23:52
-
-
Save Aeva/8927ceffe0af865cdda976976a356d93 to your computer and use it in GitHub Desktop.
python rendering API mockup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# "DataSource" classes are used to define assorted properties and | |
# functions that might be used by a shader. The properties etc are | |
# not specific to any particular shader stage. So, "vertex" | |
# properties will have an implied varrying variable if they're | |
# accessed from the fragment shader. | |
class BasicModel(DataSource): | |
position = vertex.float4(index=0) | |
normal = vertex.float3() | |
uv = vertex.float2() | |
world_matrix = uniform.matrix4() | |
@property | |
def world_space(self) -> float4: | |
return self.world_matrix @ position | |
# "DataSources" can be subclassed for polymorphic behavior. This is | |
# useful for things like vertex animation. | |
# When a DataSource references an outside variable or function (such | |
# as the call to "time.time()" below), the compiler will try to either | |
# inline the function into the shader if it is possible and reasonable | |
# to do, otherwise it will note what the shader wanted to access so | |
# that the result can be automatically produced cpu-side before | |
# starting the shader, and then the result would be passed in as an | |
# implicit uniform variable. Or it'll throw an error. | |
# The python inside of a DataSource class is not really python, but | |
# rather a subset of it. So, attempting to do something the compiler | |
# can't happen will throw an error of some kind. | |
class Rotatoe(BasicModel): | |
@property | |
def world_space(self) -> float4: | |
return matrix4.rotate_z(time.time()) @ self.world_matrix @ position | |
# DataSource classes don't need to contain any per-vertex data. You | |
# can use them to encapsulate things that might be specific to one | |
# shader stage or another. In the end, the shader programs are glue | |
# to mix the DataSources together to produce some useful outcome. | |
class Camera(DataSource): | |
perspective_matrix = uniform.matrix4() | |
view_matrix = uniform.matrix4() | |
@property | |
def camera_matrix(self) -> matrix4: | |
return self.perspective_matrix @ self.view_matrix | |
def project(self, point: float4) -> float4: | |
return self.camera_matrix @ point | |
class Material(DataSource): | |
texture = uniform.pixmap() | |
fnord = uniform.float() | |
def sample(self, uv: float2) -> float4: | |
return self.texture.sample(uv) * self.fnord | |
# RenderPasses objects define render targets and shader stages used by | |
# a render pass. Compute shaders might be made available as a special | |
# decorator. | |
class GBuffers(RenderPass): | |
color = buffer.RGBA(primary=1) | |
depth = buffer.FLOAT() | |
def vertex_shader(camera: Camera, model: BasicModel): | |
projected = camera.project(model.world_space) | |
self.material_uv = model.uv | |
self.depth = projected.z | |
return projected | |
def fragment_shader(material: Material): | |
self.color = material.sample(self.material_uv) | |
# Splat passes are like RenderPasses, but the vertex_shader may be | |
# omitted. They draw a full screen quad by default, and the implicit | |
# vertex shader provides the screen space uv. These are good for post | |
# processing effects. | |
class SomeEffect(SplatPass): | |
color = buffer.RGBA() | |
def fragment_shader(gbuffers: GBuffers): | |
some_color = gbuffers.color.sample(self.uv) | |
some_depth = gbuffers.depth.sample(self.uv) | |
a, b, c, d = some_color | |
inverse = float4(float3(1.0) - float3(a, b, c), 1.0) | |
self.color = lerp(some_color, inverse, some_depth) | |
# There are a few things missing still: | |
# | |
# - There is currently no mechanism for establishing what the legal | |
# shader permutations are, or running the compiler. | |
# | |
# - I'd like some notion of DataSource "collections". Eg, a model | |
# and a material would be used together in a collection, and the | |
# render passes could be written to consume those instead. | |
# | |
# - Some means of quering collections for objects that meet a | |
# specific criteria. These queries would basically reduce to | |
# commands to be executed by the backend, outside of python. | |
# | |
# - Some way of connecting this all together. Eg, funcitons who | |
# define collection queries and the flow of data between that and | |
# render passes. These also generate commands to be executed by | |
# the backend, outside of python. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment