Shader实验室:灰度化实践(二)

Number of views 308

上一篇介绍了区域灰度化的原理与效果的简单实现,这篇实验继续上篇已完成的实验代码,做一个舞台追光的效果

image.png

介绍

舞台追光效果或许是最贴合区域灰度化在Shader上应用的例子,它的明暗变化正好能够衬托场景的整体氛围。

分析

上一篇的原理部分已经对区域灰度化的实现算法有过分析,这里不再过多阐述。我们主要对实现的效果进行说明,首先场景中新建了三个面片,分别是两堵墙与舞台地面,随着鼠标的移动,光圈也随之移动,下面进入实验部分(结尾会提供本篇实验的工程文件的下载地址)。

实验

打开上节实验中的Shader文件,在Properties处新增_MainColor与_ColorStrength两个属性:

Properties {   
    _MainTex ("Texture", 2D) = "white" {}   
    // 调整整体环境的颜色(包括光圈与舞台跟墙面)   
    _MainColor ("Main Color", Color) = (0.5,0.5,0.5,1.0)   
    _Position ("World Position", Vector) = (0,0,0,1)   
    // 只影响光圈区域的颜色强度   
    _ColorStrength("Color Strength", Range(1,4)) = 1   
    _Radius("Radius", Range(0,100)) = 3   
    _Softness("Softness", Range(0,100)) = 10 
}

在frag函数中应用_MainColor与_ColorStrength属性:

half _ColorStrength; 
fixed4 _MainColor; 
fixed4 frag (v2f i) : SV_Target {    
    // sample the texture    
    fixed4 col = tex2D(_MainTex, i.uv) * _MainColor;    
    // 灰度化(加权平均法)不清楚的朋友可以回头看Shader实验室:图片灰度化    
    fixed val = 0.299*col.r+0.578*col.g+0.114*col.b;    
    // 灰度化    
    fixed3 grayScale = fixed3(val, val, val);    
    // _Radius - distance的最小值为0    
    half d = saturate((_Radius - distance(_Position.xyz,i.w_p.xyz))/_Softness);    
    // 当Radius大于distance时,才处理灰度化,否则就为图片原来的颜色    
    col = lerp(fixed4(grayScale,1.0),col * _ColorStrength, d);    
    // apply fog    
    UNITY_APPLY_FOG(i.fogCoord, col);    
    return col; 
}

当Main Color颜色偏暗,整体效果也会偏暗(包括光圈),当ColorStrength强度越高,光圈也会越亮,效果如下:

image.png

但是要实现光圈随着鼠标的移动而移动,只做到这些还远远不够,我们还需要新建一个C#来传递鼠标RaycastHit所处的坐标,并传递至该Shader中,并把Shader中的_Position,_Radius与_Softness移到该C#脚本中,现在Shader中的Properties中的代码如下:

Properties {    
    _MainTex ("Texture", 2D) = "white" {}    
    _MainColor ("Main Color", Color) = (0.5,0.5,0.5,1.0)    
    _ColorStrength("Color Strength", Range(1,8)) = 1 
}

在CGPROGRAM中把_Position,_Radius,_Softness属性声明为uniform(告诉Shaderlab这三个属性值是从外部传入):

uniform float4 _Position; 
uniform half _Radius; 
uniform half _Softness;

新建的C#代码如下:

using System.Collections; 
using System.Collections.Generic; 
using UnityEngine;  
public class RayPosition : MonoBehaviour {     
    private Camera mainCamera;     
    private Ray ray;     
    private RaycastHit hit;     
    private Vector3 mousePos, hitPosition;     
    [Range(0.0f, 100.0f)]     
    public float radius = 11;     
    [Range(0.0f, 100.0f)]     
    public float softness = 4;     
    [Range(10.0f, 50.0f)]     
    public float moveSpeed = 15;     
    // Start is called before the first frame update     
    void Start() {         
        mainCamera = this.GetComponent();     
    }      
    // Update is called once per frame     
    void Update() {         
        mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0);         
        ray = mainCamera.ScreenPointToRay(mousePos);         
        // 通过raycast确认_Position的位置         
        if (Physics.Raycast(ray, out hit)) {             
            hitPosition = Vector3.MoveTowards(hitPosition, hit.point, moveSpeed * Time.deltaTime);             
            Shader.SetGlobalVector("_Position", new Vector4(hitPosition.x, hitPosition.y, hitPosition.z, 1));         
        }         
        Shader.SetGlobalFloat("_Radius", this.radius);         
        Shader.SetGlobalFloat("_Softness", this.softness);     
  } 
}

