shadertoy生成PBR场景

2024-01-10 19:44
文章标签 生成 场景 pbr shadertoy

本文主要是介绍shadertoy生成PBR场景,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

Shadertoy不支持VBO,因此shadertoy下的建模需要借助SDF(符号距离函数)的方式,渲染借助步进式光线追踪(Ray-marching)算法,结合Blinn-Phong或PBR光照模型,渲染场景。

下面是实现效果图:
在这里插入图片描述

关键词:

  1. SDF(符号距离函数)
  2. Ray-marching算法
  3. 光照模型:
    • Blinn-Phong
    • PBR

Shadertoy使用

两种方式使用:

  1. 在线预览shader:Shadertoy官网
  2. VSCode安装两个插件,预览shader:
    在这里插入图片描述
    编辑好shader后,右键代码区点击
    在这里插入图片描述
    本文以VSCode下预览shadertoy为例:

添加天空盒

使用iChannelX来加载立方体贴图


#iChannel0 "file://../skybox/skybox_{}.jpg" // Note the wildcard '{}'
#iChannel0::Type "CubeMap"

{}通配符将通过替换为以下任何一组的值来解析

  • [ ‘e’, ‘w’, ‘u’, ‘d’, ‘n’, ‘s’ ],
  • [ ‘east’, ‘west’, ‘up’, ‘down’, ‘north’, ‘south’ ],
  • [ ‘px’, ‘nx’, ‘py’, ‘ny’, ‘pz’, ‘nz’ ]
  • [ ‘posx’, ‘negx’, ‘posy’, ‘negy’, ‘posz’, ‘negz’ ].
CUBE_MAP纹理目标方位
POSITIVE_X
NEGATIVE_X
POSITIVE_Y
NEGATIVE_Y
POSITIVE_Z
NEGATIVE_Z

#iChannel0 "file://../skybox/skybox_{}.jpg" // Note the wildcard '{}'
#iChannel0::Type "CubeMap"const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float EPSILON = 0.0001;

创建简单sdf模型
球体

/*** 中心位于原点半径为1的球体的符号距离函数定义*/
float sphereSDF(vec3 samplePoint) {return length(samplePoint) - 1.0;
}

立方体

/*** Signed distance function for a cube centered at the origin* with width = height = length = 2.0*/
float cubeSDF(vec3 p) {// If d.x < 0, then -1 < p.x < 1, and same logic applies to p.y, p.z// So if all components of d are negative, then p is inside the unit cubevec3 d = abs(p) - vec3(1.0, 1.0, 1.0);// Assuming p is inside the cube, how far is it from the surface?// Result will be negative or zero.float insideDistance = min(max(d.x, max(d.y, d.z)), 0.0);// Assuming p is outside the cube, how far is it from the surface?// Result will be positive or zero.float outsideDistance = length(max(d, 0.0));return insideDistance + outsideDistance;
}

CSG (构造实体)操作

/*
* 构造实体形状(CSG)操作
*/
float intersectSDF(float distA, float distB) {return max(distA, distB);
}float unionSDF(float distA, float distB) {return min(distA, distB);
}float differenceSDF(float distA, float distB) {return max(distA, -distB);
}

SDF描述场景

/*** 用SDF描述场景*/
float sceneSDF(vec3 samplePoint) {// return sphereSDF(samplePoint);float sphereDist = sphereSDF(samplePoint ) ;float cubeDist = cubeSDF(samplePoint);return intersectSDF(cubeDist, sphereDist);
}

简单的光线追踪算法


/*** 返回最短距离函数* * eye: 射线的起点,可理解为相机* marchingDirection: 射线的标准化方向向量* start: 从相机开始的最短距离* end: 最远距离*/
float shortestDistanceToSurface(vec3 eye, vec3 marchingDirection, float start, float end) {float depth = start;for (int i = 0; i < MAX_MARCHING_STEPS; i++) {float dist = sceneSDF(eye + depth * marchingDirection);if (dist < EPSILON) {return depth;}depth += dist;if (depth >= end) {return end;}}return end;
}

根据给定的垂直fov大小盒image大小,计算光追视线的方向

