接下来我想在渲染模型的时候,通过环境贴图给它一个环境光,所以咱们先说一下贴图。
知识点
- 长方体贴图
- 球体贴图
- 立方体环境光
- 球体环境光
1-贴图的显示
我们先在坐标系里显示一堆贴图看一下。
整体代码如下:
// 贴图
#iChannel0 "file://images/erha.jpg"
// Wrap方式:Clamp Repeat Mirror
#iChannel0::WrapMode "Repeat"
// 采样方式:Nearest Linear NearestMipMapNearest
#iChannel0::MinFilter "NearestMipMapNearest"
#iChannel0::MagFilter "Nearest"
// 坐标系
vec2 Coord(in vec2 coord, in float scale) {
return scale * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
// 坐标轴
vec4 AxisHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor) {
vec4 color = vec4(0, 0, 0, 0);
float dx = dFdx(coord.x) * axisWidth;
float dy = dFdy(coord.y) * axisWidth;
if(abs(coord.x) < dx) {
color = yAxisColor;
} else if(abs(coord.y) < dy) {
color = xAxisColor;
}
return color;
}
// 栅格
vec4 GridHelper(in vec2 coord, in float gridWidth, in vec4 gridColor) {
vec4 color = vec4(0, 0, 0, 0);
float dx = dFdx(coord.x) * gridWidth;
float dy = dFdy(coord.y) * gridWidth;
vec2 fraction = fract(coord);
if(fraction.x < dx || fraction.y < dy) {
color = gridColor;
}
return color;
}
// 坐标系辅助对象
vec4 CoordHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor, in float gridWidth, in vec4 gridColor) {
// 坐标轴
vec4 axisHelper = AxisHelper(coord, axisWidth, xAxisColor, yAxisColor);
// 栅格
vec4 gridHelper = GridHelper(coord, gridWidth, gridColor);
// =坐标系
return bool(axisHelper.a) ? axisHelper : gridHelper;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// 坐标
vec2 coord = Coord(fragCoord, 2.);
// 背景色
vec4 backgroundColor = vec4(0, 0, 0, 1);
// 坐标系辅助对象
vec4 coordHelper = CoordHelper(coord, 2., vec4(0, 1, 0, 1), vec4(1, 0, 0, 1), 1., vec4(1));
// texture(iChannel0, uv).rgb
// 最终的颜色
fragColor = backgroundColor + coordHelper+texture(iChannel0, coord).rgba;
}
解释一下上面的代码。
在ShaderToy中,可以通过以下方式加载贴图:
// 贴图
#iChannel0 "file://images/erha.jpg"
// Wrap方式:Clamp Repeat
#iChannel0::WrapMode "Repeat"
// 采样方式:Nearest Linear NearestMipMapNearest
#iChannel0::MinFilter "NearestMipMapNearest"
#iChannel0::MagFilter "Nearest"
加载完贴图后,可以以此方式调用贴图:
texture(iChannel0, coord).rgba
接下来我们通过立方体和球体,说一下如何在三维物体上贴图。
2-在立方体上贴图
1.先准备一个立方体。
// 贴图
#iChannel0 "file://images/erha.jpg"
// Wrap方式:Clamp Repeat Mirror
#iChannel0::WrapMode "Repeat"
// 采样方式:Nearest Linear NearestMipMapNearest
#iChannel0::MinFilter "NearestMipMapNearest"
#iChannel0::MagFilter "Nearest"
// 坐标系缩放
#define PROJECTION_SCALE 1.
// 长方体的中心位置
#define RECT_POS vec3(0.5)
// 长方体的尺寸
#define RECT_SIZE vec3(0.5)
// 长方体的漫反射系数
#define RECT_KD vec3(1)
// 相机视点位
#define CAMERA_POS mat3(cos(iTime),0,sin(iTime),0,1,0,-sin(iTime),0,cos(iTime))*(vec3(1, 1.5, 2.5)-RECT_POS)+RECT_POS
// 相机目标点
#define CAMERA_TARGET vec3(0.5)
// 上方向
#define CAMERA_UP vec3(0, 1, 0)
// 光线推进的起始距离
#define RAYMARCH_NEAR 0.1
// 光线推进的最远距离
#define RAYMARCH_FAR 128.
// 光线推进次数
#define RAYMARCH_TIME 40
// 当推进后的点位距离物体表面小于RAYMARCH_PRECISION时,默认此点为物体表面的点
#define RAYMARCH_PRECISION 0.001
// 点光源位置
#define LIGHT_POS vec3(1, 4, 1)
// 相邻点的抗锯齿的行列数
#define AA 3
// 坐标系
vec2 Coord(in vec2 coord) {
return PROJECTION_SCALE * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
// 长方体的的SDF模型
float SDFRect(vec3 coord) {
vec3 d = abs(coord - RECT_POS) - RECT_SIZE;
return length(max(d, 0.)) + min(max(d.x, max(d.y, d.z)), 0.);
}
// 视图旋转矩阵
mat3 RotateMatrix() {
//基向量c,视线
vec3 c = normalize(CAMERA_POS - CAMERA_TARGET);
//基向量a,视线和上方向的垂线
vec3 a = cross(CAMERA_UP, c);
//基向量b,修正上方向
vec3 b = cross(c, a);
//正交旋转矩阵
return mat3(a, b, c);
}
// 计算长方体的法线
vec3 SDFNormal(in vec3 p) {
const float h = 0.0001;
const vec2 k = vec2(1, -1);
return normalize(k.xyy * SDFRect(p + k.xyy * h) +
k.yyx * SDFRect(p + k.yyx * h) +
k.yxy * SDFRect(p + k.yxy * h) +
k.xxx * SDFRect(p + k.xxx * h));
}
// 获取纹理
vec3 getTexture(vec3 p,vec3 n){
vec3 colorZ = texture(iChannel0, p.xy).rgb*n.z;
vec3 colorY = texture(iChannel0, p.xz).rgb*n.y;
vec3 colorX = texture(iChannel0, -p.zy).rgb*n.x;
return colorZ+colorY+colorX;
}
// 光线推进
vec3 RayMarch(vec2 coord) {
float d = RAYMARCH_NEAR;
// 从相机视点到当前片元的射线
vec3 rd = normalize(RotateMatrix() * vec3(coord, -1));
// 片元颜色
vec3 color = vec3(0);
for(int i = 0; i < RAYMARCH_TIME && d < RAYMARCH_FAR; i++) {
// 光线推进后的点位
vec3 p = CAMERA_POS + d * rd;
// 法线
vec3 n=SDFNormal(p);
// 光线推进后的点位到长方体的有向距离
float curD = SDFRect(p);
// 若有向距离小于一定的精度,默认此点在长方体表面
if(curD < RAYMARCH_PRECISION) {
color=vec3(1);
break;
}
// 距离累加
d += curD;
}
return color;
}
// 抗锯齿 Anti-Aliasing
vec3 RayMarch_anti(vec2 fragCoord) {
// 初始颜色
vec3 color = vec3(0);
// 行列的一半
float aa2 = float(AA / 2);
// 逐行列遍历
for(int y = 0; y < AA; y++) {
for(int x = 0; x < AA; x++) {
// 基于像素的偏移距离
vec2 offset = vec2(float(x), float(y)) / float(AA) - aa2;
// 坐标位
vec2 coord = Coord(fragCoord + offset);
// 累加周围片元的颜色
color += RayMarch(coord);
}
}
// 返回周围颜色的均值
return color / float(AA * AA);
}
/* 绘图函数,画布中的每个片元都会执行一次,执行方式是并行的。
fragColor 输出参数,用于定义当前片元的颜色。
fragCoord 输入参数,当前片元的位置,原点在画布左下角,右侧边界为画布的像素宽,顶部边界为画布的像素高
*/
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// 光线推进
vec3 color = RayMarch_anti(fragCoord);
// 最终颜色
fragColor = vec4(color, 1);
}
效果如下:
当前的立方体并没有光照,这只是为了简化代码。
这个立方体的位置和尺寸都是与uv坐标相匹配的,即其左后点在零点上,宽高深都是1.
2.建立一个获取纹理数据的方法。
// 获取纹理
vec3 getTexture(vec3 p,vec3 n){
return texture(iChannel0, p.xy).rgb;
}
// 光线推进
vec3 RayMarch(vec2 coord) {
float d = RAYMARCH_NEAR;
// 从相机视点到当前片元的射线
vec3 rd = normalize(RotateMatrix() * vec3(coord, -1));
// 片元颜色
vec3 color = vec3(0);
for(int i = 0; i < RAYMARCH_TIME && d < RAYMARCH_FAR; i++) {
// 光线推进后的点位
vec3 p = CAMERA_POS + d * rd;
// 法线
vec3 n=SDFNormal(p);
// 光线推进后的点位到长方体的有向距离
float curD = SDFRect(p);
// 若有向距离小于一定的精度,默认此点在长方体表面
if(curD < RAYMARCH_PRECISION) {
color=getTexture(p,n);
break;
}
// 距离累加
d += curD;
}
return color;
}
效果如下:
当前的立方体只有前后两个面可以正常显示纹理。
3.我可以利用法线,只显示立方体的前后两个面看看。
将之前的Texture数据乘以当前着色点的法线绝对值的z值:
vec3 getTexture(vec3 p,vec3 n){
vec3 absN=abs(n);
return texture(iChannel0, p.xy).rgb*absN.z;
}
效果如下:
出现这个效果是因为立方体上、下、左、右的4个面的法线的z值都是0,那一个颜色乘以0后的值还是0。
4.同理,绘制上、下、左、右的4个面的贴图。
vec3 getTexture(vec3 p,vec3 n){
vec3 absN=abs(n);
vec3 colorZ = texture(iChannel0, p.xy*2.).rgb*absN.z;
vec3 colorY = texture(iChannel0, p.xz).rgb*absN.y;
vec3 colorX = texture(iChannel0, -p.zy).rgb*absN.x;
return colorZ+colorY+colorX;
}
效果如下:
细心的同学会发现,右侧X轴向的小狗是竖着的。
你可以将计算colorX时的yz坐标转置一下:
vec3 colorX = texture(iChannel0, p.zy).rgb*absN.x;
这样,右侧的小狗就横过来了。
以此原理,调整p点,你也可以让小狗倒过来,或者重复多个。
vec3 getTexture(vec3 p,vec3 n){
vec3 colorZ = texture(iChannel0, p.xy*2.).rgb*absN.z;
vec3 colorY = texture(iChannel0, p.xz).rgb*absN.y;
vec3 colorX = texture(iChannel0, -p.zy).rgb*absN.x;
return colorZ+colorY+colorX;
}
效果如下:
关于立方体贴图的基本原理咱们就说到这,接下来我们可以以同样的原理在球体上贴图。
3-在球体上贴图
我们先参考立方体 的代码绘制一个球体。
// 贴图
#iChannel0 "file://images/erha.jpg"
// Wrap方式:Clamp Repeat Mirror
#iChannel0::WrapMode "Repeat"
// 采样方式:Nearest Linear NearestMipMapNearest
#iChannel0::MinFilter "NearestMipMapNearest"
#iChannel0::MagFilter "Nearest"
// 坐标系缩放
#define PROJECTION_SCALE 1.
// 球体的球心位置
#define SPHERE_POS vec3(0.5)
// 球体的半径
#define SPHERE_R 0.5
// 相机视点位
#define CAMERA_POS mat3(cos(iTime),0,sin(iTime),0,1,0,-sin(iTime),0,cos(iTime))*(vec3(0, 1, 2)-SPHERE_POS)+SPHERE_POS
// 相机目标点
#define CAMERA_TARGET vec3(0.5)
// 上方向
#define CAMERA_UP vec3(0, 1, 0)
// 光线推进的起始距离
#define RAYMARCH_NEAR 0.1
// 光线推进的最远距离
#define RAYMARCH_FAR 128.
// 光线推进次数
#define RAYMARCH_TIME 40
// 当推进后的点位距离物体表面小于RAYMARCH_PRECISION时,默认此点为物体表面的点
#define RAYMARCH_PRECISION 0.001
// 点光源位置
#define LIGHT_POS vec3(1, 4, 1)
// 相邻点的抗锯齿的行列数
#define AA 3
// 坐标系
vec2 Coord(in vec2 coord) {
return PROJECTION_SCALE * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
//球体的SDF模型
float SDFSphere(vec3 coord) {
return length(coord - SPHERE_POS) - SPHERE_R;
}
// 视图旋转矩阵
mat3 RotateMatrix() {
//基向量c,视线
vec3 c = normalize(CAMERA_POS - CAMERA_TARGET);
//基向量a,视线和上方向的垂线
vec3 a = cross(CAMERA_UP, c);
//基向量b,修正上方向
vec3 b = cross(c, a);
//正交旋转矩阵
return mat3(a, b, c);
}
// 计算长方体的法线
vec3 SDFNormal(in vec3 p) {
const float h = 0.0001;
const vec2 k = vec2(1, -1);
return normalize(k.xyy * SDFSphere(p + k.xyy * h) +
k.yyx * SDFSphere(p + k.yyx * h) +
k.yxy * SDFSphere(p + k.yxy * h) +
k.xxx * SDFSphere(p + k.xxx * h));
}
// 获取纹理
vec3 getTexture(vec3 p,vec3 n){
vec3 absN=abs(n);
vec3 colorZ = texture(iChannel0, p.xy*2.).rgb*absN.z;
vec3 colorY = texture(iChannel0, p.xz).rgb*absN.y;
vec3 colorX = texture(iChannel0, -p.zy).rgb*absN.x;
return colorZ+colorY+colorX;
}
// 光线推进
vec3 RayMarch(vec2 coord) {
float d = RAYMARCH_NEAR;
// 从相机视点到当前片元的射线
vec3 rd = normalize(RotateMatrix() * vec3(coord, -1));
// 片元颜色
vec3 color = vec3(0);
for(int i = 0; i < RAYMARCH_TIME && d < RAYMARCH_FAR; i++) {
// 光线推进后的点位
vec3 p = CAMERA_POS + d * rd;
// 法线
vec3 n=SDFNormal(p);
// 光线推进后的点位到长方体的有向距离
float curD = SDFSphere(p);
// 若有向距离小于一定的精度,默认此点在长方体表面
if(curD < RAYMARCH_PRECISION) {
color=getTexture(p,n);
break;
}
// 距离累加
d += curD;
}
return color;
}
// 抗锯齿 Anti-Aliasing
vec3 RayMarch_anti(vec2 fragCoord) {
// 初始颜色
vec3 color = vec3(0);
// 行列的一半
float aa2 = float(AA / 2);
// 逐行列遍历
for(int y = 0; y < AA; y++) {
for(int x = 0; x < AA; x++) {
// 基于像素的偏移距离
vec2 offset = vec2(float(x), float(y)) / float(AA) - aa2;
// 坐标位
vec2 coord = Coord(fragCoord + offset);
// 累加周围片元的颜色
color += RayMarch(coord);
}
}
// 返回周围颜色的均值
return color / float(AA * AA);
}
/* 绘图函数,画布中的每个片元都会执行一次,执行方式是并行的。
fragColor 输出参数,用于定义当前片元的颜色。
fragCoord 输入参数,当前片元的位置,原点在画布左下角,右侧边界为画布的像素宽,顶部边界为画布的像素高
*/
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// 光线推进
vec3 color = RayMarch_anti(fragCoord);
// 最终颜色
fragColor = vec4(color, 1);
}
效果如下:
现在的效果看起来有点曝,这是因为我们无法像之前的立方体那样,单纯的依靠着色点的法线的某个分量判断此着色点是否渲染。
我们可以参考立方体,将球体也分成相等的六个面。
3-1-将立方体投影在球体上
想象一个场景:
球体中央有一个半透明的立方体,立方体中央有一个点光源,点光源可以把半透明的立方体投影到球体上。
那立方体的6个面在球体上的投影就可以把球体的表面均分成6份。
通过这种投影原理,我们就可以把一张矩形的纹理贴图映射到球体的每个面上。
例1
已知:
- 着色点P的法线为n
- 球体半径为r
求:判断着色点P是否在上图中的红色圆弧上
思路:
这就是一个三角函数问题,判断法线n 的z值是否大于上图线段a 的长度即可。
解:
由三角函数可得:
a²+a²=r²
所以:
a=sqrt(r²/2)
所以:
当n.z>sqrt(r²/2)的时候,点P在红色圆弧上。
现在最核心的点已经通了,我们可以再将这个问题延伸到三维球体上去。
例2
已知:
- 例1的图是球体中间的水平截面
- P点是球体上任意点
求:判断着色点P是否在上图中由红色的线连成的面上
思路:
红线连成的面是一种花瓣的形状。
水平截面上的半径r 是动态变化的,水平截面越趋向上下两端r 越小。
解:
根据点P的x、z 位置算出P点所在的水平截面的半径r:
r=sqrt(x²+z²)
根据例1是思路,判断点P是否在当前截面圆的红线上:
a=sqrt(r²/2)
当n.z>sqrt(r²/2)的时候,点P在红色的线连成的面上。
以此原理,我们就可以判断出点P是在球体的上、下、左、右那个方向上。
但是,我们是要把圆等分出6个面来的,所以我们需要把属于上下两个面的部分过滤掉。
我们将刚才的那片花瓣绕z轴旋转90°后求交即可。交出来的中间那个面就是等分球体的6个面之一。
至于这旋转90°的花瓣是怎么求的,大家参考第一片花瓣来画即可。
第一片花瓣的截面圆与y轴垂直,绕z轴旋转90°的花瓣的截面圆则是与x轴垂直。
其截面圆的r 值是sqrt(z²+y²),注意不要把z²写成x²,这样就会变成与z轴垂直的截面圆了。
基本原理咱们就说到这,接下来上代码。
3-2-代码实现
1.先把例1中前后方向的花瓣画出来。
vec3 getTexture(vec3 p,vec3 n){
vec3 absN=abs(n);
float a1=sqrt(pow(length(n.xz),2.)/2.);
float z=absN.z>a1?1.:0.;
return texture(iChannel0, p.xy).rgb*z;
}
效果如下:
2.参考例2,把当前的花瓣过滤掉上下两个面的部分。
vec3 getTexture(vec3 p,vec3 n){
vec3 absN=abs(n);
float a1=sqrt(pow(length(n.xz),2.)/2.);
float a2=sqrt(pow(length(n.yz),2.)/2.);
float z=absN.z>a1&&absN.z>a2?1.:0.;
return texture(iChannel0, p.xy).rgb*z;
}
效果如下:
3.同理类推,把其它的几个面也画上。
vec3 getTexture(vec3 p,vec3 n){
vec3 absN=abs(n);
float a1=sqrt(pow(length(n.xz),2.)/2.);
float a2=sqrt(pow(length(n.yz),2.)/2.);
float a3=sqrt(pow(length(n.xy),2.)/2.);
float z=absN.z>a1&&absN.z>a2?1.:0.;
float y=absN.y>a3&&absN.y>a2?1.:0.;
float x=absN.x>a1&&absN.x>a3?1.:0.;
vec3 colorZ = texture(iChannel0, p.xy).rgb*z;
vec3 colorY = texture(iChannel0, p.xz).rgb*y;
vec3 colorX = texture(iChannel0, p.zy).rgb*x;
return colorZ+colorX+colorY;
}
效果如下:
这效果有点像地狱三头犬。接下来咱们思考一下当前的贴图和球体上每个面的映射关系。
3-3-贴图与球面的映射关系
当前的贴图实际上并没有完全的映射到球体的每一个面上。
我换一张比较规则的贴看一下效果。
// #iChannel0 "file://images/erha.jpg"
#iChannel0 "file://https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/708fe531f61547e6882a913e38ae1e75~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=500&h=500&s=56593&e=jpg&b=ffffff"
贴图效果如下:

渲染效果如下:
由此可见,贴图里的橙色部分并没有显示出来。
若我想把一张贴图完全贴合到球体的每个面上应该怎么办呢?如下图所示:
其实我们可以通过球体的法线与uv的映射关系来实现这种效果。
我们可以分别将每个球面上的点映射到uv坐标的(0,1)之间。
代码如下:
// 线性插值
vec2 liner(vec2 vmin,vec2 vmax,vec2 v){
return (v-vmin)/(vmax-vmin);
}
// 获取纹理
vec3 getTexture(vec3 n){
vec3 absN=abs(n);
//3个方向上的a值
float a1=sqrt(pow(length(n.xz),2.)/2.);
float a2=sqrt(pow(length(n.yz),2.)/2.);
float a3=sqrt(pow(length(n.xy),2.)/2.);
float z=absN.z>a1&&absN.z>a2?1.:0.;
float y=absN.y>a3&&absN.y>a2?1.:0.;
float x=absN.x>a1&&absN.x>a3?1.:0.;
// xy面(前后的面)、xz面(上下的面)、zy面(左右的面)上的采样点
vec2 p_xy= liner(vec2(-a1,-a2),vec2(a1,a2),n.xy);
vec2 p_xz= liner(vec2(-a3,-a2),vec2(a3,a2),n.xz);
vec2 p_zy= liner(vec2(-a1,-a3),vec2(a1,a3),n.zy);
vec3 colorZ = texture(iChannel0, p_xy).rgb*z;
vec3 colorY = texture(iChannel0, p_xz).rgb*y;
vec3 colorX = texture(iChannel0, p_zy).rgb*x;
return colorZ+colorY+colorX;
}
// 光线推进
vec3 RayMarch(vec2 coord) {
float d = RAYMARCH_NEAR;
// 从相机视点到当前片元的射线
vec3 rd = normalize(RotateMatrix() * vec3(coord, -1));
// 片元颜色
vec3 color = vec3(0);
for(int i = 0; i < RAYMARCH_TIME && d < RAYMARCH_FAR; i++) {
// 光线推进后的点位
vec3 p = CAMERA_POS + d * rd;
// 法线绝对值
vec3 n=SDFNormal(p);
// 光线推进后的点位到长方体的有向距离
float curD = SDFSphere(p);
// 若有向距离小于一定的精度,默认此点在长方体表面
if(curD < RAYMARCH_PRECISION) {
color=getTexture(n);
break;
}
// 距离累加
d += curD;
}
return color;
}
liner() 是一个线性插值方法,用来计算一个点在两点之间的百分比。
球体法线与uv 的映射关系如下:
-
前后球面
- n.x 对应xz截面上的a1
- n.y 对应zy截面上的a2
-
左右球面
- n.z对应xz截面上的a1
- n.y 对应xy截面上的a3
-
上下球面
- n.x对应xy截面上的a3
- n.z 对应zy截面上的a2
整体有点绕,若不理解可以微信我。
对于球体的贴图,还有一个比较快捷的方式,咱们看一下。
3-4-球体的另一种贴图方式
若对球体的贴图没啥太苛刻的要求,我们可以这样贴图。
vec3 getTexture(vec3 p,vec3 n){
vec3 absN=abs(n);
absN=pow(absN,vec3(10.));
absN/=absN.x+absN.y+absN.z;
vec3 colorZ = texture(iChannel0, p.xy).rgb*absN.z;
vec3 colorY = texture(iChannel0, p.xz).rgb*absN.y;
vec3 colorX = texture(iChannel0, p.zy).rgb*absN.x;
return colorZ+colorY+colorX;
}
效果如下:
这种方式适合砂石之类的不规则肌理效果。
4-环境光
环境光为场景的渲染提供丰富的光能,其贴图可以是立方体的6个面,也可以是等距圆柱投影贴图,其模型可以是立方体,也可以是球体。
接下来咱们就说一下如何把立方体的6个面贴图贴到立方体和球体上,从而制作环境光。
4-1-立方体环境光
4-1-1-立方体外部贴图
我们可以把提前制作好的立方体贴图依次贴到立方体的6个面上。效果如下:
代码如下:
// 获取纹理
vec3 getTexture(vec3 p,vec3 n){
vec3 absN=abs(n);
vec4 textureZ=n.z>0.? texture(iChannel5, p.xy): texture(iChannel4, vec2(-p.x,p.y));
vec4 textureY=n.y>0.? texture(iChannel2, vec2(-p.x,p.z)): texture(iChannel3,-p.xz);
vec4 textureX=n.x>0.? texture(iChannel1, vec2(-p.z,p.y)): texture(iChannel0, p.zy);
vec3 colorZ = textureZ.rgb*absN.z;
vec3 colorY = textureY.rgb*absN.y;
vec3 colorX = textureX.rgb*absN.x;
return colorZ+colorY+colorX;
}
// 光线推进
vec3 RayMarch(vec2 coord) {
float d = RAYMARCH_NEAR;
// 从相机视点到当前片元的射线
vec3 rd = normalize(RotateMatrix() * vec3(coord, -1));
// 片元颜色
vec3 color = vec3(0);
for(int i = 0; i < RAYMARCH_TIME && d < RAYMARCH_FAR; i++) {
// 光线推进后的点位
vec3 p = CAMERA_POS + d * rd;
// 法线绝对值
vec3 n=SDFNormal(p);
// 光线推进后的点位到长方体的有向距离
float curD = SDFRect(p);
// 若有向距离小于一定的精度,默认此点在长方体表面
if(curD < RAYMARCH_PRECISION) {
color=getTexture(p,n);
break;
}
// 距离累加
d += curD;
}
return color;
}
其原理很简单,就是根据法线方向,识别出立方体的6个面,然后为每个面贴上相应的贴图。
以此原理,我们把相机打到立方体内部,那就是环境光了。
4-1-1-立方体内部贴图
立方体内部贴图效果如下:
整体代码如下:
// 贴图 [ 'posx', 'negx', 'posy', 'negy', 'posz', 'negz' ].
#iChannel0 "file://images/bridge/posx.jpg"
#iChannel1 "file://images/bridge/negx.jpg"
#iChannel2 "file://images/bridge/posy.jpg"
#iChannel3 "file://images/bridge/negy.jpg"
#iChannel4 "file://images/bridge/posz.jpg"
#iChannel5 "file://images/bridge/negz.jpg"
// Wrap方式:Clamp Repeat Mirror
#iChannel0::WrapMode "Repeat"
// 采样方式:Nearest Linear NearestMipMapNearest
#iChannel0::MinFilter "NearestMipMapNearest"
#iChannel0::MagFilter "Nearest"
// 坐标系缩放
#define PROJECTION_SCALE 1.
// 长方体的中心位置
#define RECT_POS vec3(0)
// 长方体的尺寸,此数值为宽高深的一半
#define RECT_SIZE vec3(3)
// 长方体的漫反射系数
#define RECT_KD vec3(1)
// 相机视点位
#define CAMERA_POS mat3(cos(iTime),0,sin(iTime),0,1,0,-sin(iTime),0,cos(iTime))*(vec3(0, 0, 2)-RECT_POS)+RECT_POS
// #define CAMERA_POS vec3(0, 0, 1)
// 相机目标点
#define CAMERA_TARGET vec3(0)
// 上方向
#define CAMERA_UP vec3(0, 1, 0)
// 光线推进的起始距离
#define RAYMARCH_NEAR 0.1
// 光线推进的最远距离
#define RAYMARCH_FAR 128.
// 光线推进次数
#define RAYMARCH_TIME 40
// 当推进后的点位距离物体表面小于RAYMARCH_PRECISION时,默认此点为物体表面的点
#define RAYMARCH_PRECISION 0.001
// 相邻点的抗锯齿的行列数
#define AA 3
// 坐标系
vec2 Coord(in vec2 coord) {
return PROJECTION_SCALE * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
// 长方体的的SDF模型
/* float SDFRect(vec3 coord) {
vec3 d = abs(coord - RECT_POS) - RECT_SIZE;
return length(max(d, 0.)) + min(max(d.x, max(d.y, d.z)), 0.);
} */
// 长方体的的SDF模型-法线翻转
float SDFRect(vec3 coord) {
vec3 d = RECT_SIZE-abs(coord - RECT_POS) ;
return min(min(d.x,d.y),d.z);
}
// 视图旋转矩阵
mat3 RotateMatrix() {
//基向量c,视线
vec3 c = normalize(CAMERA_POS - CAMERA_TARGET);
//基向量a,视线和上方向的垂线
vec3 a = cross(CAMERA_UP, c);
//基向量b,修正上方向
vec3 b = cross(c, a);
//正交旋转矩阵
return mat3(a, b, c);
}
// 计算长方体的法线
vec3 SDFNormal(in vec3 p) {
const float h = 0.0001;
const vec2 k = vec2(1, -1);
return normalize(k.xyy * SDFRect(p + k.xyy * h) +
k.yyx * SDFRect(p + k.yyx * h) +
k.yxy * SDFRect(p + k.yxy * h) +
k.xxx * SDFRect(p + k.xxx * h));
}
// 获取纹理
vec3 getTexture(vec3 p,vec3 n){
vec3 absN=abs(n);
p=(p/RECT_SIZE)/2.+0.5;
vec4 textureZ=n.z>0.? texture(iChannel5, p.xy): texture(iChannel4, vec2(-p.x,p.y));
vec4 textureY=n.y>0.? texture(iChannel3, vec2(-p.x,p.z)): texture(iChannel2,-p.xz);
vec4 textureX=n.x>0.? texture(iChannel0, vec2(-p.z,p.y)): texture(iChannel1, vec2(p.z,p.y));
vec3 colorZ = textureZ.rgb*absN.z;
vec3 colorY = textureY.rgb*absN.y;
vec3 colorX = textureX.rgb*absN.x;
return colorZ+colorY+colorX;
}
// 光线推进
vec3 RayMarch(vec2 coord) {
float d = RAYMARCH_NEAR;
// 从相机视点到当前片元的射线
vec3 rd = normalize(RotateMatrix() * vec3(coord, -1));
// 片元颜色
vec3 color = vec3(0);
for(int i = 0; i < RAYMARCH_TIME && d < RAYMARCH_FAR; i++) {
// 光线推进后的点位
vec3 p = CAMERA_POS + d * rd;
// 法线绝对值
vec3 n=SDFNormal(p);
// 光线推进后的点位到长方体的有向距离
float curD = SDFRect(p);
// 若有向距离小于一定的精度,默认此点在长方体表面
if(curD < RAYMARCH_PRECISION) {
color=getTexture(p,n);
break;
}
// 距离累加
d += curD;
}
return color;
}
// 抗锯齿 Anti-Aliasing
vec3 RayMarch_anti(vec2 fragCoord) {
// 初始颜色
vec3 color = vec3(0);
// 行列的一半
float aa2 = float(AA / 2);
// 逐行列遍历
for(int y = 0; y < AA; y++) {
for(int x = 0; x < AA; x++) {
// 基于像素的偏移距离
vec2 offset = vec2(float(x), float(y)) / float(AA) - aa2;
// 坐标位
vec2 coord = Coord(fragCoord + offset);
// 累加周围片元的颜色
color += RayMarch(coord);
}
}
// 返回周围颜色的均值
return color / float(AA * AA);
}
/* 绘图函数,画布中的每个片元都会执行一次,执行方式是并行的。
fragColor 输出参数,用于定义当前片元的颜色。
fragCoord 输入参数,当前片元的位置,原点在画布左下角,右侧边界为画布的像素宽,顶部边界为画布的像素高
*/
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// 光线推进
vec3 color = RayMarch_anti(fragCoord);
// 最终颜色
fragColor = vec4(color, 1);
}
解释一下其中的几个关键点。
当前立方的面是朝内的,相机是在立方之中的,所以我们需要从内部判断RayMarching的推进点到立方体边界的距离,其SDF模型如下:
float SDFRect(vec3 coord) {
vec3 d = RECT_SIZE-abs(coord - RECT_POS) ;
return min(min(d.x,d.y),d.z);
}
其原理就是让立方体边界减去推进点到立方体的距离,然后取其最小的分量。
接下来,在getTexture() 中需要做好着色点与vu坐标的映射关系。
p=(p/RECT_SIZE)/2.+0.5;
这里就是在将采样点映射到uv坐标的[0,1]之间。
其实当前的立方体环境光还有个问题,立方体贴图在边界处的转折太明显:
那我接下来把它贴在球体上试试。
4-2-球体环境光
4-2-1-球体外部贴图
下面是法线朝外的球体贴图效果。
看其效果会发现其面的边界处的转折不再那么明显。
整体代码如下:
// 贴图
#iChannel0 "file://images/bridge/posx.jpg"
#iChannel1 "file://images/bridge/negx.jpg"
#iChannel2 "file://images/bridge/posy.jpg"
#iChannel3 "file://images/bridge/negy.jpg"
#iChannel4 "file://images/bridge/posz.jpg"
#iChannel5 "file://images/bridge/negz.jpg"
// Wrap方式:Clamp Repeat Mirror
#iChannel0::WrapMode "Repeat"
// 采样方式:Nearest Linear NearestMipMapNearest
#iChannel0::MinFilter "NearestMipMapNearest"
#iChannel0::MagFilter "Nearest"
// 坐标系缩放
#define PROJECTION_SCALE 1.
// 球体的球心位置
#define SPHERE_POS vec3(0)
// 球体的半径
#define SPHERE_R 1.
// 相机视点位
#define CAMERA_POS mat3(cos(iTime),0,sin(iTime),0,1,0,-sin(iTime),0,cos(iTime))*(vec3(0, 2, 4)-SPHERE_POS)+SPHERE_POS
// 相机目标点
#define CAMERA_TARGET vec3(0)
// 上方向
#define CAMERA_UP vec3(0, 1, 0)
// 光线推进的起始距离
#define RAYMARCH_NEAR 0.1
// 光线推进的最远距离
#define RAYMARCH_FAR 128.
// 光线推进次数
#define RAYMARCH_TIME 40
// 当推进后的点位距离物体表面小于RAYMARCH_PRECISION时,默认此点为物体表面的点
#define RAYMARCH_PRECISION 0.001
// 相邻点的抗锯齿的行列数
#define AA 3
// 坐标系
vec2 Coord(in vec2 coord) {
return PROJECTION_SCALE * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
//球体的SDF模型
float SDFSphere(vec3 coord) {
return length(coord - SPHERE_POS) - SPHERE_R;
}
// 视图旋转矩阵
mat3 RotateMatrix() {
//基向量c,视线
vec3 c = normalize(CAMERA_POS - CAMERA_TARGET);
//基向量a,视线和上方向的垂线
vec3 a = cross(CAMERA_UP, c);
//基向量b,修正上方向
vec3 b = cross(c, a);
//正交旋转矩阵
return mat3(a, b, c);
}
// 计算长方体的法线
vec3 SDFNormal(in vec3 p) {
const float h = 0.0001;
const vec2 k = vec2(1, -1);
return normalize(k.xyy * SDFSphere(p + k.xyy * h) +
k.yyx * SDFSphere(p + k.yyx * h) +
k.yxy * SDFSphere(p + k.yxy * h) +
k.xxx * SDFSphere(p + k.xxx * h));
}
// 线性插值
vec2 liner(vec2 vmin,vec2 vmax,vec2 v){
return (v-vmin)/(vmax-vmin);
}
// 获取纹理
vec3 getTexture(vec3 n){
vec3 absN=abs(n);
//3个方向上的a值
float a1=sqrt(pow(length(n.xz),2.)/2.);
float a2=sqrt(pow(length(n.yz),2.)/2.);
float a3=sqrt(pow(length(n.xy),2.)/2.);
/* float z=absN.z>a1&&absN.z>a2?1.:0.;
float y=absN.y>a3&&absN.y>a2?1.:0.;
float x=absN.x>a1&&absN.x>a3?1.:0.; */
float z=absN.z>=a1&&absN.z>a2?1.:0.;
float y=absN.y>=a3&&absN.y>=a2?1.:0.;
float x=absN.x>a1&&absN.x>a3?1.:0.;
// xy面(前后的面)、xz面(上下的面)、zy面(左右的面)上的采样点
vec2 p_xy= liner(vec2(-a1,-a2),vec2(a1,a2),n.xy);
vec2 p_xz= liner(vec2(-a3,-a2),vec2(a3,a2),n.xz);
vec2 p_zy= liner(vec2(-a1,-a3),vec2(a1,a3),n.zy);
vec4 textureZ=n.z>0.? texture(iChannel5, p_xy): texture(iChannel4, vec2(-p_xy.x,p_xy.y));
vec4 textureY=n.y>0.? texture(iChannel2, vec2(-p_xz[0],p_xz[1])): texture(iChannel3,-p_xz);
vec4 textureX=n.x>0.? texture(iChannel1, vec2(-p_zy[0],p_zy[1])): texture(iChannel0, p_zy);
vec3 colorZ = textureZ.rgb*z;
vec3 colorY = textureY.rgb*y;
vec3 colorX = textureX.rgb*x;
return colorZ+colorY+colorX;
}
// 光线推进
vec3 RayMarch(vec2 coord) {
float d = RAYMARCH_NEAR;
// 从相机视点到当前片元的射线
vec3 rd = normalize(RotateMatrix() * vec3(coord, -1));
// 片元颜色
vec3 color = vec3(0);
for(int i = 0; i < RAYMARCH_TIME && d < RAYMARCH_FAR; i++) {
// 光线推进后的点位
vec3 p = CAMERA_POS + d * rd;
// 法线绝对值
vec3 n=SDFNormal(p);
// 光线推进后的点位到长方体的有向距离
float curD = SDFSphere(p);
// 若有向距离小于一定的精度,默认此点在长方体表面
if(curD < RAYMARCH_PRECISION) {
color=getTexture(n);
break;
}
// 距离累加
d += curD;
}
return color;
}
// 抗锯齿 Anti-Aliasing
vec3 RayMarch_anti(vec2 fragCoord) {
// 初始颜色
vec3 color = vec3(0);
// 行列的一半
float aa2 = float(AA / 2);
// 逐行列遍历
for(int y = 0; y < AA; y++) {
for(int x = 0; x < AA; x++) {
// 基于像素的偏移距离
vec2 offset = vec2(float(x), float(y)) / float(AA) - aa2;
// 坐标位
vec2 coord = Coord(fragCoord + offset);
// 累加周围片元的颜色
color += RayMarch(coord);
}
}
// 返回周围颜色的均值
return color / float(AA * AA);
}
/* 绘图函数,画布中的每个片元都会执行一次,执行方式是并行的。
fragColor 输出参数,用于定义当前片元的颜色。
fragCoord 输入参数,当前片元的位置,原点在画布左下角,右侧边界为画布的像素宽,顶部边界为画布的像素高
*/
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// 光线推进
vec3 color = RayMarch_anti(fragCoord);
// 最终颜色
fragColor = vec4(color, 1);
}
其6个面的区分原理和立方体是一样的,不过我又做了一点细节调整。
我在判断球面在x,y,z哪个方向的时候,有些地方用了>=,而不是之前的>。
/*
float z=absN.z>a1&&absN.z>a2?1.:0.;
float y=absN.y>a3&&absN.y>a2?1.:0.;
float x=absN.x>a1&&absN.x>a3?1.:0.;
*/
float z=absN.z>=a1&&absN.z>a2?1.:0.;
float y=absN.y>=a3&&absN.y>=a2?1.:0.;
float x=absN.x>a1&&absN.x>a3?1.:0.;
这是因为之前的>会让球面边界采不到点,从而出现缝隙,如下图所示:
不过我当前的解决方式并不是最好,接缝依旧存在,只是不太明显。
这个问题跟贴图没关系,应该是我划分球面时的边界数据的精度问题。
若大家有好的解决方式可以微信我(1051904257)。
接下来咱们把相机打到球体内部。
4-2-1-球体内部贴图
球体内部贴图效果如下:
整体代码如下:
// 贴图
#iChannel0 "file://images/bridge/posx.jpg"
#iChannel1 "file://images/bridge/negx.jpg"
#iChannel2 "file://images/bridge/posy.jpg"
#iChannel3 "file://images/bridge/negy.jpg"
#iChannel4 "file://images/bridge/posz.jpg"
#iChannel5 "file://images/bridge/negz.jpg"
// Wrap方式:Clamp Repeat Mirror
#iChannel0::WrapMode "Repeat"
// 采样方式:Nearest Linear NearestMipMapNearest
#iChannel0::MinFilter "NearestMipMapNearest"
#iChannel0::MagFilter "Nearest"
// 坐标系缩放
#define PROJECTION_SCALE 1.
// 球体的球心位置
#define SPHERE_POS vec3(0)
// 球体的半径
#define SPHERE_R 3.
// 相机视点位
#define CAMERA_POS mat3(cos(iTime),0,sin(iTime),0,1,0,-sin(iTime),0,cos(iTime))*(vec3(0, 0, 0.4)-SPHERE_POS)+SPHERE_POS
// #define CAMERA_POS vec3(0, 0, 0.5)
// 相机目标点
#define CAMERA_TARGET vec3(0)
// 上方向
#define CAMERA_UP vec3(0, 1, 0)
// 光线推进的起始距离
#define RAYMARCH_NEAR 0.1
// 光线推进的最远距离
#define RAYMARCH_FAR 128.
// 光线推进次数
#define RAYMARCH_TIME 40
// 当推进后的点位距离物体表面小于RAYMARCH_PRECISION时,默认此点为物体表面的点
#define RAYMARCH_PRECISION 0.001
// 相邻点的抗锯齿的行列数
#define AA 3
// 坐标系
vec2 Coord(in vec2 coord) {
return PROJECTION_SCALE * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
//球体的SDF模型-翻转法线
float SDFSphere(vec3 coord) {
return SPHERE_R-length(coord - SPHERE_POS);
}
// 视图旋转矩阵
mat3 RotateMatrix() {
//基向量c,视线
vec3 c = normalize(CAMERA_POS - CAMERA_TARGET);
//基向量a,视线和上方向的垂线
vec3 a = cross(CAMERA_UP, c);
//基向量b,修正上方向
vec3 b = cross(c, a);
//正交旋转矩阵
return mat3(a, b, c);
}
// 计算长方体的法线
vec3 SDFNormal(in vec3 p) {
const float h = 0.0001;
const vec2 k = vec2(1, -1);
return normalize(k.xyy * SDFSphere(p + k.xyy * h) +
k.yyx * SDFSphere(p + k.yyx * h) +
k.yxy * SDFSphere(p + k.yxy * h) +
k.xxx * SDFSphere(p + k.xxx * h));
}
// 线性插值
vec2 liner(vec2 vmin,vec2 vmax,vec2 v){
return (v-vmin)/(vmax-vmin);
}
// 获取纹理
vec3 getTexture(vec3 n){
vec3 absN=abs(n);
//3个方向上的a值
float a1=sqrt(pow(length(n.xz),2.)/2.);
float a2=sqrt(pow(length(n.yz),2.)/2.);
float a3=sqrt(pow(length(n.xy),2.)/2.);
float z=absN.z>=a1&&absN.z>a2?1.:0.;
float y=absN.y>=a3&&absN.y>=a2?1.:0.;
float x=absN.x>a1&&absN.x>a3?1.:0.;
// xy面(前后的面)、xz面(上下的面)、zy面(左右的面)上的采样点
vec2 p_xy= liner(vec2(-a1,-a2),vec2(a1,a2),n.xy);
vec2 p_xz= liner(vec2(-a3,-a2),vec2(a3,a2),n.xz);
vec2 p_zy= liner(vec2(-a1,-a3),vec2(a1,a3),n.zy);
vec4 textureZ=n.z>0.? texture(iChannel4, -p_xy): texture(iChannel5, vec2(p_xy.x,-p_xy.y));
vec4 textureY=n.y>0.? texture(iChannel3, vec2(-p_xz[0],p_xz[1])): texture(iChannel2,-p_xz);
vec4 textureX=n.x>0.? texture(iChannel1, vec2(p_zy[0],-p_zy[1])): texture(iChannel0, vec2(-p_zy[0],-p_zy[1]));
vec3 colorZ = textureZ.rgb*z;
vec3 colorY = textureY.rgb*y;
vec3 colorX = textureX.rgb*x;
return colorZ+colorY+colorX;
}
// 光线推进
vec3 RayMarch(vec2 coord) {
float d = RAYMARCH_NEAR;
// 从相机视点到当前片元的射线
vec3 rd = normalize(RotateMatrix() * vec3(coord, -1));
// 片元颜色
vec3 color = vec3(0);
for(int i = 0; i < RAYMARCH_TIME && d < RAYMARCH_FAR; i++) {
// 光线推进后的点位
vec3 p = CAMERA_POS + d * rd;
// 法线绝对值
vec3 n=SDFNormal(p);
// 光线推进后的点位到长方体的有向距离
float curD = SDFSphere(p);
// 若有向距离小于一定的精度,默认此点在长方体表面
if(curD < RAYMARCH_PRECISION) {
color=getTexture(n);
break;
}
// 距离累加
d += curD;
}
return color;
}
// 抗锯齿 Anti-Aliasing
vec3 RayMarch_anti(vec2 fragCoord) {
// 初始颜色
vec3 color = vec3(0);
// 行列的一半
float aa2 = float(AA / 2);
// 逐行列遍历
for(int y = 0; y < AA; y++) {
for(int x = 0; x < AA; x++) {
// 基于像素的偏移距离
vec2 offset = vec2(float(x), float(y)) / float(AA) - aa2;
// 坐标位
vec2 coord = Coord(fragCoord + offset);
// 累加周围片元的颜色
color += RayMarch(coord);
}
}
// 返回周围颜色的均值
return color / float(AA * AA);
}
/* 绘图函数,画布中的每个片元都会执行一次,执行方式是并行的。
fragColor 输出参数,用于定义当前片元的颜色。
fragCoord 输入参数,当前片元的位置,原点在画布左下角,右侧边界为画布的像素宽,顶部边界为画布的像素高
*/
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// 光线推进
vec3 color = RayMarch_anti(fragCoord);
// 最终颜色
fragColor = vec4(color, 1);
}
当前的球体和之前的立方体一样,都做了法线的翻转,其翻转原理都是让模型的尺寸减去推进点位。
//球体的SDF模型-翻转法线
float SDFSphere(vec3 coord) {
return SPHERE_R-length(coord - SPHERE_POS);
}
贴图和球面的映射原理都是一样的,只是细节需要做一下微调,比如纹理的镜像操作。
4-3-内部贴图的简单化
其实对于在模型内部贴图制作环境光的过程,是可以很简单的。
以球天为例,我只需要知道相机的视线方向就够了。
想象我们在仰望蓝天的时候,蓝天只会随我们视线的旋转而改变,不会随视点的移动而改变。
球天是不需要考虑其位置和半径大小的,因此也就不需要再做光线推进。
最终其代码就可以写成这样:
// 贴图
#iChannel0 "file://images/bridge/posx.jpg"
#iChannel1 "file://images/bridge/negx.jpg"
#iChannel2 "file://images/bridge/posy.jpg"
#iChannel3 "file://images/bridge/negy.jpg"
#iChannel4 "file://images/bridge/posz.jpg"
#iChannel5 "file://images/bridge/negz.jpg"
// Wrap方式:Clamp Repeat Mirror
#iChannel0::WrapMode "Repeat"
// 采样方式:Nearest Linear NearestMipMapNearest
#iChannel0::MinFilter "NearestMipMapNearest"
#iChannel0::MagFilter "Nearest"
// 坐标系缩放
#define PROJECTION_SCALE 1.
// 相机视点位
#define CAMERA_POS mat3(cos(iTime),0,sin(iTime),0,1,0,-sin(iTime),0,cos(iTime))*(vec3(0, 0, 0.4))
// 相机目标点
#define CAMERA_TARGET vec3(0)
// 上方向
#define CAMERA_UP vec3(0, 1, 0)
// 坐标系
vec2 Coord(in vec2 coord) {
return PROJECTION_SCALE * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
// 视图旋转矩阵
mat3 RotateMatrix() {
//基向量c,视线
vec3 c = normalize(CAMERA_POS - CAMERA_TARGET);
//基向量a,视线和上方向的垂线
vec3 a = cross(CAMERA_UP, c);
//基向量b,修正上方向
vec3 b = cross(c, a);
//正交旋转矩阵
return mat3(a, b, c);
}
// 线性插值
vec2 liner(vec2 vmin,vec2 vmax,vec2 v){
return (v-vmin)/(vmax-vmin);
}
// 获取纹理
vec3 getTexture(vec3 n){
vec3 absN=abs(n);
//3个方向上的a值
float a1=sqrt(pow(length(n.xz),2.)/2.);
float a2=sqrt(pow(length(n.yz),2.)/2.);
float a3=sqrt(pow(length(n.xy),2.)/2.);
float z=absN.z>=a1&&absN.z>a2?1.:0.;
float y=absN.y>=a3&&absN.y>=a2?1.:0.;
float x=absN.x>a1&&absN.x>a3?1.:0.;
// xy面(前后的面)、xz面(上下的面)、zy面(左右的面)上的采样点
vec2 p_xy= liner(vec2(-a1,-a2),vec2(a1,a2),n.xy);
vec2 p_xz= liner(vec2(-a3,-a2),vec2(a3,a2),n.xz);
vec2 p_zy= liner(vec2(-a1,-a3),vec2(a1,a3),n.zy);
vec4 textureZ=n.z>0.? texture(iChannel5, p_xy): texture(iChannel4, vec2(-p_xy.x,p_xy.y));
vec4 textureY=n.y>0.? texture(iChannel2, vec2(-p_xz[0],p_xz[1])): texture(iChannel3,-p_xz);
vec4 textureX=n.x>0.? texture(iChannel1, vec2(-p_zy[0],p_zy[1])): texture(iChannel0, p_zy);
vec3 colorZ = textureZ.rgb*z;
vec3 colorY = textureY.rgb*y;
vec3 colorX = textureX.rgb*x;
return colorZ+colorY+colorX;
}
/* 绘图函数,画布中的每个片元都会执行一次,执行方式是并行的。
fragColor 输出参数,用于定义当前片元的颜色。
fragCoord 输入参数,当前片元的位置,原点在画布左下角,右侧边界为画布的像素宽,顶部边界为画布的像素高
*/
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec3 rd = normalize(RotateMatrix() * vec3(Coord(fragCoord), -1));
vec3 color =getTexture(rd);
fragColor = vec4(color, 1);
}
其效果和之前是一样的,所以我就不再多说。
总结
其实,我们也可以把圆柱投影贴图映射到球体上,这个映射原理我在WebGL里说过,所以我就先不写了,有时间了再做补充。
各种SDF模型与贴图的映射方式是多种多样的,我就先说这2种了。
后面我会说一下如何以球天为环境光,渲染模型。
参考链接: