sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English
Українська
Show/Hide search bar
black cat logo variable logo
[2 Кві 2013]

Нефотореалістичний рендерінг (Cel shading)

Нефотореалістичний рендерінг може використовуватися в комп'ютерних іграх, мультиплікаційних фільмах, інтерактивних коміксах, для технічних ілюстрацій, для симуляції художнього малювання та штрихування, і тп. В багатьох випадках такий рендерінг зменшує кількість інформації, яку необхідно сприйняти користувачу.
В цій статті розглянуто створення ефекту Cel shading (Toon shading) з допомогою OpenGL та GLSL. Цей ефект додає зображенню дуже чіткий сулует, а також зменшує кількість кольорів, що використовуються.
Є різні методи створення cel shading ефекту. В найпростішому з них, меш необхідно візуалізувати два рази: для створення силуету і для створення перепадів кольорів. Для кожного проходу візуалізації застосовується свій шейдер.
Створення сулуету
Для створення сулуету необхідно візуалізувати збільшений меш. Це можна зробити з допомогою простого шейдера, який виводить на екран константний колір (колір сулуету - чорний) і зсуває кожну вершину меша на задану велечину вздовж нормалі. Меш візуалізуємо з увімкненою опцією візуалізації тільки задніх полігонів. Результатом буде повністю чорна фігура, яка показана на першому малюнку.

Тепер тим самим шейдером візуалізуємо меш ще раз, тільки без зсвувів вздовж нормалей і білим кольором, з відімкненим записом в буфер глибини і візуалізацією тільки передніх граней. Результат показаний на другому малюнку. Як бачимо, біла фігура наклалася поверх збільшеної чорної і створила силует меша (якщо необхідний не тільки сулует меша, але й ефект ступінчастого кольору, то візуалізація білої фігури не є необхідною).

Зсуваючи кожну вершину в сталу сторону можна зробити "вспухання" сітки, як на третьому малюнку.

Візуалізація збільшеного меша чорним кольором + візуалізація тільки задніх полігонів
Сулует кота: меш візуалізований білим кольором поверх збільшеного меша
Силует "грубого кота" :)
Створення ефекту ступінчастої зміни кольорів
Ефект ступінчастої зміни кольорів розраховується у фрагментному шейдері. Спочатку розраховуємо кожну компоненту стандартного освітлення по Фонгу (заповнююче, дифузне та блікове освітлення) і сумуємо їх. Приведемо сумарну інтенсивність світла фрагмента в інтервал [0,1]. З допомогою цього значення можна звертатися до одновимірної текстури. Високій інтенсивності будуть відповідати кольори, що розміщені правіше, а низькій інтенсивності ті, що лівіше. Кількість кольорів в текстурі буде відповідати кількості кольорів на меші.
Текстура для вибірки кольорів

Можна обійтися і без текстури. Достатньо визначити базовий колір для cel shading і квантизувати значення сумарної інтенсивності. Наприклад, наступним чином: shadeIntensity = ceil(intensity * numShades)/numShades;

Функція ceil() заокруглює значення до найближчого цілого. Значення numShades - кількість необхідних кольорів для cel shading.

Квантизація інтенсивності

Значення shadeIntensity можна використати для модуляції базового кольору, як на першому малюнку. Якщо в моделі є текстура, то можна модулювати колір текстури значенням shadeIntensity, як на другому малюнку. Або змішати базовий колір з текстурою, і модулювати їх суму, як на третьому малюнку. finalColor = baseColor * shadeIntensity; // modulation base color
finalColor = modelTexture * shadeIntensity; // modulation of color from texture
finalColor = baseColor * modelTexture * intensity; // mixed modulation

Модуляція основного кольору з інтенсивністю shadeIntensity
Модуляція кольору з текстури з інтенсивністю shadeIntensity
Модуляція змішаних кольорів з інтенсивністю shadeIntensity

Ефект металічного затінення
До Cel Shading можна додати додатковий ефект - металічне затінення. Якщо кут між нормаллю та напрямом до камери є більшим за заданий, то фрагмент отримує додаткове затінення. Наприклад: float metalic = dot(v_normal, v_directionToCamera); // cos of angle between N and V
metalic = smoothstep(0.4,0.6,metalic); // smooth interpolation between values
metalic = metalic/2 + 0.5; // shift metalic intensity by 0.5
o_FragColor.xyz = metalic * u_baseColor; // modulate color
Металічний ефект
Виклики OpenGL
void renderTestCat()
{
   //////////////////////////////////////////////////////////////
   // RENDERING OF SILHOUETTE ///////////////////////////////////
   //////////////////////////////////////////////////////////////
   glUseProgram(CEL_SILHOUETTE);// select shader for rendering of silhouette

   // BIND VERTEX ARRAY OBJECT (buffers with mesh data)
   glBindVertexArray(gpuBuffer.vao);
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

   // SET MODEL-VIEW-PROJECTION MATRIX
   glUniformMatrix4f("u_mvp_matrix",GL_FALSE, modelMatrix);

   // RENDER BLACK ENLARGED MESH
   glEnable(GL_CULL_FACE); // enable culling
   glCullFace (GL_CCW); // enable culling of front faces
   glDepthMask(GL_TRUE); // enable writes to Z-buffer
   glUniform3f("u_color1", vec3(0,0,0)); // black colour
   glUniform1f("u_offset1", 0.65f); // offset along normal
   glDrawElements (GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, NULL); // draw mesh

   // RENDER WHITE NORMAL SIZE MESH
   glCullFace (GL_CW); // enable culling of back faces
   glDepthMask(GL_FALSE); // disable writes to Z-buffer
   glUniform3f("u_color1", vec3(1,1,1)); // white color
   glUniform1f("u_offset1", 0.0f); // no offset
   glDrawElements (GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, NULL);

   //////////////////////////////////////////////////////////////
   // RENDERING OF COLORED MESH /////////////////////////////////
   //////////////////////////////////////////////////////////////
   glUseProgram(CEL_SHADES); // select shader for rendering of colors

   // SETUP TRANSFORMATION MATRICES
   glUniformMatrix4f("u_modelMat",GL_FALSE,modelMatrix);
   glUniformMatrix4f("u_viewProjMat",GL_FALSE,viewProjMatrix);
   glUniformMatrix4f("u_normalMat",GL_FALSE,normalMatrix);

   // SETUP OPENGL RENDERING STATES AND SHADER UNIFORMS
   glEnable(GL_DEPTH_TEST);
   glDepthMask(GL_TRUE);
   glUniform3f("u_camera_position",camera.lookFrom);
   glUniform3f("u_light_position",camera.lookFrom);
   glUniform1fv("u_numShades",numShades); // number of shades
   glUniform3fv("u_baseColor",vec3(1.0f,0.62f,0.0)); // base color

   // SETUP TEXTURE
   glEnable (GL_TEXTURE_2D);
   glActiveTexture (GL_TEXTURE0);
   glBindTexture (GL_TEXTURE_2D, colorTexture->texture);
   glUniform1i(unis.textureColor,0);

   // BIND VERTEX ARRAY OBJECT (buffers with mesh data) AND RENDER MESH
   glBindVertexArray(gpuBuffer.vao);
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
   glDrawElements (GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, NULL); // draw mesh

}
Шейдер для розрахунку силуету
//////////////////////////////////
// VERTEX SHADER /////////////////
#version 330

// vertex attributes layout(location = 0) in vec3 i_position;
layout(location = 1) in vec3 i_normal;

uniform mat4 u_mvp_mat; // model-view-projection matrix

uniform float u_offset1; // offset along normal

void main(void){
   vec4 tPos   = vec4(i_position + i_normal * u_offset1, 1.0);
   gl_Position = u_mvp_mat * tPos;
}

//////////////////////////////////
// FRAGMENT SHADER ///////////////
#version 330

uniform vec3 u_color1;

layout(location = 0) out vec4 o_FragColor;

void main(void){
   o_FragColor = vec4(u_color1, 1.0);
}
Шейдер для розрахунку cel shading кольорів
//////////////////////////////////
// VERTEX SHADER /////////////////
#version 330

// vertex attributes layout(location = 0) in vec3 i_position;
layout(location = 1) in vec3 i_normal;
layout(location = 2) in vec2 i_texcoord1;

uniform mat4 u_viewProj_mat; // view-projection matrix
uniform mat4 u_model_mat; // model matrix
uniform mat3 u_normal_mat; // normal matrix

uniform vec3 u_light_position;
uniform vec3 u_camera_position;

// inputs for fragment shader
out vec3 v_normal;
out vec2 v_texcoord1;
out vec3 v_directionToLight;
out vec3 v_directionToCamera;

void main(void){
   vec4 worldPos = u_model_mat * vec4(i_position, 1.0);
   v_normal = u_normal_mat * i_normal;
   v_texcoord1 = i_texcoord1;

   vec3 vectorToLight = u_light_position - worldPos.xyz;
   v_directionToLight = normalize( vectorToLight);
   v_directionToCamera = normalize( u_camera_position - worldPos.xyz );

   gl_Position = u_viewProj_mat * worldPos;
}


//////////////////////////////////
// FRAGMENT SHADER ///////////////
#version 330

uniform sampler2D u_colorTexture; // diffuse texture
uniform vec3 u_baseColor; // shading color
uniform float u_numShades; // number of shades

// inputs from vertex shader
in vec3 v_normal;
in vec2 v_texcoord1;
in vec3 v_directionToLight;
in vec3 v_directionToCamera;

layout(location = 0) out vec4 o_FragColor;

// calculate diffuse component of lighting
float diffuseSimple(vec3 L, vec3 N){
   return clamp(dot(L,N),0.0,1.0);
}

// calculate specular component of lighting
float specularSimple(vec3 L,vec3 N,vec3 H){
   if(dot(N,L)>0){
      return pow(clamp(dot(H,N),0.0,1.0),64.0);
   }
   return 0.0;
}

void main(void){
   // sample color from diffuse texture
   vec3 colfromtex = texture( u_colorTexture, v_texcoord1 ).rgb;

   // calculate total intensity of lighting
   vec3 halfVector = normalize( v_directionToLight + v_directionToCamera );
   float iambi = 0.1;
   float idiff = diffuseSimple(v_directionToLight, v_normal);
   float ispec = specularSimple(v_directionToLight,v_normal, halfVector);
   float intensity = iambi + idiff + ispec;

   // quantize intensity for cel shading
   shadeIntensity = ceil(intensity * u_numShades)/ u_numShades;

   // use base color
   o_FragColor.xyz = u_baseColor*shadeIntensity ;
   // or use color from texture
   o_FragColor.xyz = colfromtex*shadeIntensity ;
   // or use mixed colors
   o_FragColor.xyz = u_baseColor * colfromtex*shadeIntensity ;

   o_FragColor.w = 1.0;
}




Sun and Black Cat- Ігор Дихта (igor dykhta email) © 2007-2014