FyroxEngine / Fyrox

3D and 2D game engine written in Rust
https://fyrox.rs
MIT License
7.61k stars 340 forks source link

Custom Render Pass #427

Closed ghost closed 1 year ago

ghost commented 1 year ago

Hi,

I have followed the documentation example (with minor changes in vertex shader due to shader compilation issues) and I expect the result to be a scene rendered in red, however I see no change. Is there a missing step ?

I have a scene setup with a camera which renders as I expect it to. The setup() function below is called and the render pass appears to be added to the renderer.

    core::{
        algebra::{Matrix4},
        pool::Handle,
        sstorage::ImmutableString
    },
    scene::{
        Scene,
        mesh::{
            surface::{SurfaceData},
        },
    },
    renderer::{
        Renderer,
        SceneRenderPass,
        SceneRenderPassContext,
        RenderPassStatistics,
        framework::{
            error::FrameworkError,
            framebuffer::{DrawParameters},
            gpu_program::{
                GpuProgram,
                UniformLocation
            },
            geometry_buffer::{
                GeometryBuffer,
                GeometryBufferKind
            }
        }
    },
    utils::log::Log
};

use std::rc::Rc;
use std::cell::RefCell;

pub struct MyRenderPass {
    enabled: bool,
    shader: GpuProgram,
    target_scene: Handle<Scene>,
    quad: GeometryBuffer,
    world_view_proj: UniformLocation,
}

impl MyRenderPass {
    pub fn new(
        renderer: &mut Renderer,
        target_scene: Handle<Scene>,
    ) -> Result<Self, FrameworkError> {
        let vs = r#"
                layout(location = 0) in vec3 vertexPosition;

                uniform mat4 worldViewProjectionMatrix;

                void main()
                {
                    gl_Position = worldViewProjectionMatrix * vec4(vertexPosition, 1.0);
                }
            "#;

        let fs = r#"                
                out vec4 FragColor;             

                void main()
                {
                    FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                }
            "#;

        let shader = GpuProgram::from_source(&mut renderer.state, "MyShader", vs, fs)?;

        Ok(Self {
            enabled: true,
            world_view_proj: shader.uniform_location(
                &renderer.state,
                &ImmutableString::new("worldViewProjectionMatrix"),
            )?,
            target_scene,
            quad: GeometryBuffer::from_surface_data(
                &SurfaceData::make_quad(&Matrix4::identity()),
                GeometryBufferKind::StaticDraw,
                &mut renderer.state,
            ),
            shader,
        })
    }
}

impl SceneRenderPass for MyRenderPass {
    fn on_ldr_render(
        &mut self,
        ctx: SceneRenderPassContext,
    ) -> Result<RenderPassStatistics, FrameworkError> {
        let mut stats = RenderPassStatistics::default();

        // Make sure to render only to target scene.
        if self.enabled && ctx.scene_handle == self.target_scene {
            stats += ctx.framebuffer.draw(
                &self.quad,
                ctx.pipeline_state,
                ctx.viewport,
                &self.shader,
                &DrawParameters::default(),
                |mut program| {
                    program.set_matrix4(&self.world_view_proj, &Matrix4::identity());
                },
            );
        }

        Ok(stats)
    }
}

pub fn setup(renderer: &mut Renderer, render_pass: MyRenderPass) {
    let shared_pass = Rc::new(RefCell::new(render_pass));
    // You can share the pass across multiple places to be able to control it.
    renderer.add_render_pass(shared_pass);
}
ghost commented 1 year ago

Ok seems like the custom render pass is for post process effects ? I have at least found a way to get a minimal working example, although I'm not sure why I had to transform the quad and texture coordinates, nor how safe it is to grab the color attachment as I have done.

    core::{
        algebra::{Matrix4, Vector3, Vector4},
        pool::Handle,
        sstorage::ImmutableString
    },
    scene::{
        Scene,
        mesh::{
            surface::{SurfaceData},
        },
    },
    renderer::{
        Renderer,
        SceneRenderPass,
        SceneRenderPassContext,
        RenderPassStatistics,
        framework::{
            error::FrameworkError,
            framebuffer::{DrawParameters},
            gpu_program::{
                GpuProgram,
                UniformLocation
            },
            geometry_buffer::{
                GeometryBuffer,
                GeometryBufferKind
            }
        }
    },
    utils::log::Log
};

