【UnityShader入门精要学习笔记】第十七章 表面着色器

在这里插入图片描述
本系列为作者学习UnityShader入门精要而作的笔记,内容将包括:

  • 书本中句子照抄 + 个人批注
  • 项目源码
  • 一堆新手会犯的错误
  • 潜在的太监断更,有始无终

我的GitHub仓库

总之适用于同样开始学习Shader的同学们进行有取舍的参考。


文章目录

  • 表面着色器
  • 表面着色器的一个例子
  • 编译指令
    • 表面函数
    • 光照模型
    • 其他可选参数
  • 两个结构体
    • 数据来源:Input 结构体
    • 表面属性:SurfaceOutput结构体
  • Unity背后做了什么
  • 表面着色器实例分析


浅看了第十八章,感觉没必要写。所以这一章表面着色器就是我们的最终章了,接下来重心将会落实到一些深入引擎和优化技术上。

表面着色器

顶点片元着色器本质上是一种对硬件友好的方式,但是对人类不友好。虽然计算机世界中这样的例子已经比比皆是了。出于拒绝反人类的目的,一种新的着色器表面着色器(Surface Shader) 被加入到Unity中。

表面着色shader包含了3个层次:表面着色器,光照模型和光照着色器

其中表面着色器定义了模型表面的反射率、法线和高光等,光照模型则选择使用的光照模型类型,是半兰伯特?还是BlinnPhong或者其他光照模型?光照着色器则由系统进行实现。

表面着色器大大减少了shader开发的工作量,大多数时候我们只需要和表面着色器打交道,定义一些属性,选择要使用的光照模型即可。

表面着色器实际上就是对顶点片元着色器上的一层抽象,使用表面着色器可以用一种更容易理解的方式编写shader,不需要考虑前向渲染路径还是延迟渲染路径,场景中的光源等等等等要素。


表面着色器的一个例子

要实现一个前向光照渲染的材质,我们要定义光照模型,定义前向渲染Pass,定义阴影pass。总而言之,如果用顶点片元着色器实现会很难,很复杂。

但是现在我们可以使用表面着色器来直接实现:

Shader "Custom/BumpedDiffuse_Copy"
{Properties{_Color("Main Color",Color) = (1,1,1,1)_MainTex("Base",2D) = "white"{}_BumpMap("Normalmap",2D) = "bump"{}}SubShader{Tags{"RenderType" = "Opaque"}LOD 300CGPROGRAM#pragma surface surf Lambert#pragma target 3.0sampler2D _MainTex;sampler2D _BumpMap;fixed4 _Color;struct Input{float2 uv_MainTex;float2 uv_BumpMap;};void surf(Input IN,inout SurfaceOutput o){fixed4 tex = tex2D(_MainTex,IN.uv_MainTex);o.Albedo = tex.rgb * _Color.rgb;o.Alpha = tex.a * _Color.a;o.Normal = UnpackNormal(tex2D(_BumpMap,IN.uv_BumpMap));}ENDCG}Fallback "Diffuse"
}

如此简单就能完成一个光照模型,与顶点片元着色器需要包含到一个特定的Pass块中不同, 表面着色器可以直接且必须写在SubShader块中,Unity会自动生成多个Pass,并在CGPROGRAM块中定义表面着色器的具体代码。


编译指令

在表面着色器中,我们通过编译指令来和Unity进行沟通,编译指令最重要的作用是指示该表面着色器使用的表面函数和光照函数,并设置一些可选参数。表面着色器的CG块中的第一句代码往往就是它的编译指令,编译指令的一般格式如下:

#pragma surface surfaceFunction lightModel [optionalparams]

指定表面函数和光照模型,以及其他的一些可选参数来控制表面着色器的一些行为。

表面函数

表面着色器抽象出了表面这一概念,并包含了表面属性,一个对象的表面属性定义了它的反射率、光滑度、透明度等值。而编译指令中的surfaceFunction就用于定义这些表面属性。surfaceFunction通常就是名为surf的函数(函数名可以任意),它的函数格式是固定的:

void surf(Input IN, inout SurfaceOutput o)
void surf(Input IN, inout SurfaceOutputStandard o)
void surf(Input IN, inout SurfaceOutputStandardSpecular o)

其中输入结构体Input是我们自定义的,用于设置各种表面属性。

输出结构体通常是SurfaceOutput ,SurfaceOutputStandard ,SurfaceOutputStandardSpecular ,这些类型,它们是unity内置的结构体,需要配合不同的光照模型使用。

光照模型

官方自定义光照文档

除了表面函数,我们还需要指定光照函数,光照函数会使用表面函数中设置的各种表面属性来应用某些光照模型。

unity中内置了基于物理的光照模型Standard和StandardSpecular,以及简单的非基于物理的Lambert和BlinnPhong

当然我们也可以定义自己的光照函数,例如用下列函数来定义前线渲染中的光照函数:

// 根据官方文档的代码,Lighting是统一的前缀,后面字符部分才是光照模型的名称
// 一些常用的变量直接按规定属性名定义为函数入参并使用即可
// 用于不依赖视角的光照模型,例如漫反射
half4 Lighting<Name> (SurfaceOutput s,half3 lightDir, half atten);
// 用于依赖视角的光照模型,例如高光反射
half4 Lighting<Name> (SurfaceOutput s,half3 lightDir, half3 virwDir, half atten);

其他可选参数

除了光照模型和表面着色器两个必需参数之外,我们还可以设置一些可选参数

这些参数都在官方文档中记载了

简单的使用可选参数就可以定义一些功能

注意表面着色器只能在内置渲染管线Build-In Pipeline中使用,而URP和HDRP是不能使用的,在URP和HDRP中想要简单的实现Shader需要使用Shader Graph


两个结构体

表面着色器最多支持自定义4种关键的函数:

  • 表面函数(用于设置各种表面性质,如反射率,法线等)
  • 光照函数(定义表面使用的光照模型)
  • 顶点修改函数(修改或传递顶点属性)
  • 最后的颜色修改函数(对最后的颜色进行修改)

那么,这些函数之间的信息传递是如何实现的呢?

一个表面着色器需要使用两个结构体:表面函数的输入结构体Input,以及存储了表面属性的结构体SurfaceOutput

数据来源:Input 结构体

Input结构体包含了许多表面属性的数据来源,因此,它会作为表面函数的输入结构体。Input支持很多内置的变量名,通过这些变量名,我们告诉Unity需要使用的数据信息,例如,在Input结构体种包含了主纹理和法线纹理的采样坐标uv_MainTex和uv_BumpMap。这些采样坐标必须以uv为前缀

因此纹理的采样直接用uv_sample2Dname这种格式定义即可,而其他变量需要根据下表严格定义!

在这里插入图片描述

只需要定义变量即可使用了

除了这些变量之外,如果我们想要自定义变量也可以,就像顶点片元着色器种实现的一样,自定义变量并在顶点修改函数中进行处理,将其传递到surface函数中去。


表面属性:SurfaceOutput结构体

SurfaceOutput就是专门用于存储表面属性的。SurfaceOutput ,SurfaceOutputStandard ,SurfaceOutputStandardSpecular 等,它们作为表面着色器的输出,也作为光照函数的输入。这些结构体的变量都是提前声明好的,直接使用即可。
在这里插入图片描述

剩下的就是光照模型,可以用内置的光照模型,我们也可以自定义光照函数
在这里插入图片描述


Unity背后做了什么

使用表面着色器,我们只需要编译指令、自定义函数和两个结构体就可以生成一个表面着色器。而实际上Unity在背后为表面着色器生成了一系列的顶点/片元着色器

Unity在背后会根据表面着色器生成一个包含了很多Pass的顶点/片元着色器,例如我们设置了不同的渲染路径则会生成对应渲染路径的Pass,我们设置了不同LightMode则会生成不同的对应光照的Pass,若使用了addshadow则会生成ShadowCaster的阴影Pass。
在这里插入图片描述
我们只需在Unity 的表面着色器的面板上点击Show generated code即可生成对应的顶点/片元着色器代码。

在这里插入图片描述
根据上图可以看到,其实表面着色器的过程很简单。我们之前定义的部分都是属于片元着色器中的自定义部分。

Unity对Pass的自动生成过程如下:

(1)直接将表面着色器中CGPROGRAM块部分的代码赋值并解析,这部分代码包括了我们对预编译指令,定义的变量,以及表面函数光照函数等。这些函数和变量将在处理后进行调用

(2)unity分析代码,并生成顶点着色器的输出v2f_surf结构体,用于顶点着色器和片元着色器之间的变量传递。 v2f_surf结构体中的变量是根据我们定义的相应变量生成的,注意变量名称要一模一样。若某些变量在编译时发现未使用,也不会被生成带v2f_surf结构体中。

(3)接着生成顶点着色器vert_surf

  • 若我们自定义了顶点修改函数,则unity会首先调用顶点修改函数来修改顶点数据,或填充自定义的Input结构体中的变量,然后Unity会分析顶点修改函数中修改的数据,并通过Input结构体将修改结果存储到v2f_surf相应的变量中。
  • 将顶点数据计算成一些其他的通用变量,例如顶点坐标,纹理坐标,法线方向,逐顶点光照,光照纹理的采样坐标等。可以通过编译器控制某些变量是否需要计算
  • 最后将v2f_surf传递到下一片元着色器中

(4)生成片元着色器frag_surf

  • 使用顶点着色器传递的v2f_surf结构体变量来填充Input结构体,例如纹理坐标,视角方向等
  • 调用自定义的表面函数填充SurfaceOutput结构体
  • 调用光照函数得到初始的颜色值,如果使用的是内置的Lambert和BlinnPhong光照模型,还会计算动态全局光照
  • 进行其他的颜色叠加,例如若没有使用光照烘焙,还会添加逐顶点光照
  • 最后,如果自定义了最后的颜色修改函数,unity会调用它进行最后的颜色修改

表面着色器实例分析

文中的表面着色器实例分析针对的是表面着色器生成的顶点/片元着色器代码,具体可以详见书本

我们此处简单分析一下表面着色器的代码:

Shader "Unity Shaders Book/Chapter 17/Normal Extrusion" {Properties {_ColorTint ("Color Tint", Color) = (1,1,1,1)_MainTex ("Base (RGB)", 2D) = "white" {}_BumpMap ("Normalmap", 2D) = "bump" {}_Amount ("Extrusion Amount", Range(-0.5, 0.5)) = 0.1}SubShader {Tags { "RenderType"="Opaque" }LOD 300CGPROGRAM// surf - which surface function.// 自定义的光照模型// CustomLambert - which lighting model to use.// 自定义的顶点修改函数// vertex:myvert - use custom vertex modification function.// 自定义的颜色修改函数// finalcolor:mycolor - use custom final color modification function.// 编译选项——生成阴影// addshadow - generate a shadow caster pass. Because we modify the vertex position, the shder needs special shadows handling.// 编译选项——不为deferred/legacy deferred渲染路径生成pass// exclude_path:deferred/exclude_path:prepas - do not generate passes for deferred/legacy deferred rendering path.// 不生成用于全局动态光照的“meta” pass// nometa - do not generate a “meta” pass (that’s used by lightmapping & dynamic global illumination to extract surface information).#pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa#pragma target 3.0fixed4 _ColorTint;sampler2D _MainTex;sampler2D _BumpMap;half _Amount;struct Input {float2 uv_MainTex;float2 uv_BumpMap;};// 自定义顶点修改函数void myvert (inout appdata_full v) {v.vertex.xyz += v.normal * _Amount;}void surf (Input IN, inout SurfaceOutput o) {fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);o.Albedo = tex.rgb;o.Alpha = tex.a;o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));}// 自定义的光照模型half4 LightingCustomLambert (SurfaceOutput s, half3 lightDir, half atten) {half NdotL = dot(s.Normal, lightDir);half4 c;c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);c.a = s.Alpha;return c;}// 自定义的颜色修改函数void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) {color *= _ColorTint;}ENDCG}FallBack "Legacy Shaders/Diffuse"
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/22300.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年手机能做的赚钱软件有哪些?整理了八个手机能做的正规赚钱软件分享

在这个指尖滑动的时代&#xff0c;手机不仅仅是通讯工具&#xff0c;更是我们探索财富的钥匙。你是否曾幻想过&#xff0c;躺在沙发上&#xff0c;轻轻一滑&#xff0c;就能让钱包鼓起来&#xff1f; 今天&#xff0c;就让我们一起来探索那些隐藏在手机里的赚钱秘笈&#xff0c…

使用 Vue 3 和 vue-print-nb 插件实现复杂申请表的打印

文章目录 1&#xff1a;创建 Vue 3 项目2&#xff1a;安装 vue-print-nb 插件3&#xff1a;配置 vue-print-nb 插件4&#xff1a;创建一个复杂的申请表5&#xff1a;使用 ApplicationForm 组件6&#xff1a;运行项目 在开发管理系统或申请表打印功能时&#xff0c;打印功能是一…

光伏无人机踏勘需要使用哪些设备?用到哪些原理?

随着全球能源结构的转型和绿色能源的大力推广&#xff0c;光伏电站的建设和运维正成为能源领域的热点。然而&#xff0c;光伏电站的选址、建设和后期运维过程中&#xff0c;往往面临着地形复杂、设备分散、巡检难度大等挑战。在这一背景下&#xff0c;无人机踏勘技术以其独特的…

qt程序打包成一个exe

首先在release模式下编译然后用windeployqt打包 具体步骤参照我这篇文章&#xff1a; https://blog.csdn.net/weixin_73548574/article/details/134932044 然后使用一个加壳工具&#xff1a;https://enigmaprotector.com/en/downloads.html 下载安装后打开 到此完成&#…

c++题目_P1546 [USACO3.1] 最短网络 Agri-Net

题目背景 Farmer John 被选为他们镇的镇长&#xff01;他其中一个竞选承诺就是在镇上建立起互联网&#xff0c;并连接到所有的农场。当然&#xff0c;他需要你的帮助。 题目描述 FJ 已经给他的农场安排了一条高速的网络线路&#xff0c;他想把这条线路共享给其他农场。为了用…

数仓建模—指标拆解和选取

数仓建模—指标拆解和选取 第一节指标体系初识介绍了什么是指标体系 第二节指标体系分类分级和评价管理介绍了指标体系管理相关的,也就是指标体系的分级分类 这一节我们看一下指标体系的拆解和指标选取,这里我们先说指标选取,其实在整个企业的数字化建设过程中我们其实最…

进程和任务管理器

一、查看和控制进程 1.1ps命令 &#xff08;1&#xff09;ps 命令——查看静态的进程统计信息&#xff08;Processes Statistic&#xff09; PID TTY TIME CMD 1579 pts/1 00:00:00 bash 1730 pts/1 00:00:00 ps PID&#xff1a;进程IDTTY &#xff08;进程id&#xff0…

微信小程序发布遇到的一些问题记录

1.报错组件没有按需导入 在该路径配置微信小程序添加"lazyCodeLoading" : "requiredComponents" "mp-weixin" : { "appid" : "你的appid", "setting" : { "urlCheck" : f…

VS_图片转换点云

文章内容: 通过OpenCV读取图片数据将图片数据转换为点云显示点云保存点云到文件图片转换灰度图显示灰度图文章介绍 代码是用Ai工具生成后在VS上运行没有问题的。 可以参考里面读写PCL文件,PCL的显示等内容。 #include <opencv2/opencv.hpp> #include <pcl/io/pcd_…

ElementUI中date-picker组件,怎么给选择一个月份范围中大写月份改为阿拉伯数组月份(例如:一月、二月,改为1月、2月)

要将 Element UI 的 <el-date-picker> 组件中的月份名称从中文大写&#xff08;如 "一月", "二月"&#xff09;更改为阿拉伯数字&#xff08;如 "1月", "2月"&#xff09;&#xff0c;需要进行一些定制化处理。可以通过国际化&a…

企业微信接入系列-上传临时素材

企业微信接入系列-上传临时素材 文档介绍上传临时素材写在最后 文档介绍 创建企业群发的文档地址&#xff1a;https://developer.work.weixin.qq.com/document/path/92135&#xff0c;在创建企业群发消息或者群发群消息接口中涉及到上传临时素材的操作&#xff0c;具体文档地址…

网络服务DHCP的安装

DHCP的安装 检查并且安装dhcp有关软件包 rpm -qc dhcp #检查是否存在dhcp yum install -y dhcp #进行yum安装查看系统的配置文件 切换到对应目录查看相关文件配置&#xff0c;发现是空目录。 将官方提供的example复制到原配置文件中 cp /usr/share/doc/dhcp-4.2.5/dhcpd.…

Python | 链表的基础操作1

面向对象&#xff1a; “对象”实际上是对现实世界中所存在的事物的一种抽象 人拥有着一些静态的特征&#xff0c;比如身高、体重、性别等&#xff0c;也拥有一些动态的行为&#xff0c;比如吃法&#xff0c;睡觉等&#xff0c;而在计算机世界中&#xff0c;我们将之抽象为一个…

什么是室内外一体化定位

室内外一体化定位是一种技术&#xff0c;它允许在室内外环境中对设备或人员进行连续、无缝的定位跟踪。这种技术结合了多种定位技术的优势&#xff0c;以克服单一技术在室内外环境中可能遇到的局限性。 室内外一体化定位通常涉及以下几种技术&#xff1a; 1. 卫星定位系统&am…

汽车软件单元测试分析

汽车软件单元测试概述 随着汽车技术的不断发展,汽车的功能日益复杂,软件在汽车中的作用也变得越来越重要。汽车嵌入式软件的质量直接关系到汽车的安全性、可靠性和性能表现。在这样的背景下,汽车软件单元测试成为了确保软件质量的关键环节。 汽车嵌入式软件单元测试是指对汽…

手撕C语言题典——相交链表

目录 前言 一&#xff0c;思路 1&#xff09;暴力 2)同步指针 二&#xff0c;代码实现 前言 依旧是力扣上的一道题&#xff0c;有许多新思路提供给我们 160. 相交链表 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/intersection-of-two-linked-li…

Go中字符串转成byte数组,会发生内存拷贝吗?

引言 在Go语言中&#xff0c;字符串和字节切片是两种常见的数据类型&#xff0c;它们在内存中的表现和操作方式有着本质的不同。字符串是不可变的&#xff0c;而字节切片则是可变的。在日常开发中&#xff0c;我们经常需要在这两种类型之间进行转换。那么&#xff0c;当字符串…

c#入门详解:接口详解

接口&#xff08;interface&#xff09; 抽象类中的抽象方法只规定了不能是 private 的&#xff0c;而接口中的“抽象方法”只能是 public 的。这样的成员访问级别就决定了接口的本质&#xff1a;接口是服务消费者和服务提供者之间的契约。既然是契约&#xff0c;那就必须是透…

Message passing mechanism (消息传递机制)

objc_msgSend 是 Objective-C 运行时系统中的一个核心函数&#xff0c;用于实现消息传递机制。在 Objective-C 中&#xff0c;方法调用实际上是消息传递的过程&#xff0c;当你在代码中调用一个方法时&#xff0c;编译器会将其转换为 objc_msgSend 函数的调用。 objc_msgSend …

构建体育直播平台源码:深度解析数据分析模块的核心展示内容

在现代的体育直播平台中&#xff0c;数据分析展示已经成为不可或缺的一部分。如下参考借助“东莞梦幻网络科技”提供的体育直播源码&#xff0c;打造的平台&#xff0c;并通过表格形式为用户列出以下数据分析内容&#xff1a; 1、积分排名&#xff1a;反映了各支队伍在赛季中的…