/*** 返回相机的标准化方向向量* * fieldOfView: 垂直视野的角度* size: 输出图像的分辨率* fragCoord: 输出图像中的像素坐标*/
vec3 rayDirection(float fieldOfView, vec2 size, vec2 fragCoord) {vec2 xy = fragCoord - size / 2.0;float z = size.y / tan(radians(fieldOfView) / 2.0);return normalize(vec3(xy, -z));
}

sdf距离场下利用距离梯度近似计算物体上某一点的法线

/*** 对于那些SDF求出来在曲面上的点求标准化的法线向量*/
vec3 estimateNormal(vec3 p) {return normalize(vec3(sceneSDF(vec3(p.x + EPSILON, p.y, p.z)) - sceneSDF(vec3(p.x - EPSILON, p.y, p.z)),sceneSDF(vec3(p.x, p.y + EPSILON, p.z)) - sceneSDF(vec3(p.x, p.y - EPSILON, p.z)),sceneSDF(vec3(p.x, p.y, p.z  + EPSILON)) - sceneSDF(vec3(p.x, p.y, p.z - EPSILON))));
}

计算冯氏光照

/*** Lighting contribution of a single point light source via Phong illumination.* * The vec3 returned is the RGB color of the light's contribution.** k_a: 环境色* k_d: 漫反射颜色* k_s: 镜面颜色* alpha: 光泽系数* p: position of point being lit* eye: 相机的位置* lightPos: 光的位置* lightIntensity: 光的颜色/强度** See https://en.wikipedia.org/wiki/Phong_reflection_model#Description*/
vec3 phongContribForLight(vec3 k_d, vec3 k_s, float alpha, vec3 p, vec3 eye, vec3 lightPos, vec3 lightIntensity) {vec3 N = estimateNormal(p);vec3 L = normalize(lightPos - p);vec3 V = normalize(eye - p);vec3 R = normalize(reflect(-L, N));float dotLN = dot(L, N);float dotRV = dot(R, V);if (dotLN < 0.0) {// Light not visible from this point on the surfacereturn vec3(0.0, 0.0, 0.0);} if (dotRV < 0.0) {// Light reflection in opposite direction as viewer, apply only diffuse// componentreturn lightIntensity * (k_d * dotLN);}return lightIntensity * (k_d * dotLN + k_s * pow(dotRV, alpha));
}

计算多个点光源的冯氏光照颜色

/*** Lighting via Phong illumination.* * The vec3 returned is the RGB color of that point after lighting is applied.* k_a: 环境色* k_d: 漫反射颜色* k_s: 镜面颜色* alpha: 光泽系数* p: position of point being lit* eye: 相机的位置** See https://en.wikipedia.org/wiki/Phong_reflection_model#Description*/
vec3 phongIllumination(vec3 k_a, vec3 k_d, vec3 k_s, float alpha, vec3 p, vec3 eye) {const vec3 ambientLight = 0.5 * vec3(1.0, 1.0, 1.0);vec3 color = ambientLight * k_a;vec3 light1Pos = vec3(4.0 * sin(iTime),2.0, 4.0 * cos(iTime));vec3 light1Intensity = vec3(0.4, 0.4, 0.4);color += phongContribForLight(k_d, k_s, alpha, p, eye, light1Pos, light1Intensity);vec3 light2Pos = vec3(2.0 * sin(0.37 * iTime),2.0 * cos(0.37 * iTime),2.0);vec3 light2Intensity = vec3(0.4, 0.4, 0.4);color += phongContribForLight(k_d, k_s, alpha, p, eye,light2Pos,light2Intensity);    return color;
}

相机的LookAt矩阵

/*** Return a transform matrix that will transform a ray from view space* to world coordinates, given the eye point, the camera target, and an up vector.** This assumes that the center of the camera is aligned with the negative z axis in* view space when calculating the ray marching direction. See rayDirection.*/
mat4 viewMatrix2(vec3 eye, vec3 center, vec3 up) {// Based on gluLookAt man pagevec3 f = normalize(center - eye);vec3 s = normalize(cross(f, up));vec3 u = cross(s, f);return mat4(vec4(s, 0.0),vec4(u, 0.0),vec4(-f, 0.0),vec4(0.0, 0.0, 0.0, 1));
}const float PI = 3.14159265359;

BRDF
法线分布函数

float DistributionGGX(vec3 N, vec3 H, float roughness)
{float a = roughness*roughness;float a2 = a*a;float NdotH = max(dot(N, H), 0.0);float NdotH2 = NdotH*NdotH;float nom   = a2;float denom = (NdotH2 * (a2 - 1.0) + 1.0);denom = PI * denom * denom;return nom / denom;
}

几何函数(阴影遮蔽)

float GeometrySchlickGGX(float NdotV, float roughness)
{float r = (roughness + 1.0);float k = (r*r) / 8.0;float nom   = NdotV;float denom = NdotV * (1.0 - k) + k;return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{float NdotV = max(dot(N, V), 0.0);float NdotL = max(dot(N, L), 0.0);float ggx2 = GeometrySchlickGGX(NdotV, roughness);float ggx1 = GeometrySchlickGGX(NdotL, roughness);return ggx1 * ggx2;
}

菲涅尔方程

vec3 fresnelSchlick(float cosTheta, vec3 F0)
{return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

生成光源位置和颜色

// lights
vec3 lightPositions[2];
vec3 lightColors[2];void updateLight(){lightPositions[0] = vec3(4.0 * sin(iTime),2.0, 4.0 * cos(iTime));lightColors[0] = vec3(1.0, 0., 0.);lightPositions[1] = vec3(2.0 * sin(0.37 * iTime),2.0 * cos(0.37 * iTime),2.0);lightColors[1] = vec3(1.0, 1.0, 0);
}

pbr材质参数

vec3 albedo=vec3(0.3f, 1.0f, 0.f);
float metallic = 0.7f;
float roughness = 0.3f;
float ao = 0.7f;

计算pbr颜色

void pbrColor(out vec4 FragColor,in vec3 WorldPos,in vec3 Normal,in vec3 camPos
){		vec3 N = normalize(Normal);vec3 V = normalize(camPos - WorldPos);// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0 // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)    vec3 F0 = vec3(0.04); F0 = mix(F0, albedo, metallic);// reflectance equationvec3 Lo = vec3(0.0);for(int i = 0; i < 2; ++i) {// calculate per-light radiancevec3 L = normalize(lightPositions[i] - WorldPos);vec3 H = normalize(V + L);float distance = length(lightPositions[i] - WorldPos);float attenuation = 1.0 / (distance * distance);vec3 radiance = lightColors[i] * attenuation;// Cook-Torrance BRDFfloat NDF = DistributionGGX(N, H, roughness);   float G   = GeometrySmith(N, V, L, roughness);      vec3 F    = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);vec3 numerator    = NDF * G * F; float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; // + 0.0001 to prevent divide by zerovec3 specular = numerator / denominator;// kS is equal to Fresnelvec3 kS = F;// for energy conservation, the diffuse and specular light can't// be above 1.0 (unless the surface emits light); to preserve this// relationship the diffuse component (kD) should equal 1.0 - kS.vec3 kD = vec3(1.0) - kS;// multiply kD by the inverse metalness such that only non-metals // have diffuse lighting, or a linear blend if partly metal (pure metals// have no diffuse light).kD *= 1.0 - metallic;	  // scale light by NdotLfloat NdotL = max(dot(N, L), 0.0);        // add to outgoing radiance LoLo += (kD * albedo / PI + specular) * radiance * NdotL;  // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again}// ambient lighting (note that the next IBL tutorial will replace // this ambient lighting with environment lighting).vec3 ambient = vec3(0.03) * albedo * ao;vec3 color = ambient + Lo;// HDR tonemappingcolor = color / (color + vec3(1.0));// gamma correctcolor = pow(color, vec3(1.0/2.2)); FragColor = vec4(color, 1.0);
}void mainImage( out vec4 fragColor, in vec2 fragCoord )
{updateLight();vec3 viewDir = rayDirection(45.0, iResolution.xy, fragCoord);vec3 eye = vec3(8.0, 5.0, 7.0);mat4 viewToWorld = viewMatrix2(eye, vec3(0.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0));vec3 worldDir = (viewToWorld * vec4(viewDir, 0.0)).xyz;float dist = shortestDistanceToSurface(eye, worldDir, MIN_DIST, MAX_DIST);//skyboxvec2 uv=(fragCoord-.5*iResolution.xy)/iResolution.y;vec4 s=vec4(0.,0.,2.,1.);  float t=iTime*.5;vec3 cam_pos=s.xyz+vec3(sin(t),0.,cos(t))*5.;vec3 cam_dir=normalize(s.xyz-cam_pos);vec3 cam_r=-cross(cam_dir,vec3(0,1,0));vec3 cam_u=-cross(cam_r,cam_dir);vec3 r=normalize(uv.x*cam_r+uv.y*cam_u+1.*cam_dir);// r= normalize();if (dist > MAX_DIST - EPSILON) {// Didn't hit anythingfragColor =texture(iChannel0,r);return;}// The closest point on the surface to the eyepoint along the view rayvec3 p = eye + dist * worldDir;//冯氏光照vec3 K_a = vec3(0.2, 0.2, 0.2);vec3 K_d = vec3(0.7, 0.2, 0.2);vec3 K_s = vec3(1.0, 1.0, 1.0);float shininess = 10.0;vec3 color = phongIllumination(K_a, K_d, K_s, shininess, p, eye);fragColor = vec4(color, 1.0);//pbrpbrColor(fragColor,p,estimateNormal(p),eye);
}

理论知识可查看前面两篇文章

参考链接

SDF
https://iquilezles.org/articles/distfunctions/
https://iquilezles.org/articles/distfunctions2d/
Ray-marching
http://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-ray-tracing/how-does-it-work
PBR辐射度量学
https://www.pbr-book.org/3ed-2018/Color_and_Radiometry/Radiometry
Shadertoy官网
http://shadertoy.com/

这篇关于shadertoy生成PBR场景的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/591872

相关文章

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

Spring Security 前后端分离场景下的会话并发管理

《SpringSecurity前后端分离场景下的会话并发管理》本文介绍了在前后端分离架构下实现SpringSecurity会话并发管理的问题,传统Web开发中只需简单配置sessionManage... 目录背景分析传统 web 开发中的 sessionManagement 入口ConcurrentSess

C#使用Spire.XLS快速生成多表格Excel文件

《C#使用Spire.XLS快速生成多表格Excel文件》在日常开发中,我们经常需要将业务数据导出为结构清晰的Excel文件,本文将手把手教你使用Spire.XLS这个强大的.NET组件,只需几行C#... 目录一、Spire.XLS核心优势清单1.1 性能碾压:从3秒到0.5秒的质变1.2 批量操作的优雅

Python使用python-pptx自动化操作和生成PPT

《Python使用python-pptx自动化操作和生成PPT》这篇文章主要为大家详细介绍了如何使用python-pptx库实现PPT自动化,并提供实用的代码示例和应用场景,感兴趣的小伙伴可以跟随小编... 目录使用python-pptx操作PPT文档安装python-pptx基础概念创建新的PPT文档查看

99%的人都选错了! 路由器WiFi双频合一还是分开好的专业解析与适用场景探讨

《99%的人都选错了!路由器WiFi双频合一还是分开好的专业解析与适用场景探讨》关于双频路由器的“双频合一”与“分开使用”两种模式,用户往往存在诸多疑问,本文将从多个维度深入探讨这两种模式的优缺点,... 在如今“没有WiFi就等于与世隔绝”的时代,越来越多家庭、办公室都开始配置双频无线路由器。但你有没有注

在ASP.NET项目中如何使用C#生成二维码

《在ASP.NET项目中如何使用C#生成二维码》二维码(QRCode)已广泛应用于网址分享,支付链接等场景,本文将以ASP.NET为示例,演示如何实现输入文本/URL,生成二维码,在线显示与下载的完整... 目录创建前端页面(Index.cshtml)后端二维码生成逻辑(Index.cshtml.cs)总结

Python实现数据可视化图表生成(适合新手入门)

《Python实现数据可视化图表生成(适合新手入门)》在数据科学和数据分析的新时代,高效、直观的数据可视化工具显得尤为重要,下面:本文主要介绍Python实现数据可视化图表生成的相关资料,文中通过... 目录前言为什么需要数据可视化准备工作基本图表绘制折线图柱状图散点图使用Seaborn创建高级图表箱线图热

SQLServer中生成雪花ID(Snowflake ID)的实现方法

《SQLServer中生成雪花ID(SnowflakeID)的实现方法》:本文主要介绍在SQLServer中生成雪花ID(SnowflakeID)的实现方法,文中通过示例代码介绍的非常详细,... 目录前言认识雪花ID雪花ID的核心特点雪花ID的结构(64位)雪花ID的优势雪花ID的局限性雪花ID的应用场景