use std::rc::Rc;
use std::cell::RefCell;

pub struct MyRenderPass {
    enabled: bool,
    shader: GpuProgram,
    target_scene: Handle<Scene>,
    quad: GeometryBuffer,
    world_view_projection: UniformLocation,
    color_sampler: UniformLocation
}

impl MyRenderPass {
    pub fn new(
        renderer: &mut Renderer,
        target_scene: Handle<Scene>,
    ) -> Result<Self, FrameworkError> {
        let vs = r#"
                layout(location = 0) in vec3 vertexPosition;
                layout(location = 1) in vec2 vertexTexCoord;

                uniform mat4 worldViewProjection;

                out vec2 texCoord;

                void main()
                {
                    texCoord = 1.0 - vertexTexCoord;
                    gl_Position = worldViewProjection * vec4(vertexPosition, 1.0);
                }
            "#;

        let fs = r#"            
                in vec2 texCoord;    
                out vec4 FragColor; 

                uniform sampler2D colorSampler;

                void main()
                {
                    FragColor = texture(colorSampler, texCoord) * vec4(1.5, 1.0, 1.0, 1.0);
                }
            "#;

        let shader = GpuProgram::from_source(&mut renderer.state, "MyShader", vs, fs)?;

        Ok(Self {
            enabled: true,
            world_view_projection: shader.uniform_location(
                &renderer.state,
                &ImmutableString::new("worldViewProjection"),
            )?,
            color_sampler: shader.uniform_location(
                &renderer.state,
                &ImmutableString::new("colorSampler"),
            )?,
            target_scene,
            quad: GeometryBuffer::from_surface_data(
                &SurfaceData::make_quad(&Matrix4::new_translation(&Vector3::new(0.5, 0.5, 0.0))),
                GeometryBufferKind::StaticDraw,
                &mut renderer.state,
            ),
            shader,
        })
    }
}

impl SceneRenderPass for MyRenderPass {
    fn on_ldr_render(
        &mut self,
        ctx: SceneRenderPassContext,
    ) -> Result<RenderPassStatistics, FrameworkError> {
        let mut stats = RenderPassStatistics::default();

        // Make sure to render only to target scene.
        if self.enabled && ctx.scene_handle == self.target_scene {
            let frame_matrix = Matrix4::new_orthographic(
                0.0,
                ctx.viewport.w() as f32,
                ctx.viewport.h() as f32,
                0.0,
                -1.0,
                1.0,
            ) * Matrix4::new_nonuniform_scaling(&Vector3::new(
                ctx.viewport.w() as f32,
                ctx.viewport.h() as f32,
                0.0,
            ));

            let screen_texture = ctx.framebuffer.color_attachments()[0].texture.clone();

            stats += ctx.framebuffer.draw(
                &self.quad,
                ctx.pipeline_state,
                ctx.viewport,
                &self.shader,
                &DrawParameters::default(),
                |mut program| {
                    program.set_matrix4(&self.world_view_projection, &frame_matrix);

                    program.set_texture(&self.color_sampler, &screen_texture);
                },
            );
        }

        Ok(stats)
    }
}

pub fn setup(renderer: &mut Renderer, render_pass: MyRenderPass) {
    let shared_pass = Rc::new(RefCell::new(render_pass));
    // You can share the pass across multiple places to be able to control it.
    renderer.add_render_pass(shared_pass);
}
mrDIMAS commented 1 year ago

Custom render passes allows you to render anything you want to, however it does not auto-magically applied to scene nodes. You need to implement drawing manually for them. One of the main usages of custom render passes is post effects, yes. You might be interested in shaders and materials if you want to apply custom effects to meshes.

ghost commented 1 year ago

Somehow I overlooked the fact that pbr is supported by default, that'll save some effort.