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 alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Mysql虚拟列的使用场景

《Mysql虚拟列的使用场景》MySQL虚拟列是一种在查询时动态生成的特殊列,它不占用存储空间,可以提高查询效率和数据处理便利性,本文给大家介绍Mysql虚拟列的相关知识,感兴趣的朋友一起看看吧... 目录1. 介绍mysql虚拟列1.1 定义和作用1.2 虚拟列与普通列的区别2. MySQL虚拟列的类型2

MybatisGenerator文件生成不出对应文件的问题

《MybatisGenerator文件生成不出对应文件的问题》本文介绍了使用MybatisGenerator生成文件时遇到的问题及解决方法,主要步骤包括检查目标表是否存在、是否能连接到数据库、配置生成... 目录MyBATisGenerator 文件生成不出对应文件先在项目结构里引入“targetProje

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

Python使用qrcode库实现生成二维码的操作指南

《Python使用qrcode库实现生成二维码的操作指南》二维码是一种广泛使用的二维条码,因其高效的数据存储能力和易于扫描的特点,广泛应用于支付、身份验证、营销推广等领域,Pythonqrcode库是... 目录一、安装 python qrcode 库二、基本使用方法1. 生成简单二维码2. 生成带 Log

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南

《Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南》在日常数据处理工作中,我们经常需要将不同Excel文档中的数据整合到一个新的DataFrame中,以便进行进一步... 目录一、准备工作二、读取Excel文件三、数据叠加四、处理重复数据(可选)五、保存新DataFram

SpringBoot生成和操作PDF的代码详解

《SpringBoot生成和操作PDF的代码详解》本文主要介绍了在SpringBoot项目下,通过代码和操作步骤,详细的介绍了如何操作PDF,希望可以帮助到准备通过JAVA操作PDF的你,项目框架用的... 目录本文简介PDF文件简介代码实现PDF操作基于PDF模板生成,并下载完全基于代码生成,并保存合并P

java中VO PO DTO POJO BO DO对象的应用场景及使用方式

《java中VOPODTOPOJOBODO对象的应用场景及使用方式》文章介绍了Java开发中常用的几种对象类型及其应用场景,包括VO、PO、DTO、POJO、BO和DO等,并通过示例说明了它... 目录Java中VO PO DTO POJO BO DO对象的应用VO (View Object) - 视图对象

详解Java中如何使用JFreeChart生成甘特图

《详解Java中如何使用JFreeChart生成甘特图》甘特图是一种流行的项目管理工具,用于显示项目的进度和任务分配,在Java开发中,JFreeChart是一个强大的开源图表库,能够生成各种类型的图... 目录引言一、JFreeChart简介二、准备工作三、创建甘特图1. 定义数据集2. 创建甘特图3.