Unity Shader-后处理:简单的颜色调整(亮度,饱和度,对比度)

Unity Shader-后处理:简单的颜色调整(亮度,饱和度,对比度)

貌似一开始关于shader的讲解都是diffuse,不过,我赶脚后处理貌似更简单,所以第一篇来一发简单后处理,屏幕的简单颜色校正--调整亮度,饱和度,对比度。

一.概念介绍

 
我们在做游戏的时候,虽然现在有了Unity等引擎,不用我们自己处理一些繁琐的东西,但是不管怎么样,最后显示在屏幕上的还是一些RGB的像素信息,了解这些基本的概念,肯定对我们做游戏有更大的帮助。
 

1.颜色模型的概念

 
既然是校正屏幕的颜色,我们有必要了解一下我们要校正的这几个属性的概念。这里就不得不提到我们常用的颜色定义方式,RGB颜色模型和HSV颜色模型。
 

1.1RGB颜色模型

 
RGB颜色模型也就是我们最常用的三原色,红绿蓝。RGB颜色模型在混色时属于加法混色,RGB中每种颜色数值越高,色彩越明亮。RBG为(0,0,0)时为黑色,RGB为(255,255,255)时为白色。计算机在处理颜色信息时一般都采用RGB颜色模型,可以很精确地表示某种颜色。
 

1.2HSV颜色模型

 
RGB颜色模型对于计算机来说很容易计算,但是并不适合人类理解,于是就有了HSV颜色模型,所谓HSV代表的是 Hue(色相),Saturation(饱和度),Value(色调)也有一种说法是HSB模型,B代表的是Brightness(明度)。当然,还有一些说法,比如HSL模型,L代表的是Lightness(亮度)。HSV模型使用一个圆锥形坐标系,顶面对应的V(Value色调)为1,表示颜色较亮,底面的Value为0,表示颜色较暗;而H(Hue色相)是由绕着V轴的旋转角度给定,从红色开始逆时针方向计算,红色对应0度,绿色对应120度,蓝色对应240度;S(Saturation饱和度)由模型的半径来代表,由内向外Saturation逐渐增大,圆心处为0,边缘处为1。下图是HSV颜色模型的图示:
\
 

1.3RGB颜色模型和HSV颜色模型的转化

 
既然两种颜色模型都可以表示颜色,那么两者之间一定有某种转化关系;
 
RGB转化到HSV模型:
假设RGB分别用(r,g,b)代表,其中r,g,b分别为0-1之间的实数;max为r,g,b中最大值,min为最小值;HSV分别用(h,s,v)表示,h为0-360之间实数,而s和v分别为0-1之间的实数,转化关系如下:
\
\
 
 
\
 
HSV模型转化到RGB模型:
\ \
 

2.亮度,饱和度,对比度,灰度的概念

 

2.1亮度

 
图像中RGB值的大小,RGB各个值越大,那么亮度越亮,越小,亮度越暗。比如我们要增加亮度,那么直接增加RGB值即可。
 

2.2饱和度

 
指的是颜色的纯度。一般用彩度除以明度,表征彩色偏离同亮度灰色的程度。简单来说,当颜色越偏向某个值,即越偏离灰度,饱和度越大;当颜色越偏向灰度,饱和度越小。
下面是百度百科关于饱和度的一段定义:
饱和度是指色彩的鲜艳程度,也称色彩的纯度。饱和度取决于该色中含色成分和消色成分(灰色)的比例。含色成分越大,饱和度越大;消色成分越大,饱和度越小。纯的颜色都是高度饱和的,如鲜红,鲜绿。混杂上白色,灰色或其他色调的颜色,是不饱和的颜色,如绛紫,粉红,黄褐等。完全不饱和的颜色根本没有色调,如黑白之间的各种灰色
 

2.3对比度

 
指的是一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,差异范围越大代表对比越大,差异范围越小代表对比越小。一般来说对比度越大,图像越清晰醒目,色彩也越鲜明艳丽;而对比度小,则会让整个画面都灰蒙蒙的。
 

2.4灰度

 
灰度使用黑色调表示物体,即用黑色为基准色,不同的饱和度的黑色来显示图像。 每个灰度对象都具有从 0%(白色)到100%(黑色)的亮度值。
 
了解了一些基本的色彩概念,我们就可以开始进行处理了。首先我们看一下Unity后处理效果的原理。
 

二.Unity屏幕后处理原理

 
所谓屏幕后处理,简单来说就是渲染流水线的最后阶段,对由整个场景生成的一张图片进行处理,比如HDR,运动模糊等等效果,通过屏幕空间的后处理,可以整体改变整个游戏的风格或者效果。所以,要制作屏幕后处理,我们需要两样东西,一个是用于渲染后处理效果的shader,而另一个是我们需要调用这个渲染的脚本,好在Unity为我们提供了相关的功能。


