It works by comparing depths and drawing lines when there are discontinuities - which is normally at edges.
The line colour, thickness and detection thresholds are freely adjustable.
Unfortunately, right now it doesn't work with FSAA + OpenGL.
If anyone is interested, here is the code:
Sketch_ps.hlsl
Code: Select all
Texture2D<float> depthTexture : register(t0);
Texture2D RT : register(t1);
SamplerState samplerState : register(s0);
struct PS_INPUT
{
float2 uv0 : TEXCOORD0;
float3 cameraDir : TEXCOORD1;
};
uniform float2 projectionParams;
float linearDepth(float2 uv)
{
float fDepth = depthTexture.Sample(samplerState, uv).x;
return projectionParams.y / (fDepth - projectionParams.x);
}
float gain(float x, float k)
{
// copied from http://www.iquilezles.org/www/articles/functions/functions.htm
float a = 0.5*pow(2.0*((x<0.5)?x:1.0-x), k);
return (x<0.5)?a:1.0-a;
}
float4 main(PS_INPUT inPs) : SV_Target
{
//size of the lines
float size = 0.0015;
//colour of the lines
float3 LineColour = float3(1.0, 0.2, 0.0);
//steepness of the gain
float k = 4;
//higher values mean more lines / less depth deviation necessary for lines to be drawn
float sensitivity = 50000;
float3 Colour = RT.Sample(samplerState, inPs.uv0 ).rgb;
float centerDepth = linearDepth(inPs.uv0);
float topDepth = linearDepth( inPs.uv0 + float2( 0, -1) * size );
float bottomDepth = linearDepth( inPs.uv0 + float2( 0, 1) * size );
float leftDepth = linearDepth( inPs.uv0 + float2(-1, 0) * size );
float rightDepth = linearDepth( inPs.uv0 + float2(+1, 0) * size );
//difference between interpolated and measured depth
//- represents how "flat" the surface is
float yDeviation = centerDepth - (topDepth + bottomDepth) / 2.0;
float xDeviation = centerDepth - (leftDepth + rightDepth) / 2.0;
float Deviation = abs(xDeviation) + abs(yDeviation);
float Sketch = clamp(Deviation * sensitivity, 0.0, 1.0);
Sketch = gain(Sketch, k);
return float4(lerp(Colour, LineColour, Sketch), 1.0);
}
Code: Select all
#version 330
uniform sampler2D depthTexture;
uniform sampler2D RT;
out vec4 fragColour;
in block
{
vec2 uv0;
} inPs;
uniform vec2 projectionParams;
float linearDepth(vec2 uv)
{
float fDepth = texture(depthTexture, uv).x;
return projectionParams.y / (fDepth - projectionParams.x);
}
float gain(float x, float k)
{
// copied from http://www.iquilezles.org/www/articles/functions/functions.htm
float a = 0.5*pow(2.0*((x<0.5)?x:1.0-x), k);
return (x<0.5)?a:1.0-a;
}
void main()
{
//size of the lines
float size = 0.0015;
//colour of the lines
vec3 LineColour = vec3(1.0, 0.2, 0.0);
//steepness of the gain
float k = 4;
//higher values mean more lines / less depth deviation necessary for lines to be drawn
float sensitivity = 50000;
vec3 Colour = texture(RT, inPs.uv0).rgb;
float centerDepth = linearDepth(inPs.uv0);
float topDepth = linearDepth( inPs.uv0 + vec2( 0, -1) * size );
float bottomDepth = linearDepth( inPs.uv0 + vec2( 0, 1) * size );
float leftDepth = linearDepth( inPs.uv0 + vec2(-1, 0) * size );
float rightDepth = linearDepth( inPs.uv0 + vec2(+1, 0) * size );
//difference between interpolated and measured depth
//- represents how "flat" the surface is
float yDeviation = centerDepth - (topDepth + bottomDepth) / 2.0;
float xDeviation = centerDepth - (leftDepth + rightDepth) / 2.0;
float Deviation = abs(xDeviation) + abs(yDeviation);
float Sketch = clamp(Deviation * sensitivity, 0.0, 1.0);
Sketch = gain(Sketch, k);
fragColour = vec4(mix(Colour, LineColour, Sketch), 1.0);
}
Code: Select all
fragment_program Sketch_ps_HLSL hlsl
{
source Sketch_ps.hlsl
target ps_5_0 ps_4_0 ps_4_0_level_9_1 ps_4_0_level_9_3
entry_point main
}
fragment_program Sketch_ps_GLSL glsl
{
source Sketch_ps.glsl
default_params
{
param_named depthTexture int 0
param_named RT int 1
}
}
fragment_program Sketch_ps unified
{
delegate Sketch_ps_GLSL
delegate Sketch_ps_HLSL
}
material Postprocess/Sketch
{
technique
{
pass
{
depth_check off
depth_write off
cull_hardware none
vertex_program_ref Ogre/Compositor/Quad_vs
{
}
fragment_program_ref Sketch_ps
{
}
texture_unit depthTexture
{
tex_address_mode clamp
filtering none
}
texture_unit RT
{
tex_address_mode clamp
filtering none
}
}
}
}
Code: Select all
compositor_node Sketch
{
in 0 rt_input
in 1 rt_output
custom_id Ogre/Postprocess
//The depthTexture will be a "view" to rt0's depth because it has the exact same parameters
//(PF_D32_FLOAT, pool ID = 2; rt0 was asked to use a depth_texture)
//
//Note that other RTTs may share the same depth buffer if they have the same parameters as well due
//to depth buffer sharing (depth_pool 65534 is a special pool where sharing is disabled so each RTT gets
//its own depth buffer/texture; depth textures are by default placed there so we need to explicitly set
//it to 2).
texture depthTexture target_width target_height PF_D32_FLOAT depth_pool 2
//This depthTexture will be a copy of the original. We can read from 'depthTexture' directly, however
//on a lot of HW reading from the depth texture means it needs to be decompressed. If you later
//need to keep rendering using the same depth buffer (something very common in most use cases
//for this technique) you will pay the performance price for using a decompressed buffer.
//See section '4.1.4.2 Depth Textures' of the manual for an explanation.
texture depthTextureCopy target_width target_height PF_D32_FLOAT
target rt_output
{
pass depth_copy
{
//When alias_on_copy_failure is on, if the copy fails (i.e. hardware doesn't support it)
//then 'depthTextureCopy' will internally point to the same buffer as depthTexture.
//For a lot of cases this is harmless and a nice fallback.
alias_on_copy_failure on
in depthTexture
out depthTextureCopy
}
// Draw a fullscreen quad with the black and white image
pass render_quad
{
// Renders a fullscreen quad with a material
material Postprocess/Sketch
input 0 depthTextureCopy
input 1 rt_input
}
}
out 0 rt_output
out 1 rt_input
}
//The texture that the scene gets rendered to has to have the same depth format
texture renderTarget target_width target_height PF_R8G8B8 depth_texture depth_format PF_D32_FLOAT depth_pool 2
Code: Select all
Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().load(
"ReconstructPosFromDepth",
Ogre::ResourceGroupManager::
AUTODETECT_RESOURCE_GROUP_NAME ).staticCast<Ogre::Material>();
Ogre::Pass *pass = material->getTechnique(0)->getPass(0);
Ogre::GpuProgramParametersSharedPtr psParams = pass->getFragmentProgramParameters();
Ogre::Camera *camera = mGraphicsSystem->getCamera();
Ogre::Real projectionA = camera->getFarClipDistance() /
(camera->getFarClipDistance() - camera->getNearClipDistance());
Ogre::Real projectionB = (-camera->getFarClipDistance() * camera->getNearClipDistance()) /
(camera->getFarClipDistance() - camera->getNearClipDistance());
//The division will keep "linearDepth" in the shader in the [0; 1] range.
projectionB /= camera->getFarClipDistance();
psParams->setNamedConstant( "projectionParams", Ogre::Vector4( projectionA, projectionB, 0, 0 ) );