我们使用Shader.SetGlobalFloat与Shader.SetGlobalVector对_Radius,_Softness以及_Position进行Shader赋值,通过这种方式可以不用去指定材质球,但是这种赋值方式会对使用相同Shader的不同材质传递相同的值,没办法针对特定材质赋予不同的值,要想特定材质赋值就需要使用Material.SetXXX。当然从性能上来讲Shader.SetGlobalXXX的方式会比Material.SetXXX的方式来的更快,但是当前实验正好需要对不同材质使用的相同Shader赋予相同的值,所以Shader.SetGlobalXXX当然是最好的选择。把该C#脚本挂载至Main Camera中,运行场景发现已经实现了鼠标移动,光圈也跟着移动的效果。

image.png

进行到这一步还没最终完成,我们需要做个小小的修饰,细心的朋友会发现文章开头图片中的舞台地面有Emission自发光的光圈效果。所以需要对舞台的图片进行ps处理。

反相处理

对比度处理

色阶处理
image.png
原理就是让图片有像素内容区域的地方更亮,不相关区域更暗。另存处理后的图片后新增一个材质作为舞台地面的专属材质球,Shader还是指定我们之前新建的Shader。此时我们已经有Emission的相关素材了,回到Shader中,对Properties新增Emission相关的图片属性。

Properties {    
    _MainTex ("Texture", 2D) = "white" {}    
    _MainColor ("Main Color", Color) = (0.5,0.5,0.5,1.0)   
    _ColorStrength("Color Strength", Range(1,8)) = 1    
    _EmissionTex ("Emission Texture", 2D) = "white" {}    
    _EmissionColor ("Emission Color", Color) = (0.5,0.5,0.5,1.0)    
    _EmissionStrength("Emission Strength", Range(1,8)) = 1  
}

在struct v2f中新增emission相关uv寄存器

struct v2f {    
    float2 uv : TEXCOORD0;    
    UNITY_FOG_COORDS(1)    
    float4 vertex : SV_POSITION;    
    float4 w_p: TEXCOORD1;    
    float2 uv2: TEXCOORD2; 
};

在vert函数新增代码,TRANSFORM_TEX是Unity内置的uv处理函数,内部原理是v.uv * _EmissionTex_ST.xy + _EmissionTex_ST.zw,这里o.uv2也可以直接等于v.uv,只不过_EmissionTex就没有办法调整它的Tiling与Offset参数值了。


o.uv2 = TRANSFORM_TEX(v.uv, _EmissionTex);

frag函数的处理。

half _EmissionStrength; 
sampler2D _EmissionTex; 
float4 _EmissionTex_ST; 
fixed4 _EmissionColor; 
fixed4 frag (v2f i) : SV_Target {    
    // sample the texture    
    fixed4 col = tex2D(_MainTex, i.uv) * _MainColor;    
    // Emission贴图的颜色设置与颜色增强    
    fixed4 emissionCol = tex2D(_EmissionTex, i.uv2) * _EmissionColor * _EmissionStrength;    
    // 灰度化(加权平均法)不清楚的朋友可以回头看Shader实验室:图片灰度化    
    fixed val = 0.299*col.r+0.578*col.g+0.114*col.b;    
    // 灰度化    
    fixed3 grayScale = fixed3(val, val, val);    
    // _Radius - distance的最小值为0    
    half d = saturate((_Radius - distance(_Position.xyz,i.w_p.xyz))/_Softness);    
    // 当Radius大于distance时,才处理灰度化,否则就为图片原来的颜色    
    col = lerp(fixed4(grayScale,1.0),col * _ColorStrength, d);    
    // 还得加上Emission的颜色值    
    col += lerp(fixed4(grayScale,1.0),emissionCol, d)    
    // apply fog    
    UNITY_APPLY_FOG(i.fogCoord, col);    
    return col; 
}

最后效果。
image.png

本篇的源代码地址为 舞台灯光效果
本篇就到这里了,如果觉得有用,就用微信扫描下面的二维码,关注我们吧!

0 Answers