1.OnRenderImage函数

 
该函数在MonoBehaviour中提供,该函数在所有渲染完成后才进行调用,也就是我们上文所说的生成了一张场景图片,函数的原型如下:
?
1
void OnRenderImage(RenderTexture sourceTexture,RenderTexture destTexture);
RenderTexture表示的是渲染纹理,我们渲染物体并不是仅仅渲染在屏幕空间,也可以将物体渲染到特定纹理上,也就是RenderTexture。sourceTexture就是我们渲染的场景图片,而destTexture是目标渲染纹理。我们可以在这个函数中进行相关的后处理效果,使用带有后处理效果shader的材质将场景内容重新渲染。
 

2.Graphics.Blit函数

 
该函数是Graphics的函数,用于将源纹理拷贝到目标纹理,函数原型如下:
?
1
2
3
public static void Blit(Texture source,RenderTexture dest); 
public static void Blit(Texture source,RenderTexture dest, Material mat, int pass = - 1 ); 
public static void Blit(Texture source,Material mat, int pass = - 1 ); 
source是源纹理,dest是目标纹理,当dest为null时,直接将源纹理拷贝到屏幕;mat是拷贝时使用的材质,也就是我们后处理时使用的材质,Unity会使用该材质将源纹理进行处理拷贝给目标纹理,pass是使用的材质shader所使用的pass,我们知道,一个shader中可能有多个pass,使用哪个pass进行处理就可以从该参数传入,当然,默认为-1时表示所有的pass都会执行。
 
在了解了Untiy的后处理流程后,我们就可以着手写我们的亮度对比度饱和度调整后处理了。
 

三.后处理效果代码

 
后处理效果需要两部分,分别是脚本部分和shader部分,我们分别来看一下。
 

1.脚本部分

 
后处理脚本主要做的是两件事,第一件是获取需要的shader,生成材质,第二件是通过OnRenderImage使用材质处理屏幕效果。第一步具有一些普遍性,不管是什么后处理效果,都要有这一步相同的操作,所以我们将该步骤抽离出来,创建一个后处理效果的基类PostEffectBase,代码如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using UnityEngine;
using System.Collections;
 
//非运行时也触发效果
[ExecuteInEditMode]
//屏幕后处理特效一般都需要绑定在摄像机上
[RequireComponent(typeof(Camera))]
//提供一个后处理的基类,主要功能在于直接通过Inspector面板拖入shader,生成shader对应的材质
public class PostEffectBase : MonoBehaviour {
 
     //Inspector面板上直接拖入
     public Shader shader = null ;
     private Material _material = null ;
     public Material _Material
     {
         get
         {
             if (_material == null )
                 _material = GenerateMaterial(shader);
             return _material;
         }
     }
 
     //根据shader创建用于屏幕特效的材质
     protected Material GenerateMaterial(Shader shader)
     {
         if (shader == null )
             return null ;
         //需要判断shader是否支持
         if (shader.isSupported == false )
             return null ;
         Material material = new Material(shader);
         material.hideFlags = HideFlags.DontSave;
         if (material)
             return material;
         return null ;
     }
      
}
然后,我们以后所有的后处理效果脚本都可以继承该类PostEffectBase,就都自动具有了通过shader生成后处理材质的功能。
 
接下来就是我们这一篇的亮度,饱和度,对比度调整的脚本,脚本很简单,主要的功能就在于设置几个参数,覆写OnRenderImage函数后将参数实时传入shader,然后通过Blit函数完成后处理效果,代码如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using UnityEngine;
using System.Collections;
 
//继承自PostEffectBase
public class ColorAdjustEffect : PostEffectBase {
 
     //通过Range控制可以输入的参数的范围
     [Range( 0 .0f, 3 .0f)]
     public float brightness = 1 .0f; //亮度
     [Range( 0 .0f, 3 .0f)]
     public float contrast = 1 .0f;  //对比度
     [Range( 0 .0f, 3 .0f)]
     public float saturation = 1 .0f; //饱和度
 
     //覆写OnRenderImage函数
     void OnRenderImage(RenderTexture src, RenderTexture dest)
     {
         //仅仅当有材质的时候才进行后处理,如果_Material为空,不进行后处理
         if (_Material)
         {
             //通过Material.SetXXX("name",value)可以设置shader中的参数值
             _Material.SetFloat( "_Brightness" , brightness);
             _Material.SetFloat( "_Saturation" , saturation);
             _Material.SetFloat( "_Contrast" , contrast);
             //使用Material处理Texture,dest不一定是屏幕,后处理效果可以叠加的!
             Graphics.Blit(src, dest, _Material);
         }
         else
         {
             //直接绘制
             Graphics.Blit(src, dest);
         }
     }
}
这样,我们的后处理脚本就完成了。涉及到以下几个知识点:
1.可以通过[Range(min,max)]来控制Inspector面板中的值,限定其范围,并提供滑动条控制。
2.OnRenderImage函数每帧渲染完全部内容后执行,我们在每一帧设置Material的各项参数,通过Material.SetXXX("name",value)可以向shader中传递各种参数。
3.各种后处理效果可以叠加,这里的dest并不一定就是屏幕。不过后处理是很耗费性能的,一方面是pixel shader全屏幕overdraw,另一方面,一个rendertexture的内存占用很大,尤其是大分辨率手机上,多个后处理效果可能造成内存耗尽,程序崩溃。
 

2.shader部分

 
终于步入正题了,下面看一下后处理效果的shader。由于后处理效果是对于一个场景的渲染图进行处理,所以vertex shader基本没有什么好说的,大部分后处理都是基于pixel shader。先整理一下思路:
最简单的是亮度,我们可以直接在采样texture后乘以一个系数,达到放大或者缩小rgb值的目的,这样就可以调整亮度了。
其次是饱和度,饱和度是离灰度偏离越大,饱和度越大,我们首先可以计算一下同等亮度条件下饱和度最低的值,根据公式:gray = 0.2125 * r + 0.7154 * g + 0.0721 * b即可求出该值(公式应该是一个经验公式),然后我们使用该值和原始图像之间用一个系数进行差值,即可达到调整饱和度的目的。
最后是对比度,对比度表示颜色差异越大对比度越强,当颜色为纯灰色,也就是(0.5,0.5,0.5)时,对比度最小,我们通过在对比度最小的图像和原始图像通过系数差值,达到调整对比度的目的。代码如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//shader的目录
Shader "Custom/ColorAdjustEffect"
{
     //属性块,shader用到的属性,可以直接在Inspector面板调整
     Properties
     {
         _MainTex ( "Albedo (RGB)" , 2D) = "white" {}
         _Brightness( "Brightness" , Float) = 1
         _Saturation( "Saturation" , Float) = 1
         _Contrast( "Contrast" , Float) = 1
     }
 
     //每个shader都有Subshaer,各个subshaer之间是平行关系,只可能运行一个subshader,主要针对不同硬件
     SubShader
     {
         //真正干活的就是Pass了,一个shader中可能有不同的pass,可以执行多个pass
         Pass
             {
                 //设置一些渲染状态,此处先不详细解释
                 ZTest Always Cull Off ZWrite Off
                 
                 CGPROGRAM
                 //在Properties中的内容只是给Inspector面板使用,真正声明在此处,注意与上面一致性
                 sampler2D _MainTex;
                 half _Brightness;
                 half _Saturation;
                 half _Contrast;
 
                 //vert和frag函数
                 #pragma vertex vert
                 #pragma fragment frag
                 #include "Lighting.cginc"
 
                 //从vertex shader传入pixel shader的参数
                 struct v2f
                 {
                     float4 pos : SV_POSITION; //顶点位置
                     half2  uv : TEXCOORD0;    //UV坐标
                 };
 
                 //vertex shader
                 //appdata_img:带有位置和一个纹理坐标的顶点着色器输入
                 v2f vert(appdata_img v)
                 {
                     v2f o;
                     //从自身空间转向投影空间
                     o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                     //uv坐标赋值给output
                     o.uv = v.texcoord;
                     return o;
                 }
 
                 //fragment shader
                 fixed4 frag(v2f i) : SV_Target
                 {
                     //从_MainTex中根据uv坐标进行采样
                     fixed4 renderTex = tex2D(_MainTex, i.uv);
                     //brigtness亮度直接乘以一个系数,也就是RGB整体缩放,调整亮度
                     fixed3 finalColor = renderTex * _Brightness;
                     //saturation饱和度:首先根据公式计算同等亮度情况下饱和度最低的值:
                     fixed gray = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
                     fixed3 grayColor = fixed3(gray, gray, gray);
                     //根据Saturation在饱和度最低的图像和原图之间差值
                     finalColor = lerp(grayColor, finalColor, _Saturation);
                     //contrast对比度:首先计算对比度最低的值
                     fixed3 avgColor = fixed3( 0.5 , 0.5 , 0.5 );
                     //根据Contrast在对比度最低的图像和原图之间差值
                     finalColor = lerp(avgColor, finalColor, _Contrast);
                     //返回结果,alpha通道不变
                     return fixed4(finalColor, renderTex.a);
                 }
                     ENDCG
         }
     }
     //防止shader失效的保障措施
     FallBack Off
}

 

