带遮挡效果的轮廓线(描边)在3D实时渲染中的一种实现

3D设计应用或游戏中常常需要准确的标识选中效果,轮廓线(描边)就是其中一种常用的选中态视觉表现效果。例如下图:

如上图所示,红色的轮廓线(描边)能让使用者更清晰的知道是选中了这个人体模型。但是这个选中效果存在一个缺点:模型的遮挡关系被忽略了,容易造成用户的误判。

如何在轮廓线上呈现遮挡关系呢?先看下图:

可以看得出来,上图中人体模型被遮部分的轮廓线颜色更淡一些(透明度更大),这样的效果有助于体现遮挡关系。

Demo(资源不少,可能加载会慢):Vox APP

这种轮廓线效果使用 post processing (后处理)的方式实现, 机制和模糊类似,具体实现步骤接下来详细说明(在OpenGL规范下实现)。

第一步: 设置好绘制 选中可渲染对象 的 material(作为当前渲染过程的全局material)。此material着色器关键代码:


// 顶点着色器关键代码
vec4 wpos = u_objMat * vec4(a_vs, 1.0);
gl_Position = u_projMat * u_viewMat * wpos;

// 片段着色器关键代码, u_fillColor 设置为RGBA(1.0,0.0,0.0,0.0)
FragColor0 = u_fillColor;

第二步: 初始化FBO(Frame Buffer Object)。

第三步: 在各式为RGBA的RTT上绘制 选中可渲染对象(全部使用上述 material)。

第四步: 当前渲染状态 color mask 全部设置为 false, 当前FBO深度值重新设置为1.0。选中可渲染对象 设置为不可渲染。

第五步: 渲染和选中渲染对象可能相关(有视觉遮挡关系)的所有可渲染对象(仍然使用上述material)。

第六步: 当前渲染状态 color mask 为: 只有 GREEN 通道 为 true, 其他三个通道全部为false。选中可渲染对象 重新设置为可渲染。u_fillColor 的值设置为(0.0,1.0,0.0,0.0),也就是只绘制绿色到RTT。绘制 选中可渲染对象。

第六步: 恢复正常渲染状态(例如 color mask 全部设置为 true, 解除全局material使用状态等等)。

第七步: 利用前面步骤产生的纹理数据(RTT), 使用如下shader代码生成轮廓线(可以是整个画布大小也可以是四分之一画布大小), 绘制一个平铺于画布(viewport)区域的平面, 使用alpha混合在已经绘制好的3D渲染画面上。shader关键代码如下:


const float factor = 1.0 / 9.0;
const float floatReciprocalGamma = (1.0 / 2.2);
void main() {
    
    vec4 param = u_params[0];
    // param.xy: 纹理的实际宽高像素值
    // param.z: 强度值,可以取值1.3;
    // param.w: 像素值,可以取值为2.0, 这个值控制轮廓线的粗细
    // u_params[1]: (r,g,b,a) 颜色值
    // u_params[2].x: 被遮挡的轮廓线的透明控制系数
    // 为何用数组,是为了减少和GPU之间的通讯次数, 提升性能
    vec2 dv = param.ww / param.xy;
    vec4 srcColor = VOX_Texture2D( VOX_DIFFUSE_MAP, v_uv );
    vec2 fc = srcColor.xy;
    fc.xy += VOX_Texture2D( VOX_DIFFUSE_MAP, v_uv + dv ).xy;
    fc.xy += VOX_Texture2D( VOX_DIFFUSE_MAP, v_uv - dv ).xy;
    fc.xy += VOX_Texture2D( VOX_DIFFUSE_MAP, v_uv + vec2(dv.x ,-dv.y) ).xy;
    fc.xy += VOX_Texture2D( VOX_DIFFUSE_MAP, v_uv + vec2(-dv.x ,dv.y) ).xy;
    fc.xy += VOX_Texture2D( VOX_DIFFUSE_MAP, v_uv + vec2(dv.x ,0) ).xy;
    fc.xy += VOX_Texture2D( VOX_DIFFUSE_MAP, v_uv + vec2(0 ,dv.y) ).xy;
    fc.xy += VOX_Texture2D( VOX_DIFFUSE_MAP, v_uv - vec2(dv.x ,0) ).xy;
    fc.xy += VOX_Texture2D( VOX_DIFFUSE_MAP, v_uv - vec2(0 ,dv.y) ).xy;
    fc.xy *= factor;
    
    float dis = abs(fc.x - srcColor.x);

    float fk = step(max(fc.y, srcColor.y), 0.0001);
    float fa = u_params[2].x;
    fa = (1.0 - fk) * (1.0 - fa) + fa;

    dis *= param.z;
    dis = pow(dis * dis * dis, floatReciprocalGamma);

    param = u_params[1];
    param.w *= dis * fa;
    FragColor0 = param;
}

