这一节教程是关于渲染3D物体的发光边缘,代码结构如下:
如上面图中虚幻四引擎中被选中的立方体的边缘是发光的,我实现的就是这种发光效果
这里有篇博客介绍了“Glow”是怎么实现的:
[Unity3D][Shader 着色器]给物体边缘加高光轮廓的办法
这里大致说下我实现的方法步骤:
(1)正常的渲染整个场景得到一张RTT,我称其为SceneRTT,如下所示:
(2)渲染要发光的物体成一张BlackWhiteRTT(也就是只有黑白两种颜色的RT),如下所示:
实现的shader代码:
DrawBlackWhiteShader.fx
cbuffer CBMatrix:register(b0)
{
matrix World;
matrix View;
matrix Proj;
};
struct VertexIn
{
float3 Pos:POSITION;
};
struct VertexOut
{
float4 Pos:SV_POSITION;
};
VertexOut VS(VertexIn ina)
{
VertexOut outa;
outa.Pos = mul(float4(ina.Pos, 1.0f), World);
outa.Pos = mul(outa.Pos, View);
outa.Pos = mul(outa.Pos, Proj);
return outa;
}
float4 PS(VertexOut outa) : SV_Target
{
//输出的像素为白色
float4 color = float4(1.0f,1.0f,1.0f,1.0f);
return color;
}
(3)对BlackWhiteRTT进行高斯模糊,将物体边缘的像素偏移,也就是先进行水平模糊,然后进行垂直模糊,最终得到GlowMapVerticalBlurRTT,如下所示:
相应的Shader实现代码:
HorizontalBlurShader.fx
Texture2D ShaderTexture:register(t0); //纹理资源
SamplerState SampleType:register(s0); //采样方式
//VertexShader
cbuffer CBMatrix:register(b0)
{
matrix World;
matrix View;
matrix Proj;
};
cbuffer CBScreenWidth:register(b1)
{
float ScreenWidth;
float3 pad;
}
struct VertexIn
{
float3 Pos:POSITION;
float2 Tex:TEXCOORD0; //多重纹理可以用其它数字
};
struct VertexOut
{
float4 Pos:SV_POSITION;
float2 Tex:TEXCOORD0;
float2 TexCoord1:TEXCOORD1;
float2 TexCoord2:TEXCOORD2;
float2 TexCoord3:TEXCOORD3;
};
VertexOut VS(VertexIn ina)
{
VertexOut outa;
//将坐标变到齐次裁剪空间
outa.Pos = mul(float4(ina.Pos,1.0f), World);
outa.Pos = mul(outa.Pos, View);
outa.Pos = mul(outa.Pos, Proj);
//获取纹理值
outa.Tex= ina.Tex;
//根据纹理坐标系统U的宽度为1.0f,求出纹理图中每个像素的U值的宽度(ScreenWidth代表了水平上有多少个像素)
float TexelSize = 1.0f / ScreenWidth;
//获取像素以及其水平周围左右两边的像素的UV坐标,这个Shader只处理水平模糊,因此V值偏移量0.0f;
outa.TexCoord1 = ina.Tex + float2(TexelSize*-1.0f, 0.0f);
outa.TexCoord2 = ina.Tex + float2(TexelSize*0.0f, 0.0f);//刚好
outa.TexCoord3 = ina.Tex + float2(TexelSize*1.0f, 0.0f);//右边
return outa;
}
float4 PS(VertexOut outa) : SV_Target
{
float4 color; //输出的颜色值
float weight0, weight1; //原像素和其左右像素的权重值
float TotalWeight;
//赋予权重值
weight0 = 1.0f;
weight1 = 1.0f;
//求出总权重值
TotalWeight = weight0 + 2.0f*weight1;
//求出原像素和水平左右旁边像素的权重比
weight0 = weight0 / TotalWeight;
weight1 = weight1 / TotalWeight;
//初始化颜色值
color = float4(0.0f, 0.0f, 0.0f, 1.0f);
//将原像素和水平左右像素的颜色值按权重相加起来
color += ShaderTexture.Sample(SampleType, outa.TexCoord1)*weight1;
color += ShaderTexture.Sample(SampleType, outa.TexCoord2)*weight0;
color += ShaderTexture.Sample(SampleType, outa.TexCoord3)*weight1;
return color;
}
VerticalBlurShader.fx
Texture2D ShaderTexture:register(t0); //纹理资源
SamplerState SampleType:register(s0); //采样方式
//VertexShader
cbuffer CBMatrix:register(b0)
{
matrix World;
matrix View;
matrix Proj;
};
cbuffer CBScreenHeight:register(b1)
{
float ScreenHeight;
float3 pad;
}
struct VertexIn
{
float3 Pos:POSITION;
float2 Tex:TEXCOORD0; //多重纹理可以用其它数字
};
struct VertexOut
{
float4 Pos:SV_POSITION;
float2 Tex:TEXCOORD0;
float2 TexCoord1:TEXCOORD1;
float2 TexCoord2:TEXCOORD2;
float2 TexCoord3:TEXCOORD3;
};
VertexOut VS(VertexIn ina)
{
VertexOut outa;
//将坐标变到齐次裁剪空间
outa.Pos = mul(float4(ina.Pos, 1.0f), World);
outa.Pos = mul(outa.Pos, View);
outa.Pos = mul(outa.Pos, Proj);
//获取纹理值
outa.Tex = ina.Tex;
//根据纹理坐标系统V的宽度为1.0f,求出纹理图中每个像素的V值的宽度(ScreenHeight代表了垂直上有多少个像素)
float TexelSize = 1.0f / ScreenHeight;
//获取像素以及其水平周围上下两边的像素的UV坐标,这个Shader只处理垂直模糊,因此U值偏移量0.0f;
outa.TexCoord1 = ina.Tex + float2(0.0f,TexelSize*-1.0f);
outa.TexCoord2 = ina.Tex + float2(0.0f,TexelSize*0.0f);//刚好
outa.TexCoord3 = ina.Tex + float2(0.0f,TexelSize*1.0f);//下边
return outa;
}
float4 PS(VertexOut outa) : SV_Target
{
float4 color; //输出的颜色值
float weight0, weight1;//原像素和其左右像素的权重值
float TotalWeight;
//赋予权重值
weight0 = 1.0f; //越靠近原像素的权重越大
weight1 = 1.0f;
//求出总权重值
TotalWeight = weight0 + 2.0f*weight1;
//求出原像素和水平左右旁边像素的权重比
weight0 = weight0 / TotalWeight;
weight1 = weight1 / TotalWeight;
//初始化颜色值
color = float4(0.0f, 0.0f, 0.0f, 1.0f);
//将原像素和水平左右像素的颜色值按权重相加起来
color += ShaderTexture.Sample(SampleType, outa.TexCoord1)*weight1;
color += ShaderTexture.Sample(SampleType, outa.TexCoord2)*weight0;
color += ShaderTexture.Sample(SampleType, outa.TexCoord3)*weight1;
return color;
}
(4)进行2D Rendering,将第一步得到的“SceneRTT”和第三步得到的“GlowMapVerticalBlurRTT”作为纹理资源使用。实现“发光边缘”的思路:由于我们对之前只有黑白两色的BlackWhiteRTT进行渲染,那么经过高斯模糊后,GlowMapVerticalBlurRTT的边缘颜色值变为灰色,也就是位于0.0f和1.0f之间,将这部分的像素变为“高亮部分”,如下图所示:
实现的Shader代码如下:
GlowShader.fx
Texture2D SceneRTT:register(t0); //SceneRTT
Texture2D GlowMapRTT:register(t1); //GlowMapRTT
SamplerState WrapSampleType:register(s0); //采样方式
cbuffer CBMatrix:register(b0)
{
matrix World;
matrix View;
matrix Proj;
};
cbuffer CBGlow:register(b1)
{
float3 GlowColor;
float GlowScale;
};
struct VertexIn
{
float3 Pos:POSITION;
float2 Tex:TEXCOORD0; //多重纹理可以用其它数字
};
struct VertexOut
{
float4 Pos:SV_POSITION;
float2 Tex:TEXCOORD0;
};
VertexOut VS(VertexIn ina)
{
VertexOut outa;
//将坐标变换到观察相机下的齐次裁剪空间
outa.Pos = mul(float4(ina.Pos, 1.0f), World);
outa.Pos = mul(outa.Pos, View);
outa.Pos = mul(outa.Pos, Proj);
//获取纹理坐标
outa.Tex = ina.Tex;
return outa;
}
float4 PS(VertexOut outa) : SV_Target
{
float4 SceneRTTColor;
float4 GlowMapRTTColor;
float4 color;
//对GlowMapRTT和SceneRTT采样
SceneRTTColor= SceneRTT.Sample(WrapSampleType, outa.Tex);
GlowMapRTTColor= GlowMapRTT.Sample(WrapSampleType, outa.Tex);
if (GlowMapRTTColor.r>0.0f&& GlowMapRTTColor.r<1.0f)
{
color = SceneRTTColor + float4(GlowColor, 1.0f)*GlowScale;
}
else
{
color = SceneRTTColor;
}
return color;
}