四.效果展示

 
完成shader和后处理脚本后,我们可以创建一个场景,在场景的MainCamera下挂在上该脚本,然后把ColorAdjustEffect的shader赋给脚本的shader槽,如下图所示:
\
首先将亮度,对比度,饱和度都置为1,场景如下所示:
\
调整亮度的图像效果如下:
\
调整对比度效果如下:
\
调整饱和度的情况如下图:
\
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: #提问者没有完整的问题,无法给出全面的回答,请完善问题# 如果您要问的是如何使用Python和Unity进行Socket通信,那么我可以为您提供一些信息。Socket通信是指通过网络连接在不同的计算机上进行数据传输的过程。Python和Unity可以通过Socket协议实现实时的数据传输和通信。 对于Python和Unity之间的Socket通信,Python负责服务器端,Unity负责客户端。Python服务器将数据发送到Unity客户端,Unity客户端接收并解析数据。最常用的Socket协议是TCP(Transmission Control Protocol)协议,它提供了单一的连接,并将数据包使用特殊的数据结构进行编码和解码。 在开始使用Python和Unity进行Socket通信之前,需要安装Python的Socket模块和Unity的网络功能模块。然后,需要编写Python服务器和Unity客户端的代码,实现数据的发送和接收。 Python Socket模块是Python用于网络编程的核心模块。它提供了内置的套接字对象,可用于创建和操作Socket。在此基础上,可以编写一个Python服务器端程序,并绑定到IP地址和端口上。 Unity客户端使用C#,其自带的网络功能模块可以用于发送和接收数据。在Unity中,需要创建网络Socket连接,并进行连接、发送和接收操作。 总的来说,Python和Unity之间的Socket通信需要使用TCP协议,并需要编写Python服务器和Unity客户端的代码,才能实现数据的实时传输和通信。在实际工程中,还需要考虑数据格式、安全性等问题。 ### 回答2: #Python-Unity-Socket通信:Unity (C#)的实现 Python-Unity-Socket通信可以让Python代码与Unity (C#)代码实现良好的互动。通过Socket通信,Python和Unity都可以发送和接收网络数据,实现各种场景和功能。 实现Python-Unity Socket通信的步骤如下: 1. 在Python中建立Socket服务器(Server),等待Unity连接 2. 在Unity中建立Socket客户端(Client),连接到Python Socket服务器 3. Python和Unity互相发送和接收数据 在Python中,建立Socket服务器的代码如下: ``` import socket host = '127.0.0.1' port = 8888 s = socket.socket() s.bind((host, port)) s.listen(1) conn, addr = s.accept() print("连接地址:" + str(addr)) while True: data = conn.recv(1024).decode() if not data: break print("接收数据:" + data) conn.sendall(data.encode()) conn.close() ``` 在Unity中,建立Socket客户端的代码如下: ``` using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Net.Sockets; using System.Net; using System; public class SocketClient : MonoBehaviour { public string host = "127.0.0.1"; public int port = 8888; private TcpClient client; // Use this for initialization void Start () { try { client = new TcpClient(); client.Connect(host, port); } catch (Exception e) { Debug.Log(e); } string message = "Hello Python"; byte[] data = System.Text.Encoding.UTF8.GetBytes(message); NetworkStream stream = client.GetStream(); stream.Write(data, 0, data.Length); data = new byte[1024]; string responseData = string.Empty; int bytes = stream.Read(data, 0, data.Length); responseData = System.Text.Encoding.UTF8.GetString(data, 0, bytes); Debug.Log("收到服务器消息:" + responseData); stream.Close(); client.Close(); } } ``` 以上代码实现了Unity向Python发送一条消息,并接收Python回传的消息。 Python和Unity之间还可以通过Socket发送和接收其他类型的数据,如音频、视频等。需要注意的是,Python和Unity发送和接收数据的格式需要保持一致,可以使用Json、Protobuf等数据格式来统一。 ### 回答3: # 应该题目描述不太准确,这里按照理解给出回答 python-unity-socket-communication,是指使用Python语言和Unity游戏引擎之间进行网络Socket通信。 具体实现中,需要在Unity中编写C#代码,通过Socket连接Python服务器,实现网络通信。 在Unity中,首先需要使用Socket创建一个客户端连接到Python服务器,并通过该连接向Python服务器发送请求和接收响应。同时,需要编写代码来解析Python服务器返回的数据,以便Unity游戏引擎正确地使用它们。 在Python服务器中,需要编写Socket服务器程序,以便监听来自Unity客户端的请求,并对其做出响应。在响应Unity客户端请求时,需要将Python语言中的数据格式转换为Unity游戏引擎所需的格式。 总之,Python-Unity-Socket通信是一种常用且强大的网络通信方式,可以帮助Unity游戏开发人员高效地与Python服务器进行通信,以实现更为复杂的游戏功能和应用场景。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值