Shows how to achieve a hand sketch effect
Copyright 2008-2013 Matus Chochlik. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <oglplus/gl.hpp> #include <oglplus/all.hpp> #include <oglplus/shapes/plane.hpp> #include <oglplus/shapes/wicker_torus.hpp> #include <oglplus/images/brushed_metal.hpp> #include <oglplus/bound/texture.hpp> #include <oglplus/bound/framebuffer.hpp> #include <cmath> #include <functional> #include "example.hpp" namespace oglplus { class CommonVertShader : public VertexShader { public: CommonVertShader(void) : VertexShader( ObjectDesc("Common vertex shader"), StrLit("#version 330\n" "uniform mat4 CameraMatrix, ModelMatrix;" "uniform mat4 LightProjMatrix;" "uniform mat2 TextureMatrix;" "uniform vec3 CameraPosition, LightPosition;" "in vec4 Position;" "in vec3 Normal;" "in vec2 TexCoord;" "out gl_PerVertex {" " vec4 gl_Position;" "};" "out float vertNoise;" "out vec3 vertNormal;" "out vec3 vertLightDir, vertViewDir;" "out vec2 vertTexCoord;" "out vec4 vertLightTexCoord;" "void main(void)" "{" " gl_Position = " " ModelMatrix *" " Position;" " vertNoise = noise1(Position.x+Position.y+Position.z);" " vertLightDir = LightPosition - gl_Position.xyz;" " vertViewDir = CameraPosition - gl_Position.xyz;" " vertNormal = (ModelMatrix * vec4(Normal, 0.0)).xyz;" " vertTexCoord = TextureMatrix * TexCoord;" " vertLightTexCoord = LightProjMatrix* gl_Position;" " gl_Position = CameraMatrix * gl_Position;" "}") ) { } }; class TransformProgram : public HardwiredTupleProgram<std::tuple<CommonVertShader>> { private: const Program& prog(void) const { return *this; } public: ProgramUniform<Mat4f> camera_matrix, model_matrix, light_proj_matrix; ProgramUniform<Mat2f> texture_matrix; ProgramUniform<Vec3f> camera_position, light_position; TransformProgram(void) : HardwiredTupleProgram<std::tuple<CommonVertShader>>(ObjectDesc("Transform"), true) , camera_matrix(prog(), "CameraMatrix") , model_matrix(prog(), "ModelMatrix") , light_proj_matrix(prog(), "LightProjMatrix") , texture_matrix(prog(), "TextureMatrix") , camera_position(prog(), "CameraPosition") , light_position(prog(), "LightPosition") { } }; class ShadowFragShader : public FragmentShader { public: ShadowFragShader(void) : FragmentShader( ObjectDesc("Shadow fragment shader"), StrLit("#version 330\n" "in float vertNoise;" "in vec3 vertNormal;" "in vec3 vertLightDir, vertViewDir;" "in vec2 vertTexCoord;" "in vec4 vertLightTexCoord;" "void main(void){ }") ) { } }; class ShadowProgram : public HardwiredTupleProgram<std::tuple<ShadowFragShader>> { public: ShadowProgram(void) : HardwiredTupleProgram<std::tuple<ShadowFragShader>>(ObjectDesc("Shadow"), true) { } }; class LineGeomShader : public GeometryShader { public: LineGeomShader(void) : GeometryShader( ObjectDesc("Line geometry shader"), StrLit("#version 330\n" "layout(lines) in;" "layout(line_strip, max_vertices=4) out;" "in gl_PerVertex {" " vec4 gl_Position;" "} gl_in[];" "in float vertNoise[];" "in vec3 vertNormal[];" "in vec3 vertLightDir[], vertViewDir[];" "in vec2 vertTexCoord[];" "in vec4 vertLightTexCoord[];" "out gl_PerVertex {" " vec4 gl_Position;" "};" "out float geomOpacity;" "void main(void)" "{" " vec4 p0 = gl_in[0].gl_Position;" " vec4 p1 = gl_in[1].gl_Position;" " vec4 n0 = vec4(vertNormal[0], 0.0);" " vec4 n1 = vec4(vertNormal[1], 0.0);" " float dp = pow(length(p1 - p0), 0.25);" " float l0 = cos(vertNoise[0]*1.618)*dp;" " float l1 = sin(vertNoise[1]*1.618)*dp;" " vec4 v0 = p0 + n0*l0*0.01;" " vec4 v1 = p1 + n1*l1*0.01;" " vec4 v = v1 - v0;" " gl_Position = v0 - v*abs(l1*0.41);" " geomOpacity = 0.0;" " EmitVertex();" " gl_Position = p0;" " geomOpacity = 1.0;" " EmitVertex();" " gl_Position = p1;" " geomOpacity = 1.0;" " EmitVertex();" " gl_Position = v1 + v*abs(l0*0.44);" " geomOpacity = 0.0;" " EmitVertex();" " EndPrimitive();" "}") ) { } }; class LineFragShader : public FragmentShader { public: LineFragShader(void) : FragmentShader( ObjectDesc("Line fragment shader"), StrLit("#version 330\n" "in float geomOpacity;" "out vec4 fragColor;" "void main(void)" "{" " fragColor = vec4(0.0, 0.0, 0.0, 0.1+0.4*geomOpacity);" "}") ) { } }; class LineProgram : public HardwiredTupleProgram<std::tuple<LineGeomShader, LineFragShader>> { public: LineProgram(void) : HardwiredTupleProgram<std::tuple<LineGeomShader, LineFragShader>>(ObjectDesc("Line"), true) { } }; class SketchFragShader : public FragmentShader { public: SketchFragShader(void) : FragmentShader( ObjectDesc("Sketch fragment shader"), StrLit("#version 330\n" "uniform sampler3D SketchTex;" "uniform sampler2DShadow ShadowTex;" "in float vertNoise;" "in vec3 vertNormal;" "in vec3 vertLightDir, vertViewDir;" "in vec2 vertTexCoord;" "in vec4 vertLightTexCoord;" "out vec4 fragColor;" "void main(void)" "{" " float LightMult = 1.0;" " vec3 LightProjCoord = (" " vertLightTexCoord.xyz /" " vertLightTexCoord.w" " ) * 0.5 + 0.5;" " if(" " LightProjCoord.x >= 0.0 && " " LightProjCoord.x <= 1.0 && " " LightProjCoord.y >= 0.0 && " " LightProjCoord.y <= 1.0 && " " LightProjCoord.z <= 1.0" " )" " {" " float Shadow = 0.0;" " const int sn = 12;" " const float o = 1.0/32.0;" " for(int s=0; s!=sn; ++s)" " {" " float r = float(s)/sn;" " float a = 4.0*3.14151*r;" " Shadow += texture(" " ShadowTex, " " LightProjCoord+" " vec3(cos(a)*o*r, sin(a)*o*r, 0.0)" " );" " }" " LightMult *= (Shadow / sn);" " }" " vec3 LightRefl = reflect(" " -normalize(vertLightDir)," " normalize(vertNormal)" " );" " float Specular = pow(max(dot(" " normalize(LightRefl)," " normalize(vertViewDir)" " )+0.02, 0.0)*1.1, 2.0);" " float Diffuse = max(dot(" " normalize(vertNormal), " " normalize(vertLightDir)" " ), 0.0)*3.5;" " float Ambient = 0.1;" " float Light = Ambient + (Diffuse + Specular)*LightMult;" " float Shadow = clamp(2.0 - Light, 0.0, 1.0);" " vec3 Sample = texture(SketchTex, vec3(vertTexCoord, Shadow)).rgb;" " fragColor = vec4(0.0, 0.0, 0.0, pow(Sample.b*(0.5 + Shadow*0.5), 0.5));" "}") ) { } }; class SketchProgram : public HardwiredTupleProgram<std::tuple<SketchFragShader>> { private: const Program& prog(void) const { return *this; } public: ProgramUniformSampler sketch_tex, shadow_tex; SketchProgram(void) : HardwiredTupleProgram<std::tuple<SketchFragShader>>(ObjectDesc("Sketch"), true) , sketch_tex(prog(), "SketchTex") , shadow_tex(prog(), "ShadowTex") { } }; template <typename ShapeBuilder> class Shape { protected: // helper object building shape vertex attributes ShapeBuilder make_shape; // helper object encapsulating shape drawing instructions shapes::DrawingInstructions shape_instr, edge_instr; // indices pointing to shape primitive elements typename ShapeBuilder::IndexArray shape_indices, edge_indices; Context gl; // A vertex array object for the rendered shape VertexArray vao; // VBOs for the shape's vertex attributes Array<Buffer> vbos; public: Shape(const Program& prog, const ShapeBuilder& builder) : make_shape(builder) , shape_instr(make_shape.Instructions()) , edge_instr(make_shape.EdgeInstructions()) , shape_indices(make_shape.Indices()) , edge_indices(make_shape.EdgeIndices()) , vbos(3) { // bind the VAO for the shape vao.Bind(); typename ShapeBuilder::VertexAttribs vert_attr_info; const GLuint nva = 3; const GLchar* vert_attr_name[nva] = { "Position", "Normal", "TexCoord" }; for(GLuint va=0; va!=nva; ++va) { const GLchar* name = vert_attr_name[va]; std::vector<GLfloat> data; auto getter = vert_attr_info.VertexAttribGetter(data, name); if(getter != nullptr) { // bind the VBO for the vertex attribute vbos[va].Bind(Buffer::Target::Array); GLuint n_per_vertex = getter(make_shape, data); // upload the data Buffer::Data(Buffer::Target::Array, data); // setup the vertex attribs array VertexAttribArray attr(prog, name); attr.Setup(n_per_vertex, DataType::Float); attr.Enable(); } } } void Draw(void) { vao.Bind(); gl.FrontFace(make_shape.FaceWinding()); shape_instr.Draw(shape_indices, 1); } void Draw(const std::function<bool (GLuint)>& drawing_driver) { vao.Bind(); gl.FrontFace(make_shape.FaceWinding()); shape_instr.Draw(shape_indices, 1, 0, drawing_driver); } void DrawEdges(void) { vao.Bind(); edge_instr.Draw(edge_indices); } }; class SketchExample : public Example { private: // wrapper around the current OpenGL context Context gl; TransformProgram transf_prog; ShadowProgram shadow_prog; SketchProgram sketch_prog; LineProgram line_prog; ProgramPipeline shadow_pp, sketch_pp, line_pp; Shape<shapes::Plane> plane; Shape<shapes::WickerTorus> torus; // The sketch texture Texture sketch_texture; GLuint width, height; const GLuint sketch_tex_layers, shadow_tex_side; // The metal frame shadow texture and its framebuffer Texture shadow_tex; Framebuffer frame_shadow_fbo; public: SketchExample(void) : transf_prog() , sketch_prog() , plane( transf_prog, shapes::Plane( Vec3f(0, 0, 0), Vec3f(9, 0, 0), Vec3f(0, 0,-9), 9, 9 ) ), torus(transf_prog, shapes::WickerTorus()) , sketch_texture(Texture::Target::_3D) , sketch_tex_layers(8) , shadow_tex_side(1024) { Program::UseNone(); shadow_pp.Bind(); shadow_pp.UseStages(transf_prog).Vertex(); shadow_pp.UseStages(shadow_prog).Fragment(); sketch_pp.Bind(); sketch_pp.UseStages(transf_prog).Vertex(); sketch_pp.UseStages(sketch_prog).Fragment(); line_pp.Bind(); line_pp.UseStages(transf_prog).Vertex(); line_pp.UseStages(line_prog).Geometry().Fragment(); Texture::Active(0); sketch_prog.sketch_tex.Set(0); { auto bound_tex = Bind(sketch_texture, Texture::Target::_3D); for(GLuint i=0; i!=sketch_tex_layers; ++i) { auto image = images::BrushedMetalUByte( 512, 512, 64 + i*128, -(2+i*4), +(2+i*4), 64, 256-i*4 ); if(i == 0) { bound_tex.Image3D( 0, PixelDataInternalFormat::RGB, image.Width(), image.Height(), sketch_tex_layers, 0, image.Format(), image.Type(), nullptr ); } bound_tex.SubImage3D( 0, 0, 0, i, image.Width(), image.Height(), 1, image.Format(), image.Type(), image.RawData() ); } bound_tex.GenerateMipmap(); bound_tex.MinFilter(TextureMinFilter::LinearMipmapLinear); bound_tex.MagFilter(TextureMagFilter::Linear); bound_tex.WrapS(TextureWrap::Repeat); bound_tex.WrapT(TextureWrap::Repeat); bound_tex.WrapR(TextureWrap::ClampToEdge); } Texture::Active(1); sketch_prog.shadow_tex.Set(1); { auto bound_tex = Bind(shadow_tex, Texture::Target::_2D); bound_tex.MinFilter(TextureMinFilter::Linear); bound_tex.MagFilter(TextureMagFilter::Linear); bound_tex.WrapS(TextureWrap::ClampToEdge); bound_tex.WrapT(TextureWrap::ClampToEdge); bound_tex.CompareMode(TextureCompareMode::CompareRefToTexture); bound_tex.Image2D( 0, PixelDataInternalFormat::DepthComponent32, shadow_tex_side, shadow_tex_side, 0, PixelDataFormat::DepthComponent, PixelDataType::Float, nullptr ); } { auto bound_fbo = Bind( frame_shadow_fbo, Framebuffer::Target::Draw ); bound_fbo.AttachTexture( FramebufferAttachment::Depth, shadow_tex, 0 ); } gl.ClearDepth(1.0f); gl.Enable(Capability::DepthTest); gl.Enable(Capability::CullFace); gl.DepthFunc(CompareFn::LEqual); gl.BlendFunc(BlendFn::SrcAlpha, BlendFn::OneMinusSrcAlpha); gl.PolygonOffset(1.0, 1.0); gl.LineWidth(1.5); } void Reshape(GLuint vp_width, GLuint vp_height) { width = vp_width; height = vp_height; } void RenderShadowMap( const Vec3f& light_position, const Mat4f& torus_matrix, const Mat4f& light_proj_matrix ) { frame_shadow_fbo.Bind(Framebuffer::Target::Draw); gl.Viewport(shadow_tex_side, shadow_tex_side); gl.Clear().DepthBuffer(); gl.CullFace(Face::Back); transf_prog.camera_matrix.Set(light_proj_matrix); transf_prog.camera_position.Set(light_position); // Render the torus' frame transf_prog.model_matrix.Set(torus_matrix); shadow_pp.Bind(); gl.Enable(Capability::PolygonOffsetFill); torus.Draw(); gl.Disable(Capability::PolygonOffsetFill); } void RenderImage( double time, const Mat4f& torus_matrix, const Mat4f& light_proj_matrix ) { // this is going into the on-screen framebuffer Framebuffer::BindDefault(Framebuffer::Target::Draw); gl.ClearColor(1.0f, 0.9f, 0.8f, 0.0f); gl.Viewport(width, height); gl.Clear().ColorBuffer().DepthBuffer(); gl.CullFace(Face::Back); // transf_prog.light_proj_matrix.Set(light_proj_matrix); Mat4f perspective = CamMatrixf::PerspectiveX( Degrees(60), double(width)/height, 1, 60 ); // setup the camera Vec3f camera_target(0.0, 0.8, 0.0); auto camera = CamMatrixf::Orbiting( camera_target, 7.0 - SineWave(time / 14.0)*3.0, FullCircles(time / 26.0), Degrees(45 + SineWave(time / 17.0) * 40) ); Vec3f camera_position = camera.Position(); transf_prog.camera_matrix.Set(perspective*camera); transf_prog.camera_position.Set(camera_position); // render into the depth buffer shadow_pp.Bind(); gl.Enable(Capability::PolygonOffsetFill); gl.ColorMask(false, false, false, false); transf_prog.model_matrix = ModelMatrixf(); plane.Draw(); transf_prog.model_matrix.Set(torus_matrix); torus.Draw(); gl.ColorMask(true, true, true, true); gl.Disable(Capability::PolygonOffsetFill); gl.Enable(Capability::Blend); gl.Disable(Capability::Blend); // render into the color buffer sketch_pp.Bind(); gl.Enable(Capability::Blend); transf_prog.model_matrix = ModelMatrixf(); transf_prog.texture_matrix.Set(Mat2f(Vec2f(3.0, 0.0), Vec2f(0.0, 3.0))); plane.Draw(); transf_prog.model_matrix.Set(torus_matrix); transf_prog.texture_matrix.Set(Mat2f(Vec2f(8.0, 0.0), Vec2f(0.0, 2.0))); torus.Draw([](GLuint phase) -> bool { return phase < 4; }); transf_prog.texture_matrix.Set(Mat2f(Vec2f(0.0, 2.0), Vec2f(8.0, 0.0))); torus.Draw([](GLuint phase) -> bool { return phase >= 4; }); // render the edges line_pp.Bind(); transf_prog.model_matrix = ModelMatrixf(); plane.DrawEdges(); transf_prog.model_matrix.Set(torus_matrix); torus.DrawEdges(); gl.Disable(Capability::Blend); } void Render(double time) { const Vec3f light_position(16.0, 10.0, 9.0); const Vec3f torus_center(0.0, 1.5, 0.0); const Mat4f torus_matrix = ModelMatrixf::Translation(torus_center) * ModelMatrixf::RotationA( Vec3f(1, 2, 1), FullCircles(time / 17.0) ); const Mat4f light_proj_matrix = CamMatrixf::PerspectiveX(Degrees(10), 1.0, 1, 100) * CamMatrixf::LookingAt(light_position, torus_center); transf_prog.light_position.Set(light_position); RenderShadowMap( light_position, torus_matrix, light_proj_matrix ); RenderImage( time, torus_matrix, light_proj_matrix ); } bool Continue(double time) { return time < 90.0; } double ScreenshotTime(void) const { return 3.0; } }; void setupExample(ExampleParams& /*params*/){ } std::unique_ptr<ExampleThread> makeExampleThread( Example& /*example*/, unsigned /*thread_id*/, const ExampleParams& /*params*/ ){ return std::unique_ptr<ExampleThread>(); } std::unique_ptr<Example> makeExample(const ExampleParams& /*params*/) { return std::unique_ptr<Example>(new SketchExample); } } // namespace oglplus