返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >浅谈Unity中的Shader
  • 762
分享到

浅谈Unity中的Shader

2024-04-02 19:04:59 762人浏览 薄情痞子
摘要

目录一、Shader基础知识1.1、什么是Shader1.2、OpenGL的渲染流程1.3、shader的种类1.4、shader的开发语言二、Unity中Shader知识介绍2.1

一、Shader基础知识

1.1、什么是Shader

在讲什么是Shader之前我们先看看下面两段代码 

这里写图片描述

这两段代码实现的功能都是提取 2D 图像上每个像素点的颜色值,第一段代码是用c++写的,在cup上面运行,它需要循环遍历每个像素点,第二段代码是CG代码,在GPU上面运行,它只需要一行代码就能实现同样的功能。GPU是专门用来进行图形处理的,而Shader,就是GPU执行的一段针对3D对象进行操作的程序。

维基百科上对shader的解释是这样

Shader(着色器)应用于计算机图形学领域,指一组供计算机图形资源在执行渲染任务时使用的指令,用于计算图像的颜色或明暗。但近来,它也能用于处理一些特殊效果,或者视频后处理。通俗地说,着色器告诉电脑如何用特有的一种方法去绘制物体。

程序员将着色器应用于图形处理器(GPU)的可编程流水线,来实现三维应用程序。这样的图形处理器有别于传统的固定流水线处理器,为GPU编程带来更高的灵活性和适应性。以前固有的流水线只能进行一些几何变换和像素灰度计算。现在可编程流水线还能处理所有像素、顶点、纹理的位置、色调、饱和度、明度、对比度并实时地绘制图像。着色器还能产生如模糊、高光、有体积光源、失焦、卡通渲染、色调分离、畸变、凹凸贴图、边缘检测、运动检测等效果。

1.2、OpenGL的渲染流程

知道了什么是shader,我们再来了解一下shader的种类,首先我先介绍一下OpenGL的渲染流程

这里写图片描述

上图便是OpenGL的渲染流程,将这个流程简化之后是这样的 

顶点变换 → 图元装配和光栅化 → 片元纹理映射和着色 → 写入帧缓存

在顶点变换和片元着色这两步时,我们就可以对其编程,进行各种操作,其他的部分我们是没法进行编程的。我们的shader就是作用于顶点变换和片元着色这两个部分的。

1.3、shader的种类

知道了shader起作用的地点,我们现在可以了解一下shader的种类了。

shader按管线分类一般分为固定渲染管线与可编程渲染管线。固定渲染管线就是功能固定的管线,比如物体表面光的折射,反射算法是固定无法修改的,我们只能对这些功能进行配置,比如开启或关闭反射效果,雾化效果等。因为这种管线功能固定,无法在程序上对物体细节的表现给予更多更自由的控制,无法达到更多我们想要的画面效果。所以现在的显卡都是可编程渲染管线,也就是曾经那些我们固定无法修改的部门现在可以编程去修改,自由度高了之后,我们也就能实现更多自己想要的特效了。

1.4、shader的开发语言

知道了shader的种类,我在来说说shader的开发语言

HLSL: 主要用于Direct3D。平台:windows

GLSL: 主要用于OpenGL。 平台:移动平台(iOS,安卓),Mac(only use when you target Mac OS X or OpenGL ES 2.0)

CG:与DirectX 9.0以上以及OpenGL 完全兼容。运行时或事先编译成GPU汇编代码。CG比HLSL、GLSL支持更多的平台,Unity Shader采用CG/HLSL作为开发语言。

二、Unity中Shader知识介绍

2.1、shader在GPU的渲染流程

这里写图片描述

进入GPU运算首先进行的是Vertex Processor顶点处理器,这个部分就需要我们使用Vertex Shader顶点着色器,顶点着色器运算的结果会交给Pixel Processor像素处理器,也就是片段处理器,在这个部分我需要为像素处理编写Pixel Shader像素着色器程序,这部分计算完后就输出了最终我们可以用于在屏幕上的颜色信息,我们把它叫做Frame Buffer帧缓冲。帧缓冲存储的是计算机依次显示所要的数据。

2.2、Unity中shader的类型

①Fixed function shader :属于固定渲染管线 Shader, 基本用于高级Shader在老显卡无法显示时的回滚。使用的是ShaderLab语言,语法与微软的FX files 或者NVIDIA的 CgFX类似。

②Vertex and Fragment Shader:最强大的Shader类型,属于可编程渲染管线. 使用的是CG/HLSL语法。

③Surface Shader:Unity3d推崇的Shader类型,使用Unity预制的光照模型来进行光照运算。使用的也是CG/HLSL语法。

我们先了解一下这三种shader的异同点。

相同点: 

①都必须从唯一一个根Shader开始 
②Properties参数部分,作用及语法完全相同 
③具体功能都在SubShader里(Subshader会自上而下运行第一个硬件能支持的) 
④SubShader都可以打标签 
⑤都可以回滚 
⑥都可以处理基本的功能,例如光照漫反射(Diffuse)以及镜面反射(Specular)。但是Vertex and Fragment和Surface都能实现Fixed function实现不了的高级功能,例如基于uv计算的效果等等。

不同点 

①Fixed function shader以及Vertex and Fragment Shader在subshader下面还有pass{}结构,但是Surface Shader,已经将具体内容打包在光照模型了,不能加pass{} 
②Fixed function shader每句代码之后没有分号“;”, 但是V&F shader以及Surface shader每句代码之后都必须加分号“;” 
③核心结构不同 

Fixed function shader的SunShader中的结构为


Material{} 
……
SetTexture[_MainTex]{
    ……
}

Vertex and Fragment Shader的核心结构为


CGPROGRAM
#pragma vertex vert
#pragma fragment frag   
……       
#include "UnityCG.cginc"
ENDCG

Surface Shader的核心结构是


CGPROGRAM
#pragma surface surf Lambert
……
ENDCG

可以看到这三种shader的Subshader内的编码实现是不一样的,这三种shader的结构如下图,他们的不同点都在SubShader里面 

这里写图片描述

因为Unity推荐Surface Shader,所以文章直接分析Surface Shader的用法,其他两种shader就不做过多介绍了。

三、Surface Shader语法

在Unity的项目面板中直接创建一个Stander surface shader,默认生成的代码如下


Shader "Custom/DiffuseShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)       //设置一个默认的颜色值
        _MainTex ("Albedo (RGB)", 2D) = "white" {}  //默认的白色纹理
        _Glossiness ("Smoothness", Range(0,1)) = 0.5  //默认的光泽度
        _Metallic ("Metallic", Range(0,1)) = 0.0    //金属光泽度
    }
    SubShader {
        Tags { "RenderType"="Opaque"}
        LOD 200
 
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
 
        fixed4 _Color;
        sampler2D _MainTex;
        half _Glossiness;
        half _Metallic;
 
 
        struct Input {
            float2 uv_MainTex;
        };
 
 
 
        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

接下来我来介绍一下这段代码

Properties {}

Properties{}是定义着色器属性的,在这里定义的属性将被作为输入提供给所有的子着色器。属性定义的格式如下

_Name(“Display Name”, type) = defaultValue[{options}]

_Name代表的是属性名,如Color,MainTex,Glossiness ,Metallic 等

”Display Name”则是在Inspector中显示的名字

type代表属性:

Color - 一种颜色,由RGBA(红绿蓝和透明度)四个量来定义;

2D - 一张2的阶数大小(256,512之类)的贴图。这张贴图将在采样后被转为对应基于模型UV的每个像素的颜色,最终被显示出来;

Rect - 一个非2阶数大小的贴图;

Cube - 即Cube map texture(立方体纹理),简单说就是6张有联系的2D贴图的组合,主要用来做反射效果(比如天空盒和动态反射),也会被转换为对应点的采样;

Range(min, max) - 一个介于最小值和最大值之间的浮点数,一般用来当作调整Shader某些特性的参数(比如透明度渲染的截止值可以是从0至1的值等);

Float - 任意一个浮点数;

Vector - 一个四维数;

这段默认Properties在Inspector中的显示效果如下 

这里写图片描述

SubShader{}

Tags :tags标签是三种类型的shader都具有的标签,它决定了硬件什么调用该子着色器

Tags标签里面默认的“RenderType”=”Opaque”,是告诉系统应该在渲染非透明物体时调用这个SubShader

“RenderType”=”Transparent”表示在渲染含有透明效果的物体时调用该Sunshader,

Tags里面还有许多其他的我们可选的标签

①.”Queue”:定义渲染顺序。预制的值有这些 

    ”Background”。值为1000。比如用于天空盒。

    ”Geometry”。值为2000。大部分物体在这个队列。不透明的物体也在这里。

    ”AlphaTest”。值为2450。已进行AlphaTest的物体在这个队列。 

    ”Transparent”。值为3000。透明物体。 

    ”Overlay”。值为4000。比如镜头光晕。 

    用户可以定义任意值,比如”Queue”=”Geometry+10”

②“RenderType”:定义渲染类型。预制的值有这些

    ”Opaque”:绝大部分不透明的物体都使用这个; 

    ”Transparent”:绝大部分透明的物体、包括粒子特效都使用这个; 

    ”Background”:天空盒都使用这个; 

    ”Overlay”:GUI、镜头光晕都使用这个; 

③”ForceNoShadowCasting”:定义物体是否有阴影效果

    “true”。表示有阴影

    “false”。表示没有阴影

LOD:Level of Detail的缩写,它表示着色器的细节层次效果。在某些硬件比较差的系统上,我们可以设置一个低一点的值,减少细节的显示。Unity内置shader的LOD值如下

  • VertexLit kind of shaders = 100
  • Decal, Reflective VertexLit = 150
  • Diffuse = 200
  • Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
  • Bumped, Specular = 300
  • Bumped Specular = 400
  • Parallax = 500
  • Parallax Specular = 600

从CGPROGRAM 到ENDCG这一部分就这这个shader的核心内容了


#pragma surface surf Standard fullforwardshadows

这段编译指令声明了我们要写一个Surface Shader,并指定了光照模型。它的写法是这样的


#pragma surface surfaceFunction lightModel [optionalparams]

surface - 声明的是一个表面着色器surfaceFunction - 着色器代码的方法的名字lightModel - 使用的光照模型。

这段代码默认的surfaceFunction为surf,我们可以在源码的底部看到在这儿声明了的surf函数。默认的lightModel为Standard。

下面我先介绍一下lightModel光照模型

  • Lambert:该光照模型能很好的表示粗糙表面的光照,但不能表现出镜面反射高光
  • Toon:最近在游戏中常用的风格之一即是Toon shading(又称 cel shading).这是一种非逼真渲染风格,通过改变了光在一个模型上反射实际情况来给人以手绘的感觉
  • BlinnPhong:仿真镜面反射材料
  • Standard:Unity5中默认的光照模式是Standard, 其引入了 物理渲染 (PBR), 但是与其它光照模型没有什么不同。相比于朗伯反射, PBR提供了一个更加逼真的光线物体作用模型,PBR考虑了材料的物理属性, 比如能量守恒以及光的散射

接下来的这段代码


fixed4 _Color;
sampler2D _MainTex;
half _Glossiness;
half _Metallic;

我们可以发现 _Color,_MainTex,_Glossiness,_Metallic都shader属性的声明,在上面的Properties 中已经声明过了这些属性,但是在这段CG程序,要想访问在Properties中所定义的变量的话,必须使用和之前变量相同的名字再次进行声明,其实就是链接在上面properties中声明的属性。

我再来介绍一下shader中常用的数据类型

3种基本数值类型:float、half和fixed。 
这3种基本数值类型可以再组成vector和matrix,比如half3是由3个half组成、float4x4是由16个float组成。

float:32位高精度浮点数。 
half:16位中精度浮点数。范围是[-6万, +6万],能精确到十进制的小数点后3.3位。 
fixed:11位低精度浮点数。范围是[-2, 2],精度是1/256。 
Sampler2D:2D纹理属性

接下来就是Input结构体


struct Input {
    float2 uv_MainTex;
};

这个结构体和surf函数中的另一个参数inout结构体是相对的,一个代表输入,一个代表输出。我们可以这样理解这两个结构体,你定义输入数据结构(Inputs Struct)、编写自己的Surface函数处理输入、最终输出修改过后的SurfaceOutput。Input其实是需要我们去定义的结构,所以我们可以把所需要参与计算的数据都放到这个Input结构中,传入surf函数使用

默认的Input结构体中有一个uv_MainTex参数,代表了纹理的UV值,我们便可以在surf函数中直接使用这个参数了。

知道了Input的结构体,我们在来看看Output的结构体


struct SurfaceOutput
{
    fixed3 Albedo;  // diffuse color  漫反射的颜色值。
    fixed3 NORMal;  // tangent space normal, if written 法线坐标
    fixed3 Emission;  //自发光颜色
    half Specular;  // specular power in 0..1 range 镜面反射系数
    fixed Gloss;    // specular intensity   光泽系数
    fixed Alpha;    // alpha for transparencies  透明度系数
};

现在我们在来看看surf函数里面的内容,就已经能够看懂了

我们现在在来看看surf函数里面的代码,就能知道里面是什么意思了


void surf (Input IN, inout SurfaceOutputStandard o) {
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;               //将物体显示的漫反射颜色设置成在纹理的颜色值
    o.Metallic = _Metallic;         //将物体显示的金属光泽设置成在properties中定义的光泽
    o.Smoothness = _Glossiness;     //设置物体显示的光滑度
    o.Alpha = c.a;                  //设置物体显示的透明度
}

以上就是浅谈Unity中的Shader的详细内容,更多关于Unity Shader的资料请关注编程网其它相关文章!

--结束END--

本文标题: 浅谈Unity中的Shader

本文链接: https://lsjlt.com/news/129209.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 浅谈Unity中的Shader
    目录一、Shader基础知识1.1、什么是Shader1.2、OpenGL的渲染流程1.3、shader的种类1.4、shader的开发语言二、Unity中Shader知识介绍2.1...
    99+
    2024-04-02
  • Unity Shader实现线框效果的制作步骤
    目录一、首先模型本身需要特殊处理二、编写Shader三、讲解先上图看看效果: 下面详细分享一下制作步骤吧: 一、首先模型本身需要特殊处理 二、编写Shad...
    99+
    2024-04-02
  • 浅谈JVM中的JOL
    目录JOL简介使用JOL分析VM信息使用JOL分析String使用JOL分析数组使用JOL分析自动装箱使用JOL分析引用关系总结JOL简介 JOL的全称是Java Object La...
    99+
    2024-04-02
  • 浅谈Unity脚本生命周期与执行顺序
    目录一、脚本生命周期二、MonoBehavior生命周期图三、脚本执行顺序四、自定义执行顺序一、脚本生命周期 Unity脚本中的常见必然事件如下表所示 ...
    99+
    2024-04-02
  • 浅谈python中的多态
    目录一、多态二、多态性三、鸭子类型一、多态 多态是指一类事物有多种形态,比如动物类,可以有猫,狗,猪等等。(一个抽象类有多个子类,因而多态的概念依赖于继承) import abc class Animal(met...
    99+
    2022-06-02
    python 多态
  • 浅谈Shell中的函数
    函数可以让我们将一个复杂功能划分成若干模块,让程序结构更加清晰,代码重复利用率更高。像其他编程语言一样,Shell也支持函数。Shell函数必须先定义后使用。 1.Shell函数的定义格式 可以带function关键字...
    99+
    2022-06-04
    shell 函数
  • 浅谈Vue中的this.$store.state.xx.xx
    目录Vue this.$store.state.xx.xx获取store中的数据我的项目文件结构vue项目都在什么时候用store.state、$store.state和this.$...
    99+
    2024-04-02
  • 浅谈MySQL中的group by
    目录1、前言 2、准备user表2.1 group by规则2.2 group by使用2.3 having使用2.4 order by与limit2.5 with rol...
    99+
    2024-04-02
  • Unity中怎么利用Shader实现一个3D翻页效果
    本篇文章给大家分享的是有关Unity中怎么利用Shader实现一个3D翻页效果,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。效果图:原理:Shader顶点动画在顶点着色器进行对...
    99+
    2023-06-20
  • 浅谈Node.js中的定时器
    Node.js中定时器的实现 上一篇博文提到,在Node中timer并不是通过新开线程来实现的,而是直接在event loop中完成。下面通过几个JavaScript的定时器示例以及Node相关源码来分析在...
    99+
    2022-06-04
    定时器 浅谈 Node
  • 浅谈python中的占位符
    占位符,顾名思义就是插在输出里站位的符号。我们可以把它理解成我们预定饭店。当我们告诉饭店的时候,饭店的系统里会有我们的预定位置。虽然我们现在没有去但是后来的顾客就排在我们后面。 常见的占位符有三种:   1...
    99+
    2022-06-04
    浅谈 python
  • 浅谈Java中的Queue家族
    目录Queue接口Queue的分类BlockingQueueDequeTransferQueueQueue接口 先看下Queue的继承关系和其中定义的方法: Queue继承自Col...
    99+
    2024-04-02
  • 浅谈Java中FastJson的使用
    FastJson的使用 使用maven导入依赖包 <!--下边依赖跟aop没关系,只是项目中用到了 JSONObject,所以引入fastjson--> <de...
    99+
    2024-04-02
  • 浅谈Redis中的RDB快照
    目录一、概述二、快照怎么用?三、执行 bgsava 快照时,数据能被修改吗?四、RDB 和 AOF 合体一、概述 所谓的快照,就是记录某一个瞬间东西,比如当我们给风景拍照时,那一个瞬...
    99+
    2024-04-02
  • 浅谈MyBatis中@MapKey的妙用
    目录MyBatis @MapKey的妙用背景实现源码分析思考Mybatis @MapKey分析1. MapKey注解有啥功能2. MapKey的源码分析1. MapperMethod...
    99+
    2024-04-02
  • 浅谈一下Spring中的createBean
    目录找到BeanClass并且加载类实例化前实例化Supplier创建对象工厂方法创建对象推断构造方法BeanDefionition 的后置处理实例化后属性填充Aware回调初始化前...
    99+
    2024-04-02
  • 浅谈Java中的内部类
    最近在讲Java中的内部类,感觉内部类实际上对于初学者用得不多,那么内部类简单的说,其实就是在一个类的内部定义的类。按照定义的情况分为:成员内部类,局部内部类,静态内部类,匿名内部类。成员内部类,就是定义一个当作类的成员变量的类。局部内部类...
    99+
    2023-06-02
  • 浅谈JavaScript中的parseInt()的妙用
    起因 写这篇博客的起因是今天在刷leetcode的每日一题,是一道字符串转换整数 (atoi)的题,感兴趣的话可以点击题目名称去看一下具体描述。在我多次debug终于成功提交之后,去...
    99+
    2024-04-02
  • 浅谈Python 中的复数问题
    前言 复习试题时,发现一道复数问题 问题 关于 Python 的复数类型,以下选项中描述错误的是 A复数的虚数部分通过后缀“J”或者“j”来表示 B对于复数 z,可以用 z.real 获得它的实数部分 C对于复数 z...
    99+
    2022-06-02
    Python 复数问题
  • 浅谈Go1.18中的泛型编程
    目录前言以前的Go泛型泛型是什么Go的泛型泛型函数泛型类型类型集合和接口的差异总结前言 经过这几年的千呼万唤,简洁的Go语言终于在1.18版本迎来泛型编程。作为一门已经有了1...
    99+
    2022-06-07
    GO 泛型 泛型编程
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作