项目中的轮廓描绘使用了扩展模型的方法,即偏移法向量然后重绘.角色自阴影使用shadow map.
轮廓通过寻边(object空间方法)实现的话,算法复杂,留待以后实现.
.h
#ifndef __CartoonRenderManager_H__ #define __CartoonRenderManager_H__ #include "Loki/Singleton.h" #include "SeerBaseConfig.h" /* CartoonRenderManager是一个单例类,用于描绘entity的轮廓. 用法: Orz::CartoonRenderManager::Instance().setOutline(entity, color); 其中entity为欲描绘的实体,color为一个颜色的枚举值.请查看函数注释. */ namespace Orz { enum CartoonOutlineType { CT_WIREFRAME, CT_EXTENDMODEL }; enum CartoonOutLineColor { CT_OL_BLACK, CT_OL_RED, CT_OL_WHITE }; class _OrzSeerBaseExport CartoonRenderManager { enum CartoonRenderConstant { SP_OFFSET = 1, SP_COLOR }; public: ~CartoonRenderManager(); public: void init(Ogre::SceneManager* psm); /* @功能 描绘entity的轮廓. @entity 欲描绘的实体 @colour 轮廓颜色的枚举值:CT_OL_BLACK, CT_OL_RED, CT_OL_WHITE分别为黑,红,白. @CartoonOutlineType 描绘轮廓的方法.目前支持扩展描绘法. */ void setOutline(Ogre::Entity* entity, Ogre::ColourValue colour = Ogre::ColourValue::Black, Ogre::Real offset = 1.0, CartoonOutlineType type = CT_EXTENDMODEL); void setOutLineOffset(Ogre::Entity* entity, Ogre::Real offset); void setOutLineColour(Ogre::Entity* _entity, Ogre::ColourValue colour); void destroyMeshPass(Ogre::Entity* entity); void setMipmapsBias(Ogre::Entity* entity, Ogre::Real bias = 0.0); private: typedef Loki::SingletonHolder<CartoonRenderManager> MySingleton; public: inline static CartoonRenderManager& Instance() { return MySingleton::Instance(); } private: void setOutlineExtendModel(Ogre::Entity* entity, Ogre::ColourValue colour, Ogre::Real offset); Ogre::SceneManager* m_psm; }; } #endif
.cpp
#include "SeerBaseStableHeaders.h" #include "CartoonRenderManager.h" namespace Orz { CartoonRenderManager::~CartoonRenderManager() { //m_psm->destroyShadowTextures(); } void CartoonRenderManager::init(Ogre::SceneManager* psm) { m_psm = psm; if (Ogre::Root::getSingletonPtr()->getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_HWRENDER_TO_TEXTURE)) { m_psm->setShadowTextureSettings(1024, 1); } else { m_psm->setShadowTextureSettings(512, 1); } m_psm->setShadowTextureSelfShadow(true); m_psm->setShadowTextureCasterMaterial("CelShading/Caster/Float"); m_psm->setShadowTexturePixelFormat(Ogre::PF_FLOAT32_R); m_psm->setShadowTechnique(Ogre::SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED); m_psm->setShadowCameraSetup(Ogre::ShadowCameraSetupPtr(new Ogre::FocusedShadowCameraSetup)); //m_psm->setShadowCameraSetup(ShadowCameraSetupPtr(new Ogre::LiSPSMShadowCameraSetup)); Ogre::Light* light = m_psm->createLight("shadowLight"); light->setType(Ogre::Light::LightTypes::LT_DIRECTIONAL); light->setCastShadows(true); light->setDirection(0, 0, -1); m_psm->getRootSceneNode()->attachObject(light); } void CartoonRenderManager::setOutline(Ogre::Entity* entity, Ogre::ColourValue colour /* = Ogre::ColourValue::Black */, Ogre::Real offset /* = 1.0 */, CartoonOutlineType type /* = CT_EXTENDMODEL */) { switch (type) { case CT_WIREFRAME: { //setOutlineWirefrme(entity, colour); } break; case CT_EXTENDMODEL: { setOutlineExtendModel(entity, colour, offset); } break; } } void CartoonRenderManager::setOutlineExtendModel(Ogre::Entity* _entity, Ogre::ColourValue colour, Ogre::Real offset) { if(!_entity) return; //destroyMeshPass(_entity); int subMeshNum = _entity->getNumSubEntities(); int NumTechniques = _entity->getSubEntity(0)->getMaterial()->getNumTechniques(); for (int i = 0; i < subMeshNum; i++) { Ogre::SubEntity* sub = _entity->getSubEntity(i); sub->setCustomParameter(SP_OFFSET, Ogre::Vector4(offset, 0, 0, 0)); sub->setCustomParameter(SP_COLOR, Ogre::Vector4(colour.r, colour.g, colour.b, colour.a)); for (int j = 0; j < NumTechniques; j ++) { Ogre::Pass* pass = NULL; pass = sub->getMaterial()->getTechnique(j)->getPass("Edges"); if(pass == NULL) { pass =sub->getMaterial()->getTechnique(j)->createPass(); pass->setName("Edges"); } pass->setVertexProgram("CelShading/OutlineVP"); pass->setFragmentProgram("CelShading/OutlineFP"); pass->setCullingMode(Ogre::CULL_ANTICLOCKWISE); // pass = sub->getMaterial()->getTechnique(j)->getPass(0); if(pass == NULL) return; pass->setVertexProgram("CelShading/ReceiverVP"); pass->setFragmentProgram("CelShading/ReceiverFP"); Ogre::TextureUnitState* state = NULL; state = pass->getTextureUnitState("1"); if(state == NULL) { state = pass->createTextureUnitState(); state->setTextureCoordSet(1); state->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); state->setTextureFiltering(Ogre::TFO_NONE); state->setContentType(Ogre::TextureUnitState::CONTENT_SHADOW); } state = pass->getTextureUnitState("2"); if(state == NULL) { state = pass->createTextureUnitState(); state->setTextureCoordSet(2); state->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); state->setTextureFiltering(Ogre::TFO_BILINEAR); state->setTextureName("Effect/shader_texture/celshading_diffuse.gif", Ogre::TEX_TYPE_1D); } } } } void CartoonRenderManager::setOutLineOffset(Ogre::Entity* _entity, Ogre::Real offset) { if(!_entity) return; int subMeshNum = _entity->getNumSubEntities(); for (int i = 0; i < subMeshNum; i++) { Ogre::SubEntity* sub = _entity->getSubEntity(i); sub->setCustomParameter(SP_OFFSET, Ogre::Vector4(offset, 0, 0, 0)); } } void CartoonRenderManager::setOutLineColour(Ogre::Entity* _entity, Ogre::ColourValue colour) { if(!_entity) return; int subMeshNum = _entity->getNumSubEntities(); for (int i = 0; i < subMeshNum; i++) { Ogre::SubEntity* sub = _entity->getSubEntity(i); sub->setCustomParameter(SP_COLOR, Ogre::Vector4(colour.r, colour.g, colour.b, colour.a)); } } void CartoonRenderManager::setMipmapsBias(Ogre::Entity* _entity, Ogre::Real bias /* = 0.0 */) { if(!_entity) return; //临时固定纹理层次mipmaps int subMeshNum = _entity->getNumSubEntities(); for (int i = 0; i < subMeshNum; i++) { Ogre::SubEntity* sub = _entity->getSubEntity(i); unsigned short NumTechniques = sub->getMaterial()->getNumTechniques(); for (unsigned short j = 0; j < NumTechniques; j++) { Ogre::Technique* tech = sub->getMaterial()->getTechnique(j); for (unsigned short k = 0; k < tech->getNumPasses(); k++) { Ogre::Pass * tempPass = tech->getPass(k); for (unsigned short l = 0; l < tempPass->getNumTextureUnitStates(); l++) tempPass->getTextureUnitState(l)->setTextureMipmapBias(bias); } } } } void CartoonRenderManager::destroyMeshPass(Ogre::Entity* _entity) { int subMeshNum = _entity->getNumSubEntities(); for (int i = 0; i < subMeshNum; i++) { int NumTechniques = _entity->getSubEntity(i)->getMaterial()->getNumTechniques(); for (int j = 0; j < NumTechniques; j++) { Ogre::Pass* pass = _entity->getSubEntity(i)->getMaterial()->getTechnique(j)->getPass("Edges"); if(pass) _entity->getSubEntity(i)->getMaterial()->getTechnique(j)->removePass(pass->getIndex()); } } } }
需要注意阴影pass及轮廓pass因为特殊原因在代码中添加.更好的做法应该在.material文件中添加.
轮廓cg
void outline_vp(float4 position : POSITION, float3 normal : NORMAL, float2 uvIn : TEXCOORD0, // outputs out float4 oPosition : POSITION, // parameters uniform float4 modelOffset, uniform float4x4 worldViewProj) { float3 N = normalize(normal); position.xyz += modelOffset.x*N; oPosition = mul(worldViewProj, position); } void outline_fp(out float4 color : COLOR, uniform float4 edgeColor : COLOR) { color = edgeColor; } //// void outline_wireframe_vp( float4 position : POSITION, // outputs out float4 oPosition : POSITION, // parameters uniform float4x4 worldViewProj ) { oPosition = mul(worldViewProj, position); } void outline_wireframe_fp( out float4 color : COLOR, uniform float4 edgeColor : COLOR ) { color = edgeColor; }
轮廓.program
vertex_program CelShading/OutlineVP cg { source CelShading.cg entry_point outline_vp profiles vs_1_1 arbvp1 default_params { param_named_auto worldViewProj worldviewproj_matrix param_named_auto modelOffset custom 1 } } fragment_program CelShading/OutlineFP cg { source CelShading.cg entry_point outline_fp profiles ps_1_1 arbfp1 fp20 default_params { param_named_auto edgeColor custom 2 } } // vertex_program CelShading/OutlineVP_Wireframe cg { source CelShading.cg entry_point outline_wireframe_vp profiles vs_1_1 arbvp1 default_params { param_named_auto worldViewProj worldviewproj_matrix } } fragment_program CelShading/OutlineFP_Wireframe cg { source CelShading.cg entry_point outline_wireframe_fp profiles ps_1_1 arbfp1 fp20 default_params { param_named_auto edgeColor custom 2 } }
阴影cg
// Shadow caster vertex program. void casterVP( float4 position : POSITION, out float4 outPosition : POSITION, out float2 outDepth : TEXCOORD0, uniform float4x4 worldViewProj, uniform float4 texelOffsets, uniform float4 depthRange ) { outPosition = mul(worldViewProj, position); // fix pixel / texel alignment outPosition.xy += texelOffsets.zw * outPosition.w; // linear depth storage // offset / scale range output #if LINEAR_RANGE outDepth.x = (outPosition.z - depthRange.x) * depthRange.w; #else outDepth.x = outPosition.z; #endif outDepth.y = outPosition.w; } // Shadow caster fragment program for high-precision single-channel textures void casterFP( float2 depth : TEXCOORD0, out float4 result : COLOR ) { #if LINEAR_RANGE float finalDepth = depth.x; #else float finalDepth = depth.x / depth.y; #endif // just smear across all components // therefore this one needs high individual channel precision result = float4(finalDepth, finalDepth, finalDepth, 1); } void receiverVP( float4 position : POSITION, float4 normal : NORMAL, float2 uv : TEXCOORD0, out float4 outPosition : POSITION, out float2 outUV : TEXCOORD0, out float4 outShadowUV : TEXCOORD1, out float outDiffuse : TEXCOORD2, #if SPECULAR out float outSpecular : TEXCOORD3, uniform float4 eyePosition, uniform float shininess, #endif uniform float4x4 world, uniform float4x4 worldViewProj, uniform float4x4 texViewProj, uniform float4 lightPosition, uniform float4 shadowDepthRange, uniform float4 lightPositionObjectSpace ) { outPosition = mul(worldViewProj, position); outUV = uv; // calculate shadow map coords float4 worldPos = mul(world, position); outShadowUV = mul(texViewProj, worldPos); #if LINEAR_RANGE // adjust by fixed depth bias, rescale into range outShadowUV.z = (outShadowUV.z - shadowDepthRange.x) * shadowDepthRange.w; #endif // calculate light vector float3 N = normalize(normal.xyz); #if DIRECTIONAL // used in directional light source float3 L = normalize(lightPositionObjectSpace.xyz); #else // use this line if used in point light source float3 L = normalize(lightPositionObjectSpace.xyz - position.xyz); #endif // Calculate diffuse component outDiffuse = max(dot(N, L) , 0); #if SPECULAR // Calculate specular component float3 E = normalize(eyePosition.xyz - position.xyz); float3 H = normalize(L + E); outSpecular = pow(max(dot(N, H), 0), shininess); #endif } void receiverFP( float4 position : POSITION, float2 uv : TEXCOORD0, float4 shadowUV : TEXCOORD1, float diffuse : TEXCOORD2, #if SPECULAR float specular : TEXCOORD3, #endif uniform sampler2D myTexture : register(s0), uniform sampler2D shadowMap : register(s1), uniform sampler1D diffuseRamp : register(s2), #if SPECULAR uniform sampler1D specularRamp : register(s3), #endif uniform float inverseShadowmapSize, uniform float fixedDepthBias, uniform float gradientClamp, uniform float gradientScaleBias, //uniform float shadowFuzzyWidth, uniform float darken, #if SPECULAR uniform float lighten, #endif out float4 result : COLOR ) { // point on shadowmap #if LINEAR_RANGE shadowUV.xy = shadowUV.xy / shadowUV.w; #else shadowUV = shadowUV / shadowUV.w; #endif float centerdepth = tex2D(shadowMap, shadowUV.xy).x; // gradient calculation float pixeloffset = inverseShadowmapSize; float4 depths = float4( tex2D(shadowMap, shadowUV.xy + float2(-pixeloffset, 0)).x, tex2D(shadowMap, shadowUV.xy + float2(+pixeloffset, 0)).x, tex2D(shadowMap, shadowUV.xy + float2(0, -pixeloffset)).x, tex2D(shadowMap, shadowUV.xy + float2(0, +pixeloffset)).x); float2 differences = abs( depths.yw - depths.xz ); float gradient = min(gradientClamp, max(differences.x, differences.y)); float gradientFactor = gradient * gradientScaleBias; // visibility function float depthAdjust = gradientFactor + (fixedDepthBias * centerdepth); float finalCenterDepth = centerdepth + depthAdjust; // shadowUV.z contains lightspace position of current object float shadow; #if FUZZY_TEST // fuzzy test - introduces some ghosting in result and doesn't appear to be needed? //shadow = saturate(1 + delta_z / (gradient * shadowFuzzyWidth)); shadow = saturate(1 + (finalCenterDepth - shadowUV.z) * shadowFuzzyWidth * shadowUV.w); #else // hard test #if PCF // use depths from prev, calculate diff depths += depthAdjust.xxxx; shadow = (finalCenterDepth > shadowUV.z) ? 1.0f : 0.0f; shadow += (depths.x > shadowUV.z) ? 1.0f : 0.0f; shadow += (depths.y > shadowUV.z) ? 1.0f : 0.0f; shadow += (depths.z > shadowUV.z) ? 1.0f : 0.0f; shadow += (depths.w > shadowUV.z) ? 1.0f : 0.0f; shadow *= 0.2f; #else shadow = (finalCenterDepth > shadowUV.z) ? 1.0f : 0.0f; #endif #endif float4 vertexColour = tex2D(myTexture, uv); //result = float4(vertexColour.xyz * shadow, 1); diffuse = tex1D(diffuseRamp, diffuse).x; float darkness = (shadow > diffuse) ? diffuse : shadow; float final = (darkness - 1) * darken; #if SPECULAR // if there is already a shadow, no specular will be casted final += (shadow > 0.0f) ? tex1D(specularRamp, specular).x * lighten : 0; #endif // then darken the original texture. result = float4(vertexColour.xyz + float3(final), 1); //result = float4(float3(final),1); }
// Generic Shadow caster material (floating point shadowmap) material CelShading/Caster/Float { technique { pass { vertex_program_ref CelShading/CasterVP { } fragment_program_ref CelShading/CasterFP { } } } } material CelShading/Base { lod_distances 300.0 800.0 // ultra-high scheme technique uses cel-shading with texture shadow support technique { scheme ultrahigh lod_index 0 pass { lighting off // Vertex program reference vertex_program_ref CelShading/ReceiverVP { } // Fragment program fragment_program_ref CelShading/ReceiverFP { } texture_unit { texture_alias textureUnit_0 tex_address_mode clamp filtering bilinear tex_coord_set 0 } texture_unit { content_type shadow tex_address_mode clamp filtering none tex_coord_set 1 } texture_unit { texture Effect/shader_texture/celshading_diffuse.gif 1d tex_address_mode clamp filtering bilinear tex_coord_set 2 } } } } // Basic material with specular which support shadows as a seperate scheme // material Lolo/CelShadingV4/Specular/Base // { // lod_distances 300.0 800.0 // // ultra-high scheme technique uses cel-shading with texture shadow support // technique // { // scheme ultrahigh // lod_index 0 // pass ShadeWithShadow // { // lighting off // // Vertex program reference // vertex_program_ref CelShading/Specular/ReceiverVP // { // } // // Fragment program // fragment_program_ref CelShading/Specular/ReceiverFP // { // } // texture_unit // { // texture_alias MainTexture // tex_address_mode clamp // filtering bilinear // tex_coord_set 0 // } // texture_unit // { // content_type shadow // tex_address_mode clamp // filtering none // tex_coord_set 1 // } // texture_unit // { // texture celshading_diffuse.gif 1d // tex_address_mode clamp // filtering bilinear // tex_coord_set 2 // } // texture_unit // { // texture celshading_specular.gif 1d // tex_address_mode clamp // filtering bilinear // tex_coord_set 3 // } // } // } // }
阴影.program
vertex_program CelShading/CasterVP cg { source CelShadingShadow.cg entry_point casterVP profiles arbvp1 vs_2_0 compile_arguments -DLINEAR_RANGE=0 default_params { param_named_auto worldViewProj worldviewproj_matrix param_named_auto texelOffsets texel_offsets param_named_auto depthRange scene_depth_range } } fragment_program CelShading/CasterFP cg { source CelShadingShadow.cg entry_point casterFP profiles arbfp1 ps_2_0 fp20 compile_arguments -DLINEAR_RANGE=0 default_params { } } vertex_program CelShading/ReceiverVP cg { source CelShadingShadow.cg entry_point receiverVP profiles arbvp1 vs_2_0 // set DDIRECTIONAL=1 if used with directional light compile_arguments -DDIRECTIONAL=1 -DLINEAR_RANGE=0 -DSPECULAR=0 default_params { param_named_auto world world_matrix param_named_auto worldViewProj worldviewproj_matrix param_named_auto texViewProj texture_viewproj_matrix //param_named_auto lightPosition light_position 0 param_named_auto shadowDepthRange shadow_scene_depth_range 0 param_named_auto lightPositionObjectSpace light_position_object_space 0 } } fragment_program CelShading/ReceiverFP cg { source CelShadingShadow.cg entry_point receiverFP profiles arbfp1 ps_2_0 fp20 compile_arguments -DLINEAR_RANGE=0 -DSPECULAR=0 -DFUZZY_TEST=0 -DPCF=1 default_params { param_named inverseShadowmapSize float 0.0009765625 param_named fixedDepthBias float 0.01 param_named gradientClamp float 0.0098 param_named gradientScaleBias float 1 //param_named shadowFuzzyWidth float 0.3 param_named darken float 0.15 } }
阴影部分的cg需要注意平行光源与聚光灯光源的区别,设定cg的编译常量来决定.