이번엔 간단한 수묵 렌더링 구현!
일단 "GPU-based Rendering and Animation for Chinese Painting Cartoon" 이라는 이상한 논문 중 일부만 정말간단하게 구현한거라는걸 알아두시길.
먼저 표현할 모델부터!
수묵으로 뭘 표현하면뭐가 이쁠까? 고민하다가 사과로 결정!
수묵화의 특징은 간단하게 말하면 적당한 농담이 곁들어져있고 선이 좀 두껍고 뭐....
대충 알거라 생각하고 스킵 (역시나 본인도 잘 모름 ㅠ.ㅠ)
그래서 크게 2가지만 살리기로 했음!
첫째. 전체적으로 윤곽은 크게 살리자!
둘째. 농담은 4단계로 농담과 농담 사이는 부드럽게 해보자!
본인은 수묵에 대해 아는게 전혀 없어서 대충 이정도만 적용하기로 결정!
그럼 이 사과의 윤곽을 따보도록 하자규~
카툰렌더링과는 다르게 윤곽이 크게 드러나야 하기 때문에 따로 Edge 추출 방법을 쓰지 않고, 광원의 위치를 카메라의 위치와 동기화 시키고 뽕 셰이딩(Phong Shading)으로 대충 빠바방 해서 윤곽을 만들기로 함. 하지만 뽕 셰이딩으로는 완전히 윤곽을 잡아낼 수 없어서 대충 0.3~0.7값만 골라내서 Linear Interpolation 적용
요로케 나온 결과물을 그냥 쓰기엔 너무 끝이 날카(?)롭기 때문에 LPF로 불러효과 작렬!
윤곽을 얻어냈으니 이번에는 내부 농담 처리 시작!
일단 다시 광원 위치를 원래대로 놓고 이번에도 가짜 Phong Shading으로 광원처리 빵!
이전 카툰 렌더링에서는 그냥 코드에서 양자화 해버렸지만 이번에는 이래저래 귀찮아서(농담별로 값이 다른건 둘째치고 농담 사이도 부드럽게 처리하고 해야함) 양자화 대신에 텍스쳐에서 농담값을 불러와서 쓰기로 함.
보는봐와 같이 텍스쳐에 대충 어디부터 어디까지가 무슨 값이고 그 사이는 부드럽게 뭐 이런 정보가 들어있음. (바보가 아니면 알 수 있다고 생각하는데...)
뭐 저 값이야 가져오는건 쉽지 않은가... 대충 빛에대한 밝기값 0~1 나오는거 UV좌표로 먹여버리면 바로 나오는거니깐 -_-ㅋ
그런고로 다음과 같은 결과 득템 성공!
하지만 이녀석도 변두리가 날카로운고로 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.
//**************************************************************//
//--------------------------------------------------------------//
// 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();
}
}
'Development > 3D Programming' 카테고리의 다른 글
| Ambient Occlusion (2) | 2008/07/22 |
|---|---|
| [Shader] 간단한 수묵 렌더링 (1) | 2007/09/04 |
| [Shader] 간단한 카툰 렌더링 (0) | 2007/09/03 |
이올린에 북마크하기
이올린에 추천하기