【从UnityURP开始探索游戏渲染】专栏-直达

介绍与发展历史

Cubemap(立方体贴图)是一种由六个独立的正方形纹理组成的集合,它将多个纹理组合起来映射到一个单一纹理。Cubemap包含6个2D纹理,每个2D纹理代表立方体的一个面,形成一个有贴图的立方体。

Cubemap技术起源于早期的3D图形学,最初用于实现天空盒效果。随着硬件性能的提升和图形API的发展,Cubemap逐渐被广泛应用于环境映射、反射和折射等高级渲染效果中。

应用领域

Cubemap在Unity中主要有以下应用场景:

  • 环境反射‌:用于模拟金属、玻璃等具有反射属性物体的反射效果
  • 天空盒‌:创建环境背景,提供场景的全局光照信息
  • 折射效果‌:模拟透明材质的折射现象
  • 全局光照‌:作为间接光照的来源之一

采样反射信息原理

基本原理

Cubemap采样的核心原理是使用方向向量进行索引和采样。想象一个1×1×1的单位立方体,中心位于原点。当从原点发出一个方向向量时,该向量会与立方体的某个面相交,交点处的纹理就是采样结果。

反射计算原理

  • 反射效果的计算过程如下:
  • 计算相机指向物体表面点的向量(视线向量)
  • 根据表面法线计算反射向量
  • 使用反射向量在Cubemap中进行采样(URP中使用宏SAMPLE_TEXTURECUBE实现采样,这个宏实际调用PLATFORM_SAMPLE_TEXTURECUBE宏来处理不同图形API的采样方法,下面有详细讲原理讲解。)
  • 将采样颜色与物体本身的颜色混合

数学上,反射向量R可以通过以下公式计算:

R = I - 2 * dot(N, I) * N

其中I是入射向量(视线向量取反),N是表面法线。

PLATFORM_SAMPLE_TEXTURECUBE的实现原理

PLATFORM_SAMPLE_TEXTURECUBE是Unity URP中用于跨平台Cubemap采样的关键宏,它封装了不同图形API(Direct3D、OpenGL、Metal)下Cubemap采样的实现差异,为开发者提供统一的采样接口。

数学原理基础

Cubemap是一个由6个2D纹理组成的立方体,每个面代表一个方向(±X, ±Y, ±Z)。使用反射向量作为方向索引,可以获取立方体相应位置的纹理颜色

1. 方向向量确定采样面

Cubemap采样基于3D方向向量,通过以下步骤确定采样面:

数学表达式:

主面 = max(|x|, |y|, |z|)

if (主面 == |x|)

if (x > 0) → +X面

else → -X面

else if (主面 == |y|)

if (y > 0) → +Y面

else → -Y面

else

if (z > 0) → +Z面

else → -Z面

## **2. 方向向量到UV坐标转换**

确定采样面后,将3D方向向量转换为2D UV坐标的数学过程:

对于+X面(右面):

u = 0.5 * (1 - (z / |x|))

v = 0.5 * (1 - (y / |x|))

对于-Y面(下面):

u = 0.5 * (1 - (x / |y|))

v = 0.5 * (1 + (z / |y|))

其他面的转换类似,但需要考虑不同面的坐标系差异。

## **URP中的具体实现**

### **PLATFORM_SAMPLE_TEXTURECUBE宏定义**

在URP Core.hlsl中,该宏通常定义为:

```c
hlsl
#define PLATFORM_SAMPLE_TEXTURECUBE(textureName, samplerName, coord3) \
SampleTexture(textureName, samplerName, coord3)

实际采样函数会根据平台不同而有所区别,但对外提供统一接口。

反射向量计算示例

在URP Shader中计算反射向量并采样Cubemap的完整过程:

  • 计算视线向量(从表面点到相机):
hlsl
float3 viewDir = GetWorldSpaceViewDir(positionWS);
  • 计算反射向量:
hlsl
float3 reflectDir = reflect(-viewDir, normalWS);
  • 采样Cubemap:
hlsl
float4 cubemapColor = PLATFORM_SAMPLE_TEXTURECUBE(_Cubemap, sampler_Cubemap, reflectDir);
  • 解码HDR颜色(如果需要):
hlsl
float3 reflection = DecodeHDREnvironment(cubemapColor, _Cubemap_HDR);
  • 混合反射颜色与表面颜色:
hlsl
float3 finalColor = lerp(diffuseColor, reflection, _ReflectAmount);

不同图形API的实现差异

Direct3D

  • 面顺序:+X, -X, +Y, -Y, +Z, -Z
  • UV坐标系:左上角为(0,0),右下角为(1,1)
  • 需要特别注意Y轴的朝向

OpenGL

  • 面顺序:+X, -X, +Y, -Y, +Z, -Z
  • UV坐标系:左下角为(0,0),右上角为(1,1)
  • 通常需要翻转Y轴坐标

Metal

  • 使用MTLTextureTypeCube类型
  • 面顺序与Direct3D类似
  • 采样时需要考虑坐标系转换

数学示例:具体采样过程

假设有一个方向向量(0.5, -0.3, 0.8),计算其在Cubemap中的采样位置:

  • 确定主分量:

    • |x|=0.5, |y|=0.3, |z|=0.8 → 主分量为z
  • 确定采样面:
    • z=0.8 > 0 → +Z面
  • 计算UV坐标:
    • u = 0.5 * (1 + (x/|z|)) = 0.5 * (1 + (0.5/0.8)) ≈ 0.8125
    • v = 0.5 * (1 - (y/|z|)) = 0.5 * (1 - (-0.3/0.8)) ≈ 0.6875
  • 在+Z面纹理上采样(0.8125, 0.6875)处的颜色

性能优化考虑

  • 粗糙度与Mipmap‌:根据表面粗糙度选择适当的Mipmap级别,粗糙表面使用更高层级的Mipmap

    float perceptualRoughness = 1.0 - _Smoothness;
    float mip = PerceptualRoughnessToMipmapLevel(perceptualRoughness);
    float4 envColor = SAMPLE_TEXTURECUBE_LOD(_Cubemap, sampler_Cubemap, reflectDir, mip);

URP中的PLATFORM_SAMPLE_TEXTURECUBE宏通过统一这些复杂操作,使开发者能够专注于材质效果本身,而无需关心底层平台差异

实现示例

以下是一个简单的反射Cubemap的Shader实现:

  • 定义Shader属性,包括颜色、反射颜色、反射强度和Cubemap纹理

  • 顶点着色器计算世界空间的位置、法线、视线方向和反射方向

  • 片元着色器计算环境光、漫反射和反射颜色

  • 使用lerp函数混合漫反射和反射颜色,控制反射强度

  • 最终输出混合后的颜色

  • URPReflection.shader

    Shader "Custom/URPReflection"
    {
    Properties
    {
    _BaseColor("Base Color", Color) = (1,1,1,1)
    _ReflectColor("Reflection Color", Color) = (1,1,1,1)
    _ReflectAmount("Reflect Amount", Range(0,1)) = 0.5
    _Cubemap("Reflection Cubemap", Cube) = "_Skybox" {}
    _Smoothness("Smoothness", Range(0,1)) = 0.5
    } SubShader
    {
    Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } HLSLINCLUDE
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes
    {
    float4 positionOS : POSITION;
    float3 normalOS : NORMAL;
    }; struct Varyings
    {
    float4 positionHCS : SV_POSITION;
    float3 positionWS : TEXCOORD0;
    float3 normalWS : TEXCOORD1;
    float3 viewDirWS : TEXCOORD2;
    float3 reflectDir : TEXCOORD3;
    }; CBUFFER_START(UnityPerMaterial)
    half4 _BaseColor;
    half4 _ReflectColor;
    half _ReflectAmount;
    half _Smoothness;
    CBUFFER_END TEXTURECUBE(_Cubemap);
    SAMPLER(sampler_Cubemap); Varyings vert(Attributes IN)
    {
    Varyings OUT; // 顶点变换
    OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
    OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz);
    OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); // 计算视线方向(从表面点到相机)
    OUT.viewDirWS = GetWorldSpaceViewDir(OUT.positionWS); // 计算反射方向
    float3 viewDir = normalize(OUT.viewDirWS);
    OUT.reflectDir = reflect(-viewDir, normalize(OUT.normalWS)); return OUT;
    } half4 frag(Varyings IN) : SV_Target
    {
    // 标准化法线和反射方向
    float3 normalWS = normalize(IN.normalWS);
    float3 reflectDir = normalize(IN.reflectDir); // 计算漫反射光照
    Light light = GetMainLight();
    float3 lightDir = normalize(light.direction);
    float NdotL = saturate(dot(normalWS, lightDir));
    half3 diffuse = _BaseColor.rgb * light.color * NdotL; // 采样Cubemap
    half perceptualRoughness = 1.0 - _Smoothness;
    half mip = PerceptualRoughnessToMipmapLevel(perceptualRoughness);
    half4 cubemapColor = SAMPLE_TEXTURECUBE_LOD(_Cubemap, sampler_Cubemap, reflectDir, mip);
    half3 reflection = DecodeHDREnvironment(cubemapColor, unity_SpecCube0_HDR) * _ReflectColor.rgb; // 混合漫反射和反射
    half3 color = lerp(diffuse, reflection, _ReflectAmount); return half4(color, 1.0);
    }
    ENDHLSL
    }
    }

Cubemap生成方法

在Unity中可以通过以下步骤生成Cubemap:

  • 创建空物体作为观察位置
  • 在Project视图右键创建Legacy/Cubemap
  • 使用脚本调用Camera.RenderToCubemap方法生成
  • 确保Cubemap勾选Readable选项以便脚本读取

Cubemap技术为Unity中的环境反射和高级材质效果提供了强大的支持,通过合理使用可以显著提升场景的真实感和视觉质量


【从UnityURP开始探索游戏渲染】专栏-直达

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,)

【光照】Unity如何在Cubemap中采样反射信息?的更多相关文章

  1. 如何在.xml中配置Servlet信息

    在编写好servlet文件后需要在web.xml文件下配置servlet,才能使servlet在服务器上运行.基本配置方式如下所示 <context-param> <param-na ...

  2. 在c++中实现反射的初步想法

    最近在思考如何在c++中实现反射.事情的起因是这样的:我们服务器是用c++开发的,如果需要写一些测试用的GM指令的话,需要编写完GM代码后重新编译并且重启进程,工序繁琐且比较耗时.因此就有了想用脚本( ...

  3. 关于如何在 Unity 的 UI 菜单中默认创建出的控件 Raycast Target 属性默认为 false

    关于如何在 Unity 的 UI 菜单中默认创建出的控件 Raycast Target 属性默认为 false 我们在 Unity 中通过 UI 菜单创建的各种控件,比如 Text, Image 等, ...

  4. [Unity c#]c#中的反射

    什么是反射 在.NET中的反射也可以实现从对象的外部来了解对象(或程序集)内部结构的功能,哪怕你不知道这个对象(或程序集)是个什么东西,另外.NET中的反射还可以运态创建出对象并执行它其中的方法. 反 ...

  5. 低差异序列 (low-discrepancy sequences)之Hammerysley在半球中采样点方法的介绍

    半球上的Hammersley 源作者:Holger Dammertz 一组关于如何在2D中使用Hammersley点集以在着色器程序中快速实用地生成半球方向的笔记.如果你发现任何错误或有意见,不要犹豫 ...

  6. C# Unity游戏开发——Excel中的数据是如何到游戏中的 (二)

    本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (一) 上个帖子主要是讲了如何读取Excel,本帖主要是讲述读取的Excel数据是如何序列化成二进制的,考虑到现在在手游中 ...

  7. 如何在ROS中使用PCL—数据格式(1)

    在ROS中点云的数据类型 在ROS中表示点云的数据结构有: sensor_msgs::PointCloud      sensor_msgs::PointCloud2     pcl::PointCl ...

  8. Java中的反射(1)

    Reflection in Java 反射到底是什么呢,我被问到的时候其实也没办法很好的回答这个问题,翻一翻博客,然后逐条讲解.今天干脆就整合一下,免得以后还要去翻. 首先讲一下Java是如何在运行时 ...

  9. 如何在Python中加速信号处理

    如何在Python中加速信号处理 This post is the eighth installment of the series of articles on the RAPIDS ecosyst ...

  10. 我是如何在SQLServer中处理每天四亿三千万记录的

    首先声明,我只是个程序员,不是专业的DBA,以下这篇文章是从一个问题的解决过程去写的,而不是一开始就给大家一个正确的结果,如果文中有不对的地方,请各位数据库大牛给予指正,以便我能够更好的处理此次业务. ...

随机推荐

  1. 前端开发系列080-Node篇之cnpm

    本文输出Node中另一款包管理工具CNPM相关的内容,包括基本情况和简单使用. 一.简单介绍 CNPM是阿里的Node团队开发的一款NPM工具,由蚂蚁金服Node技术专家苏千(袁锋·Node技术专家) ...

  2. 前端开发系列015-基础篇之Javascript面向对象(四)

    一.类型检查对比 在JavaScript代码中经常需要对变量(数据)进行类型的检查,譬如检查某个变量是否是字符串.某个对象是否是数组等,这里简单总结常用了四种进行类型检查的方式. > ① **t ...

  3. Codeforces Round #669 ABC 题解

    A. Ahahahahahahahaha 题意:给个一个偶数长度的01序列,要求删除不超过2/n个元素使得奇数位和等于偶数位和. 思路:注意到题目给的提示,只有0和1,且为偶数长度. 那么对和有贡献的 ...

  4. How to fix ‘ioctl(SIOCSIWMODE) failed: Device or resource busy ‘ problem. wpa问题解决方法

    一个简单粗暴的方法 killall wpa_* 原因是开启了多个wpa服务导致的竞争关系. 可以自己看一下 ps -aux | grep "wpa*" 显示所有的 wpa项目 然后 ...

  5. 谷云科技 iPaaS 成功入选Gartner 2025 API 管理市场指南

    近日,国际权威咨询机构Gartner发布了2025中国API管理市场指南<Market Guide for API Management,China>,谷云科技凭借其卓越的API管理平台成 ...

  6. 企业IT部门在集成类项目中扮演的角色

    随着企业信息化建设的不断深入,集成类项目成为企业发展的重要支撑.这类项目涉及场景多.系统杂.实施牵扯广.周期长,对技术和管理的要求极高.企业IT部门(信息化部门)作为企业的技术核心和纽带,对业务需求. ...

  7. ETL快速拉取物流信息

    我国作为世界第一的物流大国,但是在目前的物流信息系统还存在着几大的痛点.主要包括以下几个方面: 数据孤岛:有些物流企业各个部门之间的数据标准不一致,难以实现数据共享和协同,容易导致信息孤岛. 操作繁琐 ...

  8. Unity微信小游戏小窗口模式点击适配

    1. 问题描述 项目使用UGUI方案,以点击交互为主. 微信unity方案本身只能调PixelRatio,不能直接调整Unity的分辨率,(还没有测试过"自适应屏幕尺寸"会怎么样) ...

  9. SciTech-BigDataAIML-LLM-Generative model

    https://statproofbook.github.io/D/gm Definition: Generative model Index: The Book of Statistical Pro ...

  10. SciTech-Theory-Phenomeon(Problem: Conditions+Process+Outcomes with Probability)->Experience->Concept(Natural+Common part of experiences)-> Principle(invest+research) -> Definition -> Theory -> Axiom

    SciTech Layers: Objective: Problem/Phenomeon: a objective Process and its Outcomes, and Conditions. ...