您的位置:首页 > 移动开发 > Unity3D

Cg Programming/Unity/Smooth Specular Highlights平滑镜面高光

2017-10-19 16:05 288 查看

逐顶点光照下环面网格的绘制


逐像素光照下环面网格的绘制

本教程涵盖了逐像素光照(也叫Phone着色)。

逐像素光照基于章节“镜面高光”。如果你没有阅读过这章,你应该首先阅读一下。

逐顶点光照(也就是为每个顶点计算表面光照,然后对顶点颜色进行插值)最大的不足之外在于质量有限,特别是如第一幅所示的镜面高光。补救的办法就是逐像素光照,它会为每一个片元基于插值的法向量来计算光照。然而虽然最终的图像质量有了相当多地提高,但性能消耗也很显著。

逐像素光照 (Phong着色)

逐像素光照也叫做Phone着色(与逐顶点光照对比,它也叫做Gouraud着色)。这个不应该跟Phone反射模型混淆(也叫Phone光照),它会用章节“镜面高光”中讨论的环境光、漫反射以及镜面项来计算表面光照。

逐像素光照的关键点很容易理解:为每个片元进行法向量和位置的插值,并且光照在片元着色器中计算。

着色器代码

除了优化,实现基于逐顶点光照的着色器代码的逐像素光照是简单的:光照计算从顶点着色器中移到了片元着色器中,并且顶点着色器必须光照计算所需的顶点输入参数写入到顶点输出参数中。随后片元着色器就会使用这些参数来计算光照。差不多就是这样了。在本教程中,我们将章节“镜面高光”中的着色器代码改编成逐像素光照。结果看起来像下面这样:

Shader "Cg per-pixel lighting" {
Properties {
_Color ("Diffuse Material Color", Color) = (1,1,1,1)
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
_Shininess ("Shininess", Float) = 10
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
// pass for ambient light and first light source

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")

// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;

struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD0;
float3 normalDir : TEXCOORD1;
};

vertexOutput vert(vertexInput input)
{
vertexOutput output;

float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;

output.posWorld = mul(modelMatrix, input.vertex);
output.normalDir = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
return output;
}

float4 frag(vertexOutput input) : COLOR
{
float3 normalDirection = normalize(input.normalDir);

float3 viewDirection = normalize(
_WorldSpaceCameraPos - input.posWorld.xyz);
float3 lightDirection;
float attenuation;

if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource =
_WorldSpaceLightPos0.xyz - input.posWorld.xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}

float3 ambientLighting =
UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;

float3 diffuseReflection =
attenuation * _LightColor0.rgb * _Color.rgb
* max(0.0, dot(normalDirection, lightDirection));

float3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = float3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * _LightColor0.rgb
* _SpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}

return float4(ambientLighting + diffuseReflection
+ specularReflection, 1.0);
}

ENDCG
}

Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources
Blend One One // additive blending

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")

// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;

struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD0;
float3 normalDir : TEXCOORD1;
};

vertexOutput vert(vertexInput input)
{
vertexOutput output;

float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;

output.posWorld = mul(modelMatrix, input.vertex);
output.normalDir = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
return output;
}

float4 frag(vertexOutput input) : COLOR
{
float3 normalDirection = normalize(input.normalDir);

float3 viewDirection = normalize(
_WorldSpaceCameraPos - input.posWorld.xyz);
float3 lightDirection;
float attenuation;

if (0.0
ab30
== _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource =
_WorldSpaceLightPos0.xyz - input.posWorld.xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}

float3 diffuseReflection =
attenuation * _LightColor0.rgb * _Color.rgb
* max(0.0, dot(normalDirection, lightDirection));

float3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = float3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * _LightColor0.rgb
* _SpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}

return float4(diffuseReflection
+ specularReflection, 1.0);
// no ambient lighting in this pass
}

ENDCG
}
}
Fallback "Specular"
}


注意,为了保证所有方向在插值中的权重都一样,顶点着色器会把归一化向量写入到output.normalDir。因为插值的方向不再是归一化的所以片元着色器会重新归一化。

总结

恭喜你,现在你了解了逐像素Phone光照是如何工作的。我们看到:

为什么逐顶点光照提供的质量有时是不够的(特别是镜面高光下)。

逐像素光照如何工作的以及如何基于逐顶点光照实现它。

深入阅读

关于逐顶点光照的着色器,你应该阅读章节“镜面高光”。

译者释疑

所谓逐顶点光照,简单地说就是在顶点着色器中计算光照颜色,该过程将为每个顶点计算一次光照颜色,然后在通过顶点在多边形所覆盖的区域对像素颜色进行线形插值。由此可见,对于高模来说,顶点光照的效果应该是不错的(由于每个多边形所覆盖的区域很小,因此插值之后每个像素的误差也很小);但对于低模(多边形面数比较少)来说,高光效果就不理想了(因为插值之后每个像素的误差就会变大)。

逐像素的计算量比逐顶点的大,要根据情况选择。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unity-shader