至此,主要步骤结束。前六步的实例代码如下(由于每个人的渲染器实现不一样,这里供参考交流):



// step 1:
m_preMaterial.setRGB3f(1.0, 0.0, 0.0);
m_fboIns.setGlobalMaterial(m_preMaterial, false, true);
// step 2:
m_fboIns.runBegin();
m_fboIns.drawEntity(m_target);
// step 3:
m_fboIns.lockColorMask(RendererState.COLOR_MASK_ALL_FALSE);
m_fboIns.clearDepth(1.0);
m_target.setVisible(false);
// step 4:
m_fboIns.run(false, false, false);
// step 5:
m_fboIns.lockColorMask(RendererState.COLOR_MASK_GREEN_TRUE);
m_target.setVisible(true);
m_preMaterial.setRGB3f(0.0, 1.0, 0.0);
m_fboIns.updateGlobalMaterialUniform();
m_fboIns.drawEntity(m_target);
// step 6:
m_fboIns.runEnd();
m_fboIns.unlockRenderColorMask();
m_fboIns.unlockMaterial();

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现轮廓描边效果,可以使用ShaderMaterial来为目标物体添加一个轮廓材质。以下是一种实现轮廓描边效果的方式: 1. 首先,在着色器使用Sobel算法来检测物体表面的边缘。 2. 根据Sobel算法的输出值来确定哪些像素属于物体表面的边缘。 3. 将边缘像素的颜色设置为轮廓材质的颜色,将非边缘像素的颜色设置为目标物体的颜色。 4. 将轮廓材质应用到目标物体上。 下面是一个简单的实现例子: ```glsl // 顶点着色器 varying vec3 vNormal; void main() { vNormal = normal; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } // 片元着色器 uniform vec3 outlineColor; varying vec3 vNormal; void main() { vec3 edge = vec3(1.0) - abs(vNormal); float edgeFactor = (edge.x + edge.y + edge.z) / 3.0; vec3 color = mix(outlineColor, vec3(1.0), edgeFactor); gl_FragColor = vec4(color, 1.0); } ``` 在这个着色器,我们使用了一个Sobel算法来检测表面的边缘。我们计算了法线向量的绝对值,并将其与1.0相减,以获得一个表示边缘的向量。然后,我们将这个向量的平均值作为边缘因子来混合轮廓颜色和目标物体颜色。 在使用ShaderMaterial时,我们可以将这个着色器编译并将其应用到目标物体上。同时,我们还需要设置轮廓颜色和其他材质属性(如透明度、混合模式等)。 ```javascript const outlineMaterial = new THREE.ShaderMaterial({ uniforms: { outlineColor: { value: new THREE.Color(0x00ff00) }, }, vertexShader: vertexShader, fragmentShader: fragmentShader, }); const object = new THREE.Mesh(geometry, material); object.add(new THREE.Mesh(geometry, outlineMaterial)); ``` 这里我们将轮廓材质添加到了目标物体的子对象上,这样它就能够在目标物体周围形成一个轮廓。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值