Ink Shader Analysis

Chinese Ink-wash Painting II Shader

Introduction

This article attempts to analyze the code part of Chinese Ink-wash Painting II ( Source: https://www.shadertoy.com/view/DdSyDW ) to learn the implementation ideas and methods.

Insert image description here

Code

The code below first defines some functions to generate noise, calculate ray tracing, obtain normals, etc. Then in the mainImage function, it calculates the color value of each pixel, which is based on the ray tracing results and the noise texture. Finally, it assigns the calculated color value to fragColor, which is the final color rendered to the screen.

1.Constant

In the header file 常量, used.

// 定义一些常量
#define MAX_STEPS 200
#define MAX_DIST 100.
#define SURF_DIST .001
#define TAU 6.283185
#define PI 3.141592
#define S smoothstep
#define T iTime

2. Hash value generation

Generate 2D hash values : A hash function can map the input to a fixed-size numeric field. The hash function here maps 2D coordinates to a 随机的2D向量. This function is used in the generate noise function to create a pseudo-random, smooth-looking noise texture.

// 生成哈希值
vec2 hash22(vec2 p){
    p = vec2( dot(p,vec2(127.1,311.7)),
			  dot(p,vec2(269.5,183.3)));
    return -1.0 + 2.0 * fract(sin(p)*43758.5453123);
}

3. Generate noise and texture

Generate noise : The noise function is used to generate a random but continuous 纹理texture. This texture is often used to simulate various phenomena in nature, such as clouds, flames, water waves, etc.

// 生成噪声
float noise(vec2 p)
{
    const float K1 = 0.366025404; // (sqrt(3)-1)/2;
    const float K2 = 0.211324865; // (3-sqrt(3))/6;
    
    vec2 i = floor(p + (p.x + p.y) * K1);
    
    vec2 a = p - (i - (i.x + i.y) * K2);
    vec2 o = (a.x < a.y) ? vec2(0.0, 1.0) : vec2(1.0, 0.0);
    vec2 b = a - (o - K2);
    vec2 c = a - (1.0 - 2.0 * K2);
    
    vec3 h = max(0.5 - vec3(dot(a, a), dot(b, b), dot(c, c)), 0.0);
    vec3 n = h * h * h * h * vec3(dot(a, hash22(i)), dot(b, hash22(i + o)), dot(c, hash22(i + 1.0)));
     
    return dot(vec3(70.0, 70.0, 70.0), n);
}

// 生成噪声纹理
float noise_itself(vec2 p)
{
    float f = 0.;
    p *= 8.;
    f += 1.0000 * noise(p); p = 6.0 * p;
    f += 0.1000 * noise(p); p = 4.0 * p;
    f += 0.0100 * noise(p); p = 3.0 * p;
    return f;
}

4. Rotation matrix

Rotation matrix : used to rotate coordinates. It can rotate a coordinate to a new position. In the scene, a rotation matrix is ​​used 旋转相机的方向to allow the user to view the terrain from different angles.

// 旋转矩阵
mat2 Rot(float a) {
    float s=sin(a), c=cos(a);
    return mat2(c, -s, s, c);
}

5. Get distance

Show some below 内联代码片.

// 获取距离
float GetDist(vec3 p) {
    float d = p.y+4.;
    p.z -= iTime;
    float h = max(0., noise_itself(p.xz*0.01+13.7)); 
    h = pow(h, 1.6)*8.;
    d -= h;
    
    return d*0.3;
}

6. Ray tracing

Ray tracing is a technique used to generate images by tracing the path of a ray of light from a viewpoint through pixels into a scene 计算每个像素的颜色.

// 光线追踪
float RayMarch(vec3 ro, vec3 rd) {
	float dO=0.;
    
    for(int i=0; i<MAX_STEPS; i++) {
    	vec3 p = ro + rd*dO;
        float dS = GetDist(p);
        dO += dS;
        if(dO>MAX_DIST || abs(dS)<SURF_DIST) break;
    }
    
    return dO;
}

7. Get normals

Normal vectors have many important applications in computer graphics, mainly including the following aspects:

  1. Lighting calculation: When calculating the lighting effect on the surface of an object, the normal vector is a very critical factor. According to the angle between the light source direction and the normal vector, the brightness of the object surface can be determined.

  2. Collision detection: In physics simulation and game development, normal vectors are often used for collision detection and collision response. When two objects collide, the direction of the rebound can be calculated using the normal vector.

  3. Texture mapping: When performing texture mapping, the normal vector can be used to calculate the direction of the texture so that the texture can be correctly attached to the surface of the object.

  4. Geometric processing: Normal vectors will also be used when performing geometric processing, such as model clipping, hidden surface elimination, etc.

vec3 GetNormal(vec3 p) {
    vec2 e = vec2(.001, 0);
    vec3 n = GetDist(p) - 
        vec3(GetDist(p-e.xyy), GetDist(p-e.yxy),GetDist(p-e.yyx));
    
    return normalize(n);
}

8. Get the light direction

In the ray tracing or ray casting algorithm, it is calculated based on the starting point, target point and focal length of the ray, as well as the 2D texture coordinates 光线的方向.

// 获取光线方向
vec3 GetRayDir(vec2 uv, vec3 p, vec3 l, float z) {
    vec3 
        f = normalize(l-p),
        r = normalize(cross(vec3(0,1,0), f)),
        u = cross(f,r),
        c = f*z,
        i = c + uv.x*r + uv.y*u;
    return normalize(i);
}

9. Main function

Main function : The main function (usually named main) in GLSL (OpenGL Shading Language) is responsible for calculating the color of each pixel in the fragment shader. It is the entry point of the program and it is the beginning of program execution. In this context, mainImagea function is a special main function that is called in the rendering pipeline to calculate the color value of each pixel.

When your graphics program runs, the GPU calls the fragment shader once for each pixel on the screen. On each call, the main function calculates the color of the current pixel and stores it in the gl_FragColor variable. This way, after all the pixels have been processed by the fragment shader, you get the final image.

It should be noted that although the main function will be called multiple times (once for each pixel), each call is independent, that is, each call will only affect the current pixel and will not affect the color of other pixels.

This Shader first calculates the UV coordinates corresponding to each pixel based on the input screen coordinates and resolution. Then, based on the position of the mouse, the camera's position and direction are calculated. Then, based on the UV coordinates and the position and direction of the camera, the direction of the light is calculated. Then, using a ray tracing algorithm, the intersection point of the light ray and the terrain is calculated, as well as the color at the intersection point. Finally, the color is adjusted according to the brightness of the sun, and the calculated color is assigned to the output color. After calculating the color of each pixel, a rendered image of the 3D terrain is generated.

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // 将屏幕坐标转换为[-1, 1]范围内的UV坐标
    vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;
    
    // 设置默认的鼠标位置
	vec2 m = vec2(-.05, -0.5);
    
    // 如果鼠标在屏幕上移动,则更新鼠标位置
    if(iMouse.z > 0.) m = iMouse.xy/iResolution.xy;

    // 设置相机的初始位置
    vec3 ro = vec3(0, 3, -3);
    
    // 根据鼠标的位置旋转相机的方向
    ro.yz *= Rot(-m.y*PI+1.);
    ro.y = max(ro.y, -1.);
    ro.xz *= Rot(-m.x*TAU);
    
    // 计算光线的方向
    vec3 rd = GetRayDir(uv, ro, vec3(0,0.,0), 1.);
    
    // 初始化颜色为黑色
    vec3 col = vec3(0);
   
    // 进行光线追踪,获取光线行进的距离
    float d = RayMarch(ro, rd);
    
    // 计算太阳的亮度
    float sun = S(0.998,1.,dot(normalize(rd), normalize(vec3(0.,0.5,-1.))));
    
    // 如果光线没有超出最大距离,说明光线击中了物体
    if(d<MAX_DIST) {
        // 计算光线击中的位置
        vec3 p = ro + rd * d;
        
        // 计算该位置的法线
        vec3 n = GetNormal(p);
        
        // 设置光源的方向
        vec3 lightDir = normalize(vec3(0.0, .5,-.5));

        // 计算菲涅尔项,用于模拟光线在表面的反射和折射,描述光线在接触物体表面时,反射和折射
        float fre = max(0.,dot(n, normalize(ro-p)));
        fre = pow(fre,.125);
        fre = S(0.8,0.85,fre);
        fre = mix(fre, 1., S(-0.0, -4., p.y));
        
        // 根据高度计算颜色
        float heigh = S(-4., 0., p.y);
        col = mix(vec3(0.6588,0.5176,0.2824), vec3(0.3059, 0.4627, 0.633), heigh)*fre;
        
        // 如果高度在一定范围内,修改颜色
        heigh = S(-4.1, -3.9, p.y);
        col = mix(vec3(0.6,0.55,0.2)*S(0.,15., d), col, heigh);
        
        // 将太阳亮度设置为0
        sun = .0;
    }
    
    // 如果光线没有击中物体,设置背景颜色
    col = mix(col, vec3(0.6588,0.5176,0.2824)*1.5, S(0.,100., d));
    
    // 根据太阳的亮度调整颜色
    col = mix(col, vec3(1.,0.4,0.), sun);

    // 设置最终的颜色
    fragColor = vec4(col, 1.);
}

The main purpose of this function is to calculate the color of each pixel.
Process : First calculate the direction of the light, and then perform ray tracing. If the light hits the object, the color is calculated based on the properties of the object (such as normal, height, etc.), otherwise it is set to the background color. Finally, adjust the color according to the brightness of the sun and assign the calculated color to fragColor.

10. Summary

The main purpose of these parts of the code is to generate a 3D terrain that looks real. By using noise functions and noise textures, you can generate a terrain that is both random and natural. By using a rotation matrix, the user can view the terrain from different angles.

other

Noise is a very important tool in computer graphics because it can generate a random but continuous texture that visually looks very similar to many phenomena in nature. Such as mountains, rivers, clouds, flames, water waves, etc., all have a random but regular characteristic. This property is difficult to model with simple mathematical functions, but noise functions model it well.

The texture generated by the noise function has both randomness and continuity, which allows it to be used to simulate various phenomena in nature. For example, we can generate terrain height values ​​through a noise function, making the terrain look random and natural. We can also generate the shape of the clouds through a noise function, making the clouds look both random and continuous.

Therefore, the noise function can be used to simulate various phenomena in nature to generate more realistic and vivid images.

Guess you like

Origin blog.csdn.net/m0_51947486/article/details/132058882