예제 모델 '코끼리'
만화적 표현은 크게 2가지 특징이 있다. 첫째로 외곽선 표현하는 것이고, 둘째로는 명암처리를 날카롭게 표현하는 것이다.
일단은 외곽선을 생성한다.
외곽선은 깊이맵을 이용하거나 혹은 노말맵을 이용하는게 일반적이다.
이에 HPF(High Pass Filter)를 적용하면 외곽선 추출을 손쉽게 얻어낼 수 있다.
라플라시안 필터(Laplician Filter) 적용 결과
외곽선을 찾았으니 명암처리를 하도록 한다.
명암처리는 셰이딩 결과 값을 양자화(Quantization) 하면 손쉽게 얻어올 수 있다.
예를 들어 0.0f~0.3f 까지는 0.0f으로 반환, 0.3f~0.7f 까지는 0.5f 반환 0.7f~1.0f 까지는 1.0f 을 반환하면 된다.
아래 이미지는 위 예를 바탕으로 만들어진 결과물이다.
최종 결과물을 만들기 위해 위에서 만들어진 두 결과물을 합성한다.
합성할 때에는 Multiply기법을 사용하면 된다. (두 픽셀값을 곱하여 값을 얻어오는 방식)
외곽선이 너무 딱딱하게 표현된다 생각이 들면 LPF를 추가로 적용해주면 된다.
FX 파일 내용 보기
//**************************************************************//
// 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.
//**************************************************************//
//--------------------------------------------------------------//
// Cartoon Drawing
//--------------------------------------------------------------//
//--------------------------------------------------------------//
// Pass 0 : Normal
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Normal_Model : ModelData = ".\\Model\\ElephantBody\\ElephantBody.3ds";
texture RTDepth_Tex : RenderColorTarget
<
float2 ViewportRatio={1.0,1.0};
string Format="D3DFMT_A8R8G8B8";
float ClearDepth=1.000000;
int ClearColor=-16777216;
>;
float4x4 view_proj_matrix : ViewProjection;
struct VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
struct VS_OUTPUT
{
float4 Pos : POSITION;
float4 Color : COLOR0;
};
VS_OUTPUT Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Normal_Vertex_Shader_vs_main( VS_INPUT In )
{
VS_OUTPUT Out;
Out.Pos = mul( In.Pos, view_proj_matrix );
Out.Color = float4(In.Normal, 1.0f);//Out.Pos.z/Out.Pos.w;
return Out;
}
float4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Normal_Pixel_Shader_ps_main(float4 Color : COLOR0) : COLOR0
{
return Color ;
}
//--------------------------------------------------------------//
// Pass 0 : Depth
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Model : ModelData = ".\\Model\\ElephantBody\\ElephantBody.3ds";
texture Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_RTDepth : RenderColorTarget
<
float2 ViewportRatio={1.0,1.0};
string Format="D3DFMT_A8R8G8B8";
float ClearDepth=1.000000;
int ClearColor=-16777216;
>;
float4x4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Vertex_Shader_view_proj_matrix : ViewProjection;
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Vertex_Shader_VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Vertex_Shader_VS_OUTPUT
{
float4 Pos : POSITION;
float4 Color : COLOR0;
};
Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Vertex_Shader_VS_OUTPUT Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Vertex_Shader_vs_main( Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Vertex_Shader_VS_INPUT In )
{
Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Vertex_Shader_VS_OUTPUT Out;
Out.Pos = mul( In.Pos, Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Vertex_Shader_view_proj_matrix );
Out.Color = Out.Pos.z/Out.Pos.w;
return Out;
}
float4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Pixel_Shader_ps_main(float4 Color : COLOR0) : COLOR0
{
return Color;
}
//--------------------------------------------------------------//
// Pass 1 : Edge
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_ScreenAlignedQuad : ModelData = ".\\Model\\Etc\\ScreenAlignedQuad.3ds";
texture RTEdge_Tex : RenderColorTarget
<
float2 ViewportRatio={1.0,1.0};
string Format="D3DFMT_A8R8G8B8";
float ClearDepth=1.000000;
int ClearColor=-16777216;
>;
float4x4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_Vertex_Shader_view_proj_matrix;
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_Vertex_Shader_VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_Vertex_Shader_VS_OUTPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_Vertex_Shader_VS_OUTPUT Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_Vertex_Shader_vs_main( Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_Vertex_Shader_VS_INPUT In )
{
Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_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 mapDepth = sampler_state
{
Texture = (RTDepth_Tex);
ADDRESSU = CLAMP;
ADDRESSV = CLAMP;
};
float mask[9] =
{ -1, -1, -1,
-1, 8, -1,
-1, -1, -1 }; // Laplacian Filter
float coord[3] = { -1, 0, +1 };
float divider = 1;
float fViewportWidth : ViewportWidth;
float fViewportHeight : ViewportHeight;
float4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_Pixel_Shader_ps_main( float2 Tex : TEXCOORD0 ) : COLOR
{
float4 Color = 0;
float4 Ret;
for( int i = 0 ; i < 9 ; i++ )
Color += mask[i] * ( tex2D( mapDepth, Tex + float2( coord[i%3]/(fViewportWidth), coord[i/3]/(fViewportHeight) ) ) );
float gray = 1 - (Color.r * 0.3 + Color.g * 0.59 + Color.b * 0.11);
Ret = float4( gray, gray, gray, 1 ) / divider;
return smoothstep(0.8, 1, Ret);
}
//--------------------------------------------------------------//
// Pass 2 : Blurred Edge
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_ScreenAlignedQuad : ModelData = ".\\Model\\Etc\\ScreenAlignedQuad.3ds";
texture RTBlurredEdge_Tex : RenderColorTarget
<
float2 ViewportRatio={1.0,1.0};
string Format="D3DFMT_A8R8G8B8";
float ClearDepth=1.000000;
int ClearColor=-16777216;
>;
float4x4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Vertex_Shader_view_proj_matrix;
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Vertex_Shader_VS_INPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Vertex_Shader_VS_OUTPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Vertex_Shader_VS_OUTPUT Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Vertex_Shader_vs_main( Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Vertex_Shader_VS_INPUT In )
{
Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_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 mapEdge = sampler_state
{
Texture = (RTEdge_Tex);
ADDRESSU = CLAMP;
ADDRESSV = CLAMP;
MAGFILTER = ANISOTROPIC;
MINFILTER = ANISOTROPIC;
MIPFILTER = ANISOTROPIC;
};
float Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_fViewportWidth : ViewportWidth;
float Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_fViewportHeight : ViewportHeight;
float Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_mask[9] =
{ 1, 1, 1,
1, 1, 1,
1, 1, 1 }; // LPH
float Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_coord[3] = { -1, 0, +1 };
float Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_divider = 9;
float4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_ps_main( float2 TexCoord : TEXCOORD0 ) : COLOR
{
float4 Color = 0;
float4 Ret;
for( int i = 0 ; i < 9 ; i++ )
{
float2 tex = TexCoord + float2( Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_coord[i%3]/Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_fViewportWidth, Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_coord[i/3]/Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_fViewportHeight ) ;
tex = saturate(tex);
Color += Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_mask[i] * ( tex2D( mapEdge, tex ) );
}
Color = Color / Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_divider;
return Color;
}
//--------------------------------------------------------------//
// Pass 3 : Interior
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Model : ModelData = ".\\Model\\ElephantBody\\ElephantBody.3ds";
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_Cartoon_Drawing_Pass_3_:_Interior_Vertex_Shader_view_proj_matrix : ViewProjection;
float4x4 view_matrix : View;
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Vertex_Shader_VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Vertex_Shader_VS_OUTPUT
{
float4 Pos : POSITION;
float3 Normal : TEXCOORD0;
float2 TexCoord : TEXCOORD1;
};
Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Vertex_Shader_VS_OUTPUT Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Vertex_Shader_vs_main( Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Vertex_Shader_VS_INPUT In )
{
Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Vertex_Shader_VS_OUTPUT Out;
Out.Pos = mul( In.Pos, Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Vertex_Shader_view_proj_matrix );
float3 Normal = mul( In.Normal, view_matrix );
Out.Normal = normalize(Normal);
Out.TexCoord = In.TexCoord;
return Out;
}
sampler2D mapModel;
texture texShade_Tex
<
string ResourceName = ".\\Texture\\Cartoon Drawing\\shade.jpg";
>;
sampler2D mapShade = sampler_state
{
Texture = (texShade_Tex);
};
float4 vColor
<
string UIName = "vColor";
string UIWidget = "Color";
bool UIVisible = true;
> = float4( 0.65, 0.65, 0.65, 1.00 );
float3 lightPos
<
string UIName = "lightPos";
string UIWidget = "Numeric";
bool UIVisible = false;
float UIMin = -1.00;
float UIMax = 1.00;
> = float3( 0.52, 0.77, -0.37 );
float4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Pixel_Shader_ps_main( float3 Normal : TEXCOORD0,
float4 TexCoord : TEXCOORD1 ) : COLOR0
{
float3 EyeDir = normalize(lightPos);
float3 Diffuse = saturate(dot(Normal, EyeDir));
float4 Color = tex2D( mapShade, float2( Diffuse.x, 0.0f ));
//return vColor * float4(Diffuse, 1.0f);
return vColor * (Color+0.4);//tex2D( mapModel, TexCoord);
}
//--------------------------------------------------------------//
// Pass 4 : Mix Above Results
//--------------------------------------------------------------//
string Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_ScreenAlignedQuad : ModelData = ".\\Model\\Etc\\ScreenAlignedQuad.3ds";
float4x4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_view_proj_matrix;
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
struct Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_VS_OUTPUT
{
float4 Pos : POSITION;
float2 TexCoord : TEXCOORD0;
};
Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_VS_OUTPUT Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_vs_main( Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_VS_INPUT In )
{
Non_Photorealistic_Rendering_Cartoon_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 mapInterior = sampler_state
{
Texture = (RTInterior_Tex);
ADDRESSU = CLAMP;
ADDRESSV = CLAMP;
};
sampler2D mapBlurredEdge = sampler_state
{
Texture = (RTEdge_Tex);
ADDRESSU = CLAMP;
ADDRESSV = CLAMP;
};
float4 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_Pixel_Shader_ps_main(float2 TexCoord : TEXCOORD0) : COLOR0
{
float4 Color1 = saturate(tex2D( mapInterior, TexCoord));
float4 Color2 = saturate(tex2D( mapBlurredEdge, TexCoord + float2(0, 0.001)));
//Color2 = saturate(tex2D( mapEdge, TexCoord ));
//return Color1;
return Color1 * Color2;
}
//--------------------------------------------------------------//
// Technique Section for HL Project.Non Photorealistic Rendering.Cartoon Drawing
//--------------------------------------------------------------//
technique Cartoon_Drawing
{
pass Pass_0_:_Normal
<
string Script = "RenderColorTarget0 = RTDepth_Tex;"
"ClearColor = (0, 0, 0, 255);"
"ClearDepth = 1.000000;";
>
{
CULLMODE = CCW;
VertexShader = compile vs_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Normal_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Normal_Pixel_Shader_ps_main();
}
pass Pass_0_:_Depth
<
string Script = "RenderColorTarget0 = Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_RTDepth;"
"ClearColor = (0, 0, 0, 255);"
"ClearDepth = 1.000000;";
>
{
CULLMODE = CCW;
VertexShader = compile vs_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_0_:_Depth_Pixel_Shader_ps_main();
}
pass Pass_1_:_Edge
<
string Script = "RenderColorTarget0 = RTEdge_Tex;"
"ClearColor = (0, 0, 0, 255);"
"ClearDepth = 1.000000;";
>
{
CULLMODE = CW;
VertexShader = compile vs_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_1_:_Edge_Pixel_Shader_ps_main();
}
pass Pass_2_:_Blurred_Edge
<
string Script = "RenderColorTarget0 = RTBlurredEdge_Tex;"
"ClearColor = (0, 0, 0, 255);"
"ClearDepth = 1.000000;";
>
{
CULLMODE = NONE;
VertexShader = compile vs_1_1 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_2_:_Blurred_Edge_Pixel_Shader_ps_main();
}
pass Pass_3_:_Interior
<
string Script = "RenderColorTarget0 = RTInterior_Tex;"
"ClearColor = (255, 255, 255, 255);"
"ClearDepth = 1.000000;";
>
{
CULLMODE = CCW;
VertexShader = compile vs_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_3_:_Interior_Pixel_Shader_ps_main();
}
pass Pass_4_:_Mix_Above_Results
{
CULLMODE = CW;
VertexShader = compile vs_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_Vertex_Shader_vs_main();
PixelShader = compile ps_2_0 Non_Photorealistic_Rendering_Cartoon_Drawing_Pass_4_:_Mix_Above_Results_Pixel_Shader_ps_main();
}
}
'Development & Tips > 3D Programming' 카테고리의 다른 글
| Ambient Occlusion (2) | 2008/07/22 |
|---|---|
| [Shader] 간단한 수묵 렌더링 (3) | 2007/09/04 |
| [Shader] 간단한 카툰 렌더링 (0) | 2007/09/03 |