"GPU-based Rendering and Animation for Chinese Painting Cartoon" 이라는 논문 중 일부를 간단하게 구현해보았다.
먼저 표현할 모델은 비교적 간단하게 표현할 수 있는 아래 사과 메쉬를 사용하였다.
수묵화의 가장 큰 특징인 먹이 번진듯 한 농담 표현과 두꺼운 선을 간단하게 적용해보았다.
언급한 두 특징을 이용하여 렌더링 순서를 정리하며 아래와 같다.
첫째. 전체적으로 윤곽은 크고 두껍게 표현한다.
둘째. 농담은 4단계로 표현하되 농담과 농담 사이는 부드럽게 한다.
셋째. 두 결과 이미지를 혼합하여 최종 결과물을 얻는다.
카툰렌더링과는 다르게 윤곽이 크고 두껍게 표현되어야 하기 때문에 따로 Edge 추출 방법을 쓰지 않고, 광원의 위치를 카메라의 위치와 일치 시키고 퐁 셰이딩(Phong Shading)으로 렌더링 한 결과로 윤곽을 표현하였다. 하지만 퐁 셰이딩으로는 완전히 윤곽을 잡아낼 수 없어서 대략 0.3~0.7(Min 0.0 ~ Max 1.0)값을 골라내서 Linear Interpolation 적용하였다.
이렇게 나온 결과물을 그냥 사용하기엔 가장자리가 너무 날카롭기 때문에 LPF(Low Pass Filter)로 부드럽게 처리하였다.
다시 광원을 원 위치로 옮기고 Phong Shading을 적용한다.
이전 카툰 렌더링은 간단하게 코드상에서 양자화 했지만 이번에는 농담별로 값이 다르기도 하고 농담 사이도 부드럽게 처리해야 하기 때문에 양자화 대신 텍스쳐에서 농담값을 불러와서 사용하였다. (아래 이미지 참조)
위 이미지를 먼저 Phong Shading한 값과 맵핑시켜 아래와 같은 결과를 얻을 수 있었다.
하지만 이 결고 또한 가장자리가 날카롭기 때문에 윤곽과 같이 LPF를 적용하였다.
위에서 먼저 얻은 두 결과물을 혼합하면 아래와 같은 결과를 얻을 수 있다.
하얀 배경으로는 수묵화 느낌을 살리기엔 허전한 부분이 존재한다.
그래서아래 그림과 같은 종이 표면같은 텍스쳐를 배경으로 사용해 보았다.
사과 뿐 아니라 다른 모델 또한 위와 같은 작업을 통해 아래와 같은 결과를 얻을 수 있었다.
닫기
//**************************************************************//
// Effect File exported by RenderMonkey 1.6
//
// - Although many improvements were made to RenderMonkey FX
// file export, there are still situations that may cause
// compilation problems once the file is exported, such as
// occasional naming conflicts for methods, since FX format
// does not support any notions of name spaces. You need to
// try to create workspaces in such a way as to minimize
// potential naming conflicts on export.
//
// - Note that to minimize resulting name collisions in the FX
// file, RenderMonkey will mangle names for passes, shaders
// and function names as necessary to reduce name conflicts.
//**************************************************************//
//--------------------------------------------------------------//
// Sumi-e Drawing
//--------------------------------------------------------------//
//--------------------------------------------------------------//
// Pass 0 : Silhouette
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_0_:_Silhouette_Model : ModelData = ".\\Model\\Apple\\apple.obj";
texture RTSilhouette_Tex : RenderColorTarget
<
float2 ViewportRatio={1.0,1.0};
string Format="D3DFMT_A8R8G8B8";
float ClearDepth=1.000000;
int ClearColor=-1;
>;
float4x4 view_proj_matrix : ViewProjection;
float4x4 view_matrix : View;
struct VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
struct VS_OUTPUT
{
float4 Pos : POSITION;
float3 Normal : TEXCOORD0;
};
VS_OUTPUT Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_0_:_Silhouette_Vertex_Shader_vs_main( VS_INPUT In )
{
VS_OUTPUT Out = (VS_OUTPUT)0;
Out.Pos = mul( In.Pos, view_proj_matrix );
float3 Normal = mul( In.Normal, view_matrix );
Out.Normal = normalize(Normal);
return Out;
}
float4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_0_:_Silhouette_Pixel_Shader_ps_main( float3 Normal : TEXCOORD0 ) : COLOR0
{
float3 EyeDir = normalize(float3(0,0,-1));
float3 Diffuse = saturate(dot(Normal, EyeDir));
float3 Linear = smoothstep(0.3f, 0.7f, Diffuse);
return float4(Linear, 1.0f);
}
//--------------------------------------------------------------//
// Pass 1 : Blurred Silhouette
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_ScreenAlignedQuad : ModelData = ".\\Model\\Etc\\ScreenAlignedQuad.3ds";
texture RTBlurredSilhouette_Tex : RenderColorTarget
<
float2 ViewportRatio={1.0,1.0};
string Format="D3DFMT_A8R8G8B8";
float ClearDepth=1.000000;
int ClearColor=-16777216;
>;
float4x4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Vertex_Shader_view_proj_matrix;
struct Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Vertex_Shader_VS_INPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
struct Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Vertex_Shader_VS_OUTPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Vertex_Shader_VS_OUTPUT Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Vertex_Shader_vs_main( Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Vertex_Shader_VS_INPUT In )
{
Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Vertex_Shader_VS_OUTPUT Out;
In.Pos.xy = sign(In.Pos.xy);
Out.Pos = float4(In.Pos.xy, 0.0, 1.0);
// Image-space
Out.TexCoord.x = 0.5 * (1 + In.Pos.x);
Out.TexCoord.y = 0.5 * (1 - In.Pos.y);
return Out;
}
sampler2D mapSilhouette = sampler_state
{
Texture = (RTSilhouette_Tex);
ADDRESSU = CLAMP;
ADDRESSV = CLAMP;
MAGFILTER = ANISOTROPIC;
MINFILTER = ANISOTROPIC;
MIPFILTER = ANISOTROPIC;
};
float fViewportWidth : ViewportWidth;
float fViewportHeight : ViewportHeight;
float mask[9] =
{ 1, 1, 1,
1, 1, 1,
1, 1, 1 }; // LPH
float coord[3] = { -2, 0, +2 };
float divider = 9;
float4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Pixel_Shader_ps_main( float2 TexCoord : TEXCOORD0 ) : COLOR
{
float4 Color = 0;
float4 Ret;
for( int i = 0 ; i < 9 ; i++ )
{
float2 tex = TexCoord + float2( coord[i%3]/fViewportWidth, coord[i/3]/fViewportHeight ) ;
tex = saturate(tex);
Color += mask[i] * ( tex2D( mapSilhouette, tex ) );
}
Color = Color / divider;
return Color;
}
//--------------------------------------------------------------//
// Pass 2 : Interior
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Model : ModelData = ".\\Model\\Apple\\apple.obj";
texture RTInterior_Tex : RenderColorTarget
<
float2 ViewportRatio={1.0,1.0};
string Format="D3DFMT_A8R8G8B8";
float ClearDepth=1.000000;
int ClearColor=-1;
>;
float4x4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_view_proj_matrix : ViewProjection;
float4x4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_view_matrix : View;
struct Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
struct Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_VS_OUTPUT
{
float4 Pos : POSITION;
float3 Normal : TEXCOORD0;
float2 TexCoord : TEXCOORD1;
};
Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_VS_OUTPUT Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_vs_main( Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_VS_INPUT In )
{
Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_VS_OUTPUT Out = (Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_VS_OUTPUT)0;
Out.Pos = mul( In.Pos, Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_view_proj_matrix );
float3 Normal = mul( In.Normal, Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_view_matrix );
Out.Normal = normalize(Normal);
Out.TexCoord = In.TexCoord;
return Out;
}
texture texShade_Tex
<
string ResourceName = ".\\Texture\\Sumi-e Drawing\\shade.jpg";
>;
sampler2D mapShade = sampler_state
{
Texture = (texShade_Tex);
ADDRESSU = CLAMP;
ADDRESSV = CLAMP;
MAGFILTER = ANISOTROPIC;
MINFILTER = ANISOTROPIC;
MIPFILTER = ANISOTROPIC;
};
float3 lightPos
<
string UIName = "lightPos";
string UIWidget = "Numeric";
bool UIVisible = false;
float UIMin = -1.00;
float UIMax = 1.00;
> = float3( 0.96, 0.64, -1.08 );
float4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Pixel_Shader_ps_main( float3 Normal : TEXCOORD0,
float2 TexCoord : TEXCOORD1 ) : COLOR0
{
float3 EyeDir = normalize(lightPos);
float3 Diffuse = saturate(dot(Normal, EyeDir));
float4 Color = tex2D( mapShade, float2( Diffuse.x, 0.0f ));
return Color;
}
//--------------------------------------------------------------//
// Pass 3 : Blurred Interior
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_ScreenAlignedQuad : ModelData = ".\\Model\\Etc\\ScreenAlignedQuad.3ds";
texture RTBlurredInterior_Tex : RenderColorTarget
<
float2 ViewportRatio={1.0,1.0};
string Format="D3DFMT_A8R8G8B8";
float ClearDepth=1.000000;
int ClearColor=-16777216;
>;
float4x4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Vertex_Shader_view_proj_matrix;
struct Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Vertex_Shader_VS_INPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
struct Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Vertex_Shader_VS_OUTPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Vertex_Shader_VS_OUTPUT Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Vertex_Shader_vs_main( Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Vertex_Shader_VS_INPUT In )
{
Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Vertex_Shader_VS_OUTPUT Out;
In.Pos.xy = sign(In.Pos.xy);
Out.Pos = float4(In.Pos.xy, 0.0, 1.0);
// Image-space
Out.TexCoord.x = 0.5 * (1 + In.Pos.x);
Out.TexCoord.y = 0.5 * (1 - In.Pos.y);
return Out;
}
sampler2D mapInterior = sampler_state
{
Texture = (RTInterior_Tex);
ADDRESSU = CLAMP;
ADDRESSV = CLAMP;
MAGFILTER = ANISOTROPIC;
MINFILTER = ANISOTROPIC;
MIPFILTER = ANISOTROPIC;
};
float Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_fViewportWidth : ViewportWidth;
float Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_fViewportHeight : ViewportHeight;
float Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_mask[9] =
{ 1, 1, 1,
1, 1, 1,
1, 1, 1 }; // LPH
float Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_coord[3] = { -2, 0, +2 };
float Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_divider = 9;
float4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_ps_main( float2 TexCoord : TEXCOORD0 ) : COLOR
{
float4 Color = 0;
float4 Ret;
for( int i = 0 ; i < 9 ; i++ )
Color += Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_mask[i] * ( tex2D( mapInterior, TexCoord + float2( Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_coord[i%3]/Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_fViewportWidth, Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_coord[i/3]/Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_fViewportHeight ) ) );
Color = Color / Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_divider;
return Color;
}
//--------------------------------------------------------------//
// Pass 4 : Mix Above Results
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_ScreenAlignedQuad : ModelData = ".\\Model\\Etc\\ScreenAlignedQuad.3ds";
float4x4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_view_proj_matrix;
struct Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_VS_INPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
struct Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_VS_OUTPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_VS_OUTPUT Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_vs_main( Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_VS_INPUT In )
{
Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_VS_OUTPUT Out;
In.Pos.xy = sign(In.Pos.xy);
Out.Pos = float4(In.Pos.xy, 0.0, 1.0);
// Image-space
Out.TexCoord.x = 0.5 * (1 + In.Pos.x);
Out.TexCoord.y = 0.5 * (1 - In.Pos.y);
return Out;
}
sampler2D mapRTBlurredInterior = sampler_state
{
Texture = (RTBlurredInterior_Tex);
ADDRESSU = CLAMP;
ADDRESSV = CLAMP;
MAGFILTER = ANISOTROPIC;
MINFILTER = ANISOTROPIC;
MIPFILTER = ANISOTROPIC;
};
sampler2D mapRTBlurredSilhouette = sampler_state
{
Texture = (RTBlurredSilhouette_Tex);
MAGFILTER = ANISOTROPIC;
ADDRESSU = WRAP;
ADDRESSV = WRAP;
MIPFILTER = ANISOTROPIC;
MINFILTER = ANISOTROPIC;
};
texture texPaper_Tex
<
string ResourceName = ".\\Texture\\Sumi-e Drawing\\paper.JPG";
>;
sampler2D mapPaper = sampler_state
{
Texture = (texPaper_Tex);
ADDRESSU = WRAP;
ADDRESSV = WRAP;
MAGFILTER = ANISOTROPIC;
MINFILTER = ANISOTROPIC;
MIPFILTER = ANISOTROPIC;
};
struct PS_INPUT
{
float2 uv : TEXCOORD0;
};
float4 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Pixel_Shader_ps_main( PS_INPUT Input ) : COLOR0
{
float4 c1 = tex2D(mapRTBlurredInterior, Input.uv);
float4 c2 = tex2D(mapRTBlurredSilhouette, Input.uv);
float4 c3 = tex2D(mapPaper, Input.uv);
//return 1-saturate((1-c1)+(1-c2));
return 1-saturate((1-c1)+(1-c2)+(1-c3));
}
//--------------------------------------------------------------//
// Technique Section for HL Project.Non Photorealistic Rendering.Sumi-e Drawing
//--------------------------------------------------------------//
technique Sumi_e_Drawing
{
pass Pass_0_:_Silhouette
<
string Script = "RenderColorTarget0 = RTSilhouette_Tex;"
"ClearColor = (255, 255, 255, 255);"
"ClearDepth = 1.000000;";
>
{
VertexShader = compile vs_1_1 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_0_:_Silhouette_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_0_:_Silhouette_Pixel_Shader_ps_main();
}
pass Pass_1_:_Blurred_Silhouette
<
string Script = "RenderColorTarget0 = RTBlurredSilhouette_Tex;"
"ClearColor = (0, 0, 0, 255);"
"ClearDepth = 1.000000;";
>
{
CULLMODE = NONE;
VertexShader = compile vs_1_1 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_1_:_Blurred_Silhouette_Pixel_Shader_ps_main();
}
pass Pass_2_:_Interior
<
string Script = "RenderColorTarget0 = RTInterior_Tex;"
"ClearColor = (255, 255, 255, 255);"
"ClearDepth = 1.000000;";
>
{
VertexShader = compile vs_1_1 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_2_:_Interior_Pixel_Shader_ps_main();
}
pass Pass_3_:_Blurred_Interior
<
string Script = "RenderColorTarget0 = RTBlurredInterior_Tex;"
"ClearColor = (0, 0, 0, 255);"
"ClearDepth = 1.000000;";
>
{
CULLMODE = NONE;
VertexShader = compile vs_1_1 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_3_:_Blurred_Interior_Pixel_Shader_ps_main();
}
pass Pass_4_:_Mix_Above_Results
{
CULLMODE = NONE;
VertexShader = compile vs_1_1 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Sumi_e_Drawing_Pass_4_:_Mix_Above_Results_Pixel_Shader_ps_main();
}
}
닫기