Cocos Creator 2d光照
godot游戏引擎是有2d光照的,用起来感觉还是很强大的,不知道他是怎么搞的,有时间看看他们怎么实现的。
之前一直以为cocos社区里面没有2d光照的实现,偶然看到2d实现的具体逻辑,现在整理如下,
一:实现原理
这里实现的2d光源是类似聚光灯的效果,是有一个衰减过程的,具体怎么个衰减法,还得用到我们学的数学知识就是线性衰减 y = -x + b,就是用它来模拟光照的衰减效果的,在光照半径范围内衰减的时候是一个值(根据距离进行衰减,该值的意义是对光照效果的贡献值),大于光照半径范围 光照贡献值急速的变为0.
1:需要哪些参数
模拟光照的参数,需要一个光源照射的半径范围r,光源锥形的角度r1,光源的强度indensity,光源的颜色 color,光源世界坐标,用来计算物体距离光源大小,以此来计算光照效果。
properties:alphaThreshold: { value: 0.5 }light_normal: { value: white }light_worldpos: { value: [255, 255, 255, 255], editor: { type: vec4 } } light_ambientColor: { value: [127, 127, 127, 127], editor: { type: color } }light_lightColor: { value: [255, 255, 255, 255], editor: { type: color } } light_radius: { value: 10.0 }light_halfRadius: { value: 0.5 }light_brightness: { value: 1.0 }
uniform Constant {// 环境光模拟白天和黑夜vec4 light_ambientColor;// 光源颜色vec4 light_lightColor;// 光源世界坐标vec4 light_worldpos;// 光源半径float light_radius;// 光源角度半径 决定了光源锥形区域的宽度float light_halfRadius;// 光源的亮度float light_brightness;float light_unused;};
光源的世界坐标可以通过外部脚本传入,定义一个节点挂在Light脚本来控制光源的世界坐标:
import { _decorator, Component, Node, Sprite, math, UITransform, Label, Vec2, Vec3, Vec4, Camera, view, Material, Texture2D, renderer, color, Color } from 'cc';
import { EDITOR } from 'cc/env';
const { ccclass, property, executeInEditMode } = _decorator;@ccclass('Light')
@executeInEditMode
export class Light extends Component {@property([Node])bodys_normal: Node[] = [];@property([Node])bodys: Node[] = [];@property(Material)eff: Material = null!;@property(Material)eff_normal: Material = null!;onLoad() {}start() {this.updateLight();}update() {this.updateLight();}getwpos(node2d: Node) {return node2d.worldPosition;}updateBody(target, lightPos) {// 更新uniformlet spr = target.getComponent(Sprite);// 灯光位置spr.getSharedMaterial(0).setProperty('light_worldpos', new Vec4(lightPos.x, lightPos.y, lightPos.z, 1));}updateLight() {// 光源位置let lightPos = this.getwpos(this.node)for (var idx in this.bodys_normal) {let node = this.bodys_normal[idx];if (null == node) return;this.updateBody(node, lightPos);}for (var idx in this.bodys) {let node = this.bodys[idx];if (null == node) return;this.updateBody(node, lightPos);}}
}
2:具体的一些细节
想要让光源产生效果,需要单独的给每个图片加上一个单独的材质,这样才能控制颜色的输出,这里使用时最新版本(3.8.x)的shader的结构:
// Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
CCEffect %{techniques:- passes:- vert: light-vs:vertfrag: light-fs:fragdepthStencilState:depthTest: falsedepthWrite: falseblendState:targets:- blend: trueblendSrc: src_alphablendDst: one_minus_src_alphablendDstAlpha: one_minus_src_alpharasterizerState:cullMode: noneproperties:alphaThreshold: { value: 0.5 }light_normal: { value: white }light_worldpos: { value: [255, 255, 255, 255], editor: { type: vec4 } } light_ambientColor: { value: [127, 127, 127, 127], editor: { type: color } }light_lightColor: { value: [255, 255, 255, 255], editor: { type: color } } light_radius: { value: 10.0 }light_halfRadius: { value: 0.5 }light_brightness: { value: 1.0 }
}%CCProgram light-vs %{precision highp float;#include <builtin/uniforms/cc-global>#if USE_LOCAL#include <builtin/uniforms/cc-local>#endif#if SAMPLE_FROM_RT#include <common/common-define>#endifin vec3 a_position;in vec2 a_texCoord;in vec4 a_color;out vec4 color;out vec2 uv0;out vec4 object_position;vec4 vert () {vec4 pos = vec4(a_position, 1);// 不适用mvp矩阵计算成世界坐标,因为如果屏幕是横屏的时候,转成世界坐标后,x轴会出现拉伸。// 这里使用的是UI的坐标系,参考light.ts获取世界坐标的代码。object_position = pos;#if USE_LOCALpos = cc_matWorld * pos;#endif#if USE_PIXEL_ALIGNMENTpos = cc_matView * pos;pos.xyz = floor(pos.xyz);pos = cc_matProj * pos;#elsepos = cc_matViewProj * pos;#endifuv0 = a_texCoord;#if SAMPLE_FROM_RTCC_HANDLE_RT_SAMPLE_FLIP(uv0);#endifcolor = a_color;return pos;}
}%CCProgram light-fs %{precision highp float;#include <builtin/internal/embedded-alpha>#include <builtin/internal/alpha-test>in vec4 color;in vec4 object_position;#if USE_TEXTUREin vec2 uv0;#pragma builtin(local)layout(set = 2, binding = 11) uniform sampler2D cc_spriteTexture;#endif// 是否使用2d法线#if USE_2D_NORMALuniform sampler2D light_normal;#endif#if USE_2D_LIGHTuniform Constant {// 环境光模拟白天和黑夜vec4 light_ambientColor;// 光源颜色vec4 light_lightColor;// 光源世界坐标vec4 light_worldpos;// 光源半径float light_radius;// 光源角度半径 决定了光源锥形区域的宽度float light_halfRadius;// 光源的亮度float light_brightness;float light_unused;};/*** 亮度计算, 按照距离远近衰减, 采取内外光圈叠加方式, 按照世界坐标计算 (0.0 ~ 1.0)* @param dist 距离 (0.0 ~ 1.0)* @param cutoff_r 外光圈半径 (> 0.0) 光源的截止半径 超过这个半径区域不再受到关照的影响* @param half_r 内光圈半径, 使用cutoffRadius的半径占比 (0.0 ~ 1.0) 光源的角度半径,决定了光源锥形区域的宽度*/float light_bright(float dist, float cutoff_r, float half_r) {// 截距float intercept = cutoff_r * half_r;// dx_1 = 1 / (2 * intercept) => y = 1 / 2x; 双曲线 近处float dx_1 = 0.5 / intercept;// dx_2曲线和dx_1曲线对称,对称中心是(cutoff_r / 2,1 / cutoff_r) 远处float dx_2 = 0.5 / (cutoff_r - intercept);float offset = 0.5 + intercept * dx_2;// 近处 慢慢衰减float falloffTermNear = clamp((1.0 - dist * dx_1), 0.0, 1.0);// 远处 远离光源的时候迅速减小到0float falloffTermFar = clamp((offset - dist * dx_2), 0.0, 1.0);// 当dist > intercept 的时候 => 1 dist < intercept => 0float falloffSelect = step(intercept, dist);// 计算光源对某一个点的照明贡献 距离衰减因子 dist < intercept => fallofftTermNear 近距离因子 dist > intercept => 远距离因子float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;return falloffTerm;}/*** 计算灯光的颜色值* @param dist 物体距离光源的距离, 世界单位 (> 0.0)* @param radius 光源半径,世界单位 (> 0.0)*/vec3 light_diffuse (float dist, float radius) { // 计算像素点所在光圈位置的亮度float falloffTerm = light_bright(dist, radius, light_halfRadius);// falloffTerm 为亮度值, light_lightColor 为灯光颜色return falloffTerm * vec3(light_lightColor);}/*** 计算光照颜色* @param object_position 物体坐标, 世界坐标* @param object_vertex_normal 顶点的法线向量, 归一化*/vec3 light_color(vec3 col) {// 计算光线方向, 这个方式不能直接用,打个比方纹理是正方形的,而世界坐标可能是长方形的(GL的坐标固定在-1.0到1.0之间, 而屏幕不一定是正方形)vec4 object_direction = object_position - light_worldpos;// 计算物体与灯光的距离float object_dist = length(vec3(object_direction));// 开启这个可以测试// object_dist = length(uv0 - 0.5);// 计算物体与灯光的的距离, 占用直径的百分比float object_dist_normal = object_dist / (light_radius * 2.0);// 获取灯光漫反射颜色vec3 diffuse = light_diffuse(object_dist_normal, light_radius);#if USE_2D_NORMAL// 获取法向量vec3 normal = texture(light_normal, uv0).rgb;normal = normal * 2.0 - 1.0;// 计算光照反射系数,向量点积float normalDot = max(0.0, dot(normal, -normalize(vec3(object_direction.x, object_direction.y, -60))));// 反射光 * 法向量衰减 + 环境光 return col * (diffuse * light_brightness * normalDot + vec3(light_ambientColor));#else// 反射光 * 法向量衰减 + 环境光 (没有法线的情况下需要 0.5 衰减)return col * (diffuse * light_brightness + vec3(light_ambientColor));#endif }/*** 计算光照颜色* @param object_position 物体坐标, 世界坐标* @param object_vertex_normal 顶点的法线向量, 归一化*/vec4 light_dist() {}#endifvec4 frag () {vec4 o = vec4(1, 1, 1, 1);#if USE_TEXTUREo *= CCSampleWithAlphaSeparated(cc_spriteTexture, uv0);#if IS_GRAYfloat gray = 0.2126 * o.r + 0.7152 * o.g + 0.0722 * o.b;o.r = o.g = o.b = gray;#endif#endifo *= color;ALPHA_TEST(o);#if USE_2D_LIGHTreturn vec4(light_color(vec3(o)), o.a);#elsereturn o;#endif}
}%
主要的核心方法:light_bright,这个方法是用来计算光照贡献值的。
其中intercept = cutoff_r * half_r 是设定了一个阙值,当dist < intercept的时候用什么样的衰减方式,当dist > intercept的时候用什么样的衰减方式,
那么 float dx_1 = 0.5 / intercept 该怎么理解呢,这个得结合falloffTermNear,falloffTermFar来理解,就好比v = s / t,总的衰减总数是1,前半部分(dist < intercept)占了0.5,那么后半部分就是1 - 0.5,我还是画一张图来理解吧,每衰减 1需要消耗的值就是 dx_1,这个dx_1可以理解为衰减的速率,也就是下面代码中出现的斜率,也就是衰减速度。那么dx_1讲清楚了,自然而然dx_2你也是理解的

这里还有一个点就是offset什么意思,offset指的就是当dist = intercept的时候怎么保证两个衰减过程衔接的非常自然呢,咱们可以列一个公式看看
1 - dist * dx_1 = offset - dist * dx_2,很容易我们解方程就能够知道 offset = 0.5 + intercept * dx_2;
最后根据dist来计算对光照的影响程度就可以了,
/*** 亮度计算, 按照距离远近衰减, 采取内外光圈叠加方式, 按照世界坐标计算 (0.0 ~ 1.0)* @param dist 距离 (0.0 ~ 1.0)* @param cutoff_r 外光圈半径 (> 0.0) 光源的截止半径 超过这个半径区域不再受到关照的影响* @param half_r 内光圈半径, 使用cutoffRadius的半径占比 (0.0 ~ 1.0) 光源的角度半径,决定了光源锥形区域的宽度*/float light_bright(float dist, float cutoff_r, float half_r) {// 截距float intercept = cutoff_r * half_r;// dx_1 = 1 / (2 * intercept) => y = 1 / 2x; 双曲线 近处float dx_1 = 0.5 / intercept;// dx_2曲线和dx_1曲线对称,对称中心是(cutoff_r / 2,1 / cutoff_r) 远处float dx_2 = 0.5 / (cutoff_r - intercept);// 用在两种衰减过程中的阙值出的矫正保证颜色渐变的连贯 计算过程是 1 - dist * dx_1 = offset - dist * dx_2 可以反算出来 offset = 0.5 + intercept * dx_2float offset = 0.5 + intercept * dx_2;// 近处 慢慢衰减 线性衰减float falloffTermNear = clamp((1.0 - dist * dx_1), 0.0, 1.0);// 远处 远离光源的时候迅速减小到0float falloffTermFar = clamp((offset - dist * dx_2), 0.0, 1.0);// 当dist > intercept 的时候 => 1 dist < intercept => 0float falloffSelect = step(intercept, dist);// 计算光源对某一个点的照明贡献 距离衰减因子 dist < intercept => fallofftTermNear 近距离因子 dist > intercept => 远距离因子float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;return falloffTerm;}
讲解到这里希望你能够理解光源产生的过程。
下面贴下原文的链接:
cocos creator 2D关照
我只是把不容易理解的部分给讲一下,希望对你有帮助。
相关文章:
Cocos Creator 2d光照
godot游戏引擎是有2d光照的,用起来感觉还是很强大的,不知道他是怎么搞的,有时间看看他们怎么实现的。 之前一直以为cocos社区里面没有2d光照的实现,偶然看到2d实现的具体逻辑,现在整理如下, 一࿱…...
5款好用的AI办公软件,一键轻松制作PPT、视频,提升工作效率!
众所周知,AI 人工智能技术已渗透到生活的方方面面,无论是很多人早已用上的智能音箱、语音助手,还是新近诞生的各种 AI 软件工具,背后都离不开 AI 人工智能技术的加持。 对于各类新生的 AI 软件工具,人们很容易「选边站…...
【MyBatis面试题】
目录 前言 1.MyBatis执行流程。 2.Mybatis是否支持延迟加载? 3.延迟加载的底层原理知道吗? 4.Mybatis的一级、二级缓存用过吗? 5.Mybatis的二级缓存什么时候会清理缓存中的数据? 总结 前言 本文主要介绍了MyBatis面试题相…...
编程界的圣经:从Scheme到JavaScript构建你的计算思维
文章目录 适读人群目 录 《计算机程序的构造和解释》(Structure and Interpretation of Computer Programs,简记为SICP)是MIT的基础课教材,出版后引起计算机教育界的广泛关注,对推动全世界大学计算机科学技术教育的发…...
智慧城市与智慧乡村:共创城乡一体化新局面
一、引言 随着科技的不断进步和城乡发展的日益融合,智慧城市与智慧乡村的建设已成为推动城乡一体化发展的新引擎。智慧城市利用物联网、大数据、云计算等先进技术,实现城市治理、公共服务、产业发展等领域的智能化;而智慧乡村则借助现代科技…...
蓝桥杯——web(ECharts)
ECharts 初体验 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><script src"echarts.js">&l…...
MySQL数据库在Windows和Linux中由于大小写默认规则不同,出现大小写问题如何解决?
Windows和Linux差异:在Windows上,lower_case_table_names默认为1,而在Linux上,默认值通常为0。因此,在Linux上更改这个设置更常见,以确保与Windows环境的兼容性或实现特定的大小写敏感性需求。 操作系统的大…...
新雀优化算法NOA求解机器人栅格地图最短路径规划,可以自定义地图(提供MATLAB代码)
一、星雀优化算法 星雀优化算法(Nutcracker optimizer algorithm,NOA)由Mohamed Abdel-Basset等人于2023年提出,该算法模拟星雀的两种行为,即:在夏秋季节收集并储存食物,在春冬季节搜索食物的存储位置。CEC2005:星雀优化算法(Nut…...
重塑语言智能未来:掌握Transformer,驱动AI与NLP创新实战
Transformer模型 Transformer是自然语言理解(Natural Language Understanding,NLU)的游戏规则改变者,NLU 是自然语言处理(Natural Language Processing,NLP)的一个子集。NLU已成为全球数字经济中AI 的支柱之一。 Transformer 模型标志着AI 新…...
【Windows】Windows 11无法连接共享打印机
Windows 11无法连接共享打印机 1.在电脑点击winr 键然后输入gpedit.msc进行回车进入本地本地组策略编辑器2.打开本地组策略-管理模板>打印机->找到配置RPC连接设置,打开3.选择“已启用”,将下面连接协议改成“命名管道上的RPC”,搞定。…...
Window10数据库崩溃启动失败,MySQL8.0.30通过data文件夹恢复数据库到Docker
背景: 昨天关机前还在使用mysql,一切正常,但今天打开电脑,发现mysql启动不起来了,老是提示端口占用,但是系统也没有新安装什么软件,而且通过查询nat命令也没发现3306端口占用。而且修改成3307等…...
【树】-Lc101-对称二叉树(一棵树是否是另一棵树的子树的变形)
写在前面 最近想复习一下数据结构与算法相关的内容,找一些题来做一做。如有更好思路,欢迎指正。 目录 写在前面一、场景描述二、具体步骤1.环境说明2.代码 写在后面 一、场景描述 对称二叉树。给给定一个二叉树,检查它是否是镜像对称的。 例…...
在Jupyter Notebook中安装第三方库
pip vs. conda pip 可以在所有环境下安装python包。conda 可以在conda环境下安装所有包。 如果你已经安装了python,那么这个选择对你来说是非常容易的: 如果你是用Anaconda或者Miniconda安装的python,那么请使用conda命令来安装python包。如…...
「AI工程师」数据处理与分析-工作指导
工作指导书 一、工作职责 负责数据的收集、清洗、整合和处理,确保数据质量和准确性。进行数据分析和挖掘,提取有价值的信息,为业务决策提供支持。构建和维护数据处理和分析的流程和工具,提高数据处理效率。与其他团队成员合作,共同解决数据处理和分析过程中遇到的问题。二…...
Rust:Mutex 的示例代码
在Rust中,你可以使用std::sync::Mutex来创建一个互斥锁,从而保护共享资源。下面是一个使用Mutex的简单示例: use std::sync::Mutex; use std::thread; use std::time::Duration; fn main() { // 创建一个包含整数的Mutex let counter…...
在 Docker 环境下安装 OpenWrt
在 Docker 环境下安装 OpenWrt 是一种方便且易于管理的方式,它允许您在不需要物理设备的情况下运行 OpenWrt。以下是在 Docker 中安装 OpenWrt 的步骤: 首先,您需要安装 Docker。具体安装方法可以参考 Docker 官方文档。在安装完成后…...
stl的基本知识学习
1.vector: 2.set: 3.map: 4.栈: 5.队列: 6. unordered_map与unordered_set: 7. 位运算: 8.cctype: 导图:...
Python从0到100(三):Python中的变量介绍
前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…...
污水处理厂重金属废水深度处理CH-90树脂处理系统
项目名称 广东某工业污水处理厂重金属废水深度处理工程项目 工艺选择 科海思重金属深度处理工艺 工艺原理 离子交换吸附 项目背景 随着环保要求不断提高,工业废水处理已成为众多企业的必修课。然而在工业生产中,如何有效处理含有重金属的废水成为…...
WordPress供求插件API文档:用户登录
该文档为WordPress供求插件文档,详情请查看 WordPress供求插件:一款专注于同城生活信息发布的插件-CSDN博客文章浏览阅读67次。WordPress供求插件:sliver-urban-life 是一款专注于提供同城生活信息发布与查看的插件,该插件可以实…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
