Skip to content

Instantly share code, notes, and snippets.

@Pikachuxxxx
Created March 16, 2022 16:23
Show Gist options
  • Save Pikachuxxxx/41527b0bebf9dcd3adda5e9026c68b98 to your computer and use it in GitHub Desktop.
Save Pikachuxxxx/41527b0bebf9dcd3adda5e9026c68b98 to your computer and use it in GitHub Desktop.
A Path Tracer written is GLSL [https://www.shadertoy.com/view/NtfcRr]
[https://www.shadertoy.com/view/NtfcRr] - ShaderToy Link: This gist has some unsaved edits cause shader toy login was down
---------------------------------------------------------------------------------
Common
---------------------------------------------------------------------------------
// Path Tracer Settings
// The minimunm distance a ray must travel before we consider an intersection.
// This is to prevent a ray from intersecting a surface it just bounced off of.
const float c_minimumRayHitTime = 0.01f;
// the farthest we look for ray hits
const float c_superFar = 10000.0f;
// camera FOV
const float c_FOVDegrees = 100.0f;
// number of ray bounces allowed
const int c_numBounces = 4;
// after a hit, it moves the ray this far along the normal away from a surface.
// Helps prevent incorrect intersections when rays bounce off of objects.
const float c_rayPosNormalNudge = 0.01f;
// a pixel value multiplier of light before tone mapping and sRGB
const float c_exposure = 0.5f;
// how many renders per frame - make this larger to get around the vsync limitation, and get a better image faster.
const int c_numRendersPerFrame = 8;
const float KEY_SPACE = 32.5/256.0;
/////////////////////////////////
// math constants
const float c_pi = 3.14159265359f;
const float c_twopi = 2.0f * c_pi;
/////////////////////////////////
// math util
float ScalarTriple(vec3 u, vec3 v, vec3 w)
{
return dot(cross(u, v), w);
}
uint wang_hash(inout uint seed)
{
seed = uint(seed ^ uint(61)) ^ uint(seed >> uint(16));
seed *= uint(9);
seed = seed ^ (seed >> 4);
seed *= uint(0x27d4eb2d);
seed = seed ^ (seed >> 15);
return seed;
}
float RandomFloat01(inout uint state)
{
return float(wang_hash(state)) / 4294967296.0;
}
vec3 RandomUnitVector(inout uint state)
{
float z = RandomFloat01(state) * 2.0f - 1.0f;
float a = RandomFloat01(state) * c_twopi;
float r = sqrt(1.0f - z * z);
float x = r * cos(a);
float y = r * sin(a);
return vec3(x, y, z);
}
// Rotation matrix around the X axis.
mat3 rotateX(float theta) {
float c = cos(theta);
float s = sin(theta);
return mat3(
vec3(1, 0, 0),
vec3(0, c, -s),
vec3(0, s, c)
);
}
// Rotation matrix around the Y axis.
mat3 rotateY(float theta) {
float c = cos(theta);
float s = sin(theta);
return mat3(
vec3(c, 0, s),
vec3(0, 1, 0),
vec3(-s, 0, c)
);
}
// Rotation matrix around the Z axis.
mat3 rotateZ(float theta) {
float c = cos(theta);
float s = sin(theta);
return mat3(
vec3(c, -s, 0),
vec3(s, c, 0),
vec3(0, 0, 1)
);
}
/////////////////////////////////
// Hit Info
struct SMaterialInfo
{
vec3 albedo; // the color used for diffuse lighting
vec3 emissive; // how much the surface glows
float percentSpecular; // percentage chance of doing specular instead of diffuse lighting
float roughness; // how rough the specular reflections are
vec3 specularColor; // the color tint of specular reflections
};
struct SRayHitInfo
{
float dist;
vec3 normal;
SMaterialInfo material;
};
// Hit test functions
bool TestQuadTrace(in vec3 rayPos, in vec3 rayDir, inout SRayHitInfo info, in vec3 a, in vec3 b, in vec3 c, in vec3 d)
{
// calculate normal and flip vertices order if needed
vec3 normal = normalize(cross(c-a, c-b));
if (dot(normal, rayDir) > 0.0f)
{
normal *= -1.0f;
vec3 temp = d;
d = a;
a = temp;
temp = b;
b = c;
c = temp;
}
vec3 p = rayPos;
vec3 q = rayPos + rayDir;
vec3 pq = q - p;
vec3 pa = a - p;
vec3 pb = b - p;
vec3 pc = c - p;
// determine which triangle to test against by testing against diagonal first
vec3 m = cross(pc, pq);
float v = dot(pa, m);
vec3 intersectPos;
if (v >= 0.0f)
{
// test against triangle a,b,c
float u = -dot(pb, m);
if (u < 0.0f) return false;
float w = ScalarTriple(pq, pb, pa);
if (w < 0.0f) return false;
float denom = 1.0f / (u+v+w);
u*=denom;
v*=denom;
w*=denom;
intersectPos = u*a+v*b+w*c;
}
else
{
vec3 pd = d - p;
float u = dot(pd, m);
if (u < 0.0f) return false;
float w = ScalarTriple(pq, pa, pd);
if (w < 0.0f) return false;
v = -v;
float denom = 1.0f / (u+v+w);
u*=denom;
v*=denom;
w*=denom;
intersectPos = u*a+v*d+w*c;
}
float dist;
if (abs(rayDir.x) > 0.1f)
{
dist = (intersectPos.x - rayPos.x) / rayDir.x;
}
else if (abs(rayDir.y) > 0.1f)
{
dist = (intersectPos.y - rayPos.y) / rayDir.y;
}
else
{
dist = (intersectPos.z - rayPos.z) / rayDir.z;
}
if (dist > c_minimumRayHitTime && dist < info.dist)
{
info.dist = dist;
info.normal = normal;
return true;
}
return false;
}
bool TestSphereTrace(in vec3 rayPos, in vec3 rayDir, inout SRayHitInfo info, in vec4 sphere)
{
//get the vector from the center of this sphere to where the ray begins.
vec3 m = rayPos - sphere.xyz;
//get the dot product of the above vector and the ray's vector
float b = dot(m, rayDir);
float c = dot(m, m) - sphere.w * sphere.w;
//exit if r's origin outside s (c > 0) and r pointing away from s (b > 0)
if(c > 0.0 && b > 0.0)
return false;
//calculate discriminant
float discr = b * b - c;
//a negative discriminant corresponds to ray missing sphere
if(discr < 0.0)
return false;
//ray now found to intersect sphere, compute smallest t value of intersection
bool fromInside = false;
float dist = -b - sqrt(discr);
if (dist < 0.0f)
{
fromInside = true;
dist = -b + sqrt(discr);
}
if (dist > c_minimumRayHitTime && dist < info.dist)
{
info.dist = dist;
info.normal = normalize((rayPos+rayDir*dist) - sphere.xyz) * (fromInside ? -1.0f : 1.0f);
return true;
}
return false;
}
// SRGB-Linear Conversion Functions
vec3 LessThan(vec3 f, float value)
{
return vec3(
(f.x < value) ? 1.0f : 0.0f,
(f.y < value) ? 1.0f : 0.0f,
(f.z < value) ? 1.0f : 0.0f);
}
vec3 LinearToSRGB(vec3 rgb)
{
rgb = clamp(rgb, 0.0f, 1.0f);
return mix(
pow(rgb, vec3(1.0f / 2.4f)) * 1.055f - 0.055f,
rgb * 12.92f,
LessThan(rgb, 0.0031308f)
);
}
vec3 SRGBToLinear(vec3 rgb)
{
rgb = clamp(rgb, 0.0f, 1.0f);
return mix(
pow(((rgb + 0.055f) / 1.055f), vec3(2.4f)),
rgb / 12.92f,
LessThan(rgb, 0.04045f)
);
}
// Tonemapping
// ACES tone mapping curve fit to go from HDR to LDR
//https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
vec3 ACESFilm(vec3 x)
{
float a = 2.51f;
float b = 0.03f;
float c = 2.43f;
float d = 0.59f;
float e = 0.14f;
return clamp((x*(a*x + b)) / (x*(c*x + d) + e), 0.0f, 1.0f);
}
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
Buffer A - iChannel0 = BufferA, iChannel1 = Cubemao, iChannel2 = Keyboard(space)
---------------------------------------------------------------------------------
void TestSceneTrace(in vec3 rayPos, in vec3 rayDir, inout SRayHitInfo hitInfo)
{
// to move the scene around, since we can't move the camera yet
vec3 sceneTranslation = vec3(0.0f, 0.0f, 10.0f);
vec4 sceneTranslation4 = vec4(sceneTranslation, 0.0f);
// back wall
{
vec3 A = vec3(-12.6f, -12.6f, 25.0f) + sceneTranslation;
vec3 B = vec3( 12.6f, -12.6f, 25.0f) + sceneTranslation;
vec3 C = vec3( 12.6f, 12.6f, 25.0f) + sceneTranslation;
vec3 D = vec3(-12.6f, 12.6f, 25.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.8f, 0.5f, 0.2f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.2f;
hitInfo.material.roughness = 0.8f;
hitInfo.material.specularColor = vec3(1.0f, 0.6f, 0.0f);
}
}
// floor
{
vec3 A = vec3(-12.6f, -12.45f, 25.0f) + sceneTranslation;
vec3 B = vec3( 12.6f, -12.45f, 25.0f) + sceneTranslation;
vec3 C = vec3( 12.6f, -12.45f, 15.0f) + sceneTranslation;
vec3 D = vec3(-12.6f, -12.45f, 15.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.3f, 0.6f, 0.7f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.5f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.3f, 0.6f, 0.8f);
}
}
// cieling
{
vec3 A = vec3(-12.6f, 12.5f, 25.0f) + sceneTranslation;
vec3 B = vec3( 12.6f, 12.5f, 25.0f) + sceneTranslation;
vec3 C = vec3( 12.6f, 12.5f, 15.0f) + sceneTranslation;
vec3 D = vec3(-12.6f, 12.5f, 15.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.7f, 0.7f, 0.7f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}
// left wall
{
vec3 A = vec3(-12.5f, -12.6f, 25.0f) + sceneTranslation;
vec3 B = vec3(-12.5f, -12.6f, 15.0f) + sceneTranslation;
vec3 C = vec3(-12.5f, 12.6f, 15.0f) + sceneTranslation;
vec3 D = vec3(-12.5f, 12.6f, 25.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.7f, 0.1f, 0.1f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.7f;
hitInfo.material.roughness = 0.2f;
hitInfo.material.specularColor = vec3(1.0f, 0.0f, 0.0f);
}
}
// right wall
{
vec3 A = vec3( 12.5f, -12.6f, 25.0f) + sceneTranslation;
vec3 B = vec3( 12.5f, -12.6f, 15.0f) + sceneTranslation;
vec3 C = vec3( 12.5f, 12.6f, 15.0f) + sceneTranslation;
vec3 D = vec3( 12.5f, 12.6f, 25.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.1f, 0.7f, 0.1f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.4f;
hitInfo.material.roughness = 0.2f;
hitInfo.material.specularColor = vec3(0.0f, 0.7f, 0.0f);
}
}
// left box_1 - left face
{
vec3 A = vec3( -4.5f, -2.0f, 10.0f) + sceneTranslation;
vec3 B = vec3( -4.5f, -2.0f, 5.0f) + sceneTranslation;
vec3 C = vec3( -4.5f, 2.0f, 5.0f) + sceneTranslation;
vec3 D = vec3( -4.5f, 2.0f, 10.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.8f, 0.8f, 0.8f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}
// left box_1 - right face
{
vec3 A = vec3( -1.5f, -2.0f, 10.0f) + sceneTranslation;
vec3 B = vec3( -1.5f, -2.0f, 5.0f) + sceneTranslation;
vec3 C = vec3( -1.5f, 2.0f, 5.0f) + sceneTranslation;
vec3 D = vec3( -1.5f, 2.0f, 10.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.9f, 0.5f, 0.9f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.01;
hitInfo.material.specularColor = vec3(0.9f, 0.5f, 0.9f);
}
}
// left box_1 - cieling
{
vec3 A = vec3(-4.5f, 2.0f, 10.0f) + sceneTranslation;
vec3 B = vec3(-1.5f, 2.0f, 10.0f) + sceneTranslation;
vec3 C = vec3(-1.5f, 2.0f, 5.0f) + sceneTranslation;
vec3 D = vec3(-4.5f, 2.0f, 5.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.7f, 0.7f, 0.7f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}
// left box_1 - flooar
{
vec3 A = (vec3(-4.5f, -2.0f, 10.0f) + sceneTranslation) * rotateY(sin(90.0f));
vec3 B = (vec3(-1.5f, -2.0f, 10.0f) + sceneTranslation) * rotateY(sin(90.0f));
vec3 C = (vec3(-1.5f, -2.0f, 5.0f) + sceneTranslation) * rotateY(sin(90.0f));
vec3 D = (vec3(-4.5f, -2.0f, 5.0f) + sceneTranslation) * rotateY(sin(90.0f));
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.7f, 0.7f, 0.7f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}
// left box_1 - back wall
{
vec3 A = (vec3(-4.5f, -2.0f, 5.0f) + sceneTranslation) * rotateZ(0.0f);
vec3 B = (vec3(-1.5f, -2.0f, 5.0f) + sceneTranslation) * rotateZ(0.0f);
vec3 C = (vec3(-1.5f, 2.0f, 5.0f) + sceneTranslation) * rotateZ(0.0f);
vec3 D = (vec3(-4.5f, 2.0f, 5.0f) + sceneTranslation) * rotateZ(0.0f);
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.8f, 0.2f, 0.2f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.2f;
hitInfo.material.roughness = 0.8f;
hitInfo.material.specularColor = vec3(1.0f, 0.2f, 0.0f);
}
}
// light
{
vec3 A = vec3(-5.0f, 12.4f, 22.5f) + sceneTranslation;
vec3 B = vec3( 5.0f, 12.4f, 22.5f) + sceneTranslation;
vec3 C = vec3( 5.0f, 12.4f, 17.5f) + sceneTranslation;
vec3 D = vec3(-5.0f, 12.4f, 17.5f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.emissive = vec3(1.0f, 0.9f, 0.7f) * 20.0f;
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}
if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(-9.0f, -9.5f, 20.0f, 3.0f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(0.9f, 0.6f, 0.1f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.6f;
hitInfo.material.roughness = 0.6f;
hitInfo.material.specularColor = vec3(0.9f, 0.6f, 0.1f);
}
if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(0.0f, -9.5f, 20.0f, 3.0f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(0.9f, 0.5f, 0.9f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.6f;
hitInfo.material.roughness = 0.2;
hitInfo.material.specularColor = vec3(0.9f, 0.9f, 0.9f);
}
// a ball which has blue diffuse but red specular. an example of a "bad material".
// a better lighting model wouldn't let you do this sort of thing
if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(9.0f, -9.5f, 20.0f, 3.0f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(0.0f, 0.0f, 1.0f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.5f;
hitInfo.material.roughness = 0.4f;
hitInfo.material.specularColor = vec3(1.0f, 0.0f, 0.0f);
}
}
vec3 GetColorForRay(in vec3 startRayPos, in vec3 startRayDir, inout uint rngState)
{
// initialize
vec3 ret = vec3(0.0f, 0.0f, 0.0f);
vec3 throughput = vec3(1.0f, 1.0f, 1.0f);
vec3 rayPos = startRayPos;
vec3 rayDir = startRayDir;
for (int bounceIndex = 0; bounceIndex <= c_numBounces; ++bounceIndex)
{
// shoot a ray out into the world
SRayHitInfo hitInfo;
hitInfo.dist = c_superFar;
TestSceneTrace(rayPos, rayDir, hitInfo);
// if the ray missed, we are done
if (hitInfo.dist == c_superFar)
{
ret += SRGBToLinear(texture(iChannel1, rayDir).rgb) * throughput;
break;
}
// update the ray position
rayPos = (rayPos + rayDir * hitInfo.dist) + hitInfo.normal * c_rayPosNormalNudge;
// calculate whether we are going to do a diffuse or specular reflection ray
float doSpecular = (RandomFloat01(rngState) < hitInfo.material.percentSpecular) ? 1.0f : 0.0f;
// Calculate a new ray direction.
// Diffuse uses a normal oriented cosine weighted hemisphere sample.
// Perfectly smooth specular uses the reflection ray.
// Rough (glossy) specular lerps from the smooth specular to the rough diffuse by the material roughness squared
// Squaring the roughness is just a convention to make roughness feel more linear perceptually.
vec3 diffuseRayDir = normalize(hitInfo.normal + RandomUnitVector(rngState));
vec3 specularRayDir = reflect(rayDir, hitInfo.normal);
specularRayDir = normalize(mix(specularRayDir, diffuseRayDir, hitInfo.material.roughness * hitInfo.material.roughness));
rayDir = mix(diffuseRayDir, specularRayDir, doSpecular);
// add in emissive lighting
ret += hitInfo.material.emissive * throughput;
// update the colorMultiplier
throughput *= mix(hitInfo.material.albedo, hitInfo.material.specularColor, doSpecular);
// Russian Roulette
// As the throughput gets smaller, the ray is more likely to get terminated early.
// Survivors have their value boosted to make up for fewer samples being in the average.
{
float p = max(throughput.r, max(throughput.g, throughput.b));
if (RandomFloat01(rngState) > p)
break;
// Add the energy we 'lose' by randomly terminating paths
throughput *= 1.0f / p;
}
}
// return pixel color
return ret;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// initialize a random number state based on frag coord and frame
uint rngState = uint(uint(fragCoord.x) * uint(1973) + uint(fragCoord.y) * uint(9277) + uint(iFrame) * uint(26699)) | uint(1);
// The ray starts at the camera position (the origin)
vec3 rayPosition = vec3(0.0f, 0.0f, 0.0f);//sin(iTime));
// calculate the camera distance
float cameraDistance = 1.0f / tan(c_FOVDegrees * 0.5f * c_pi / 180.0f);
// Anti - Aliasing
// calculate subpixel camera jitter for anti aliasing
vec2 jitter = vec2(RandomFloat01(rngState), RandomFloat01(rngState)) - 0.5f;
// calculate coordinates of the ray target on the imaginary pixel plane.
// -1 to +1 on x,y axis. 1 unit away on the z axis
vec3 rayTarget = vec3(((fragCoord+jitter)/iResolution.xy) * 2.0f - 1.0f, cameraDistance);
// correct for aspect ratio
float aspectRatio = iResolution.x / iResolution.y;
rayTarget.y /= aspectRatio;
// calculate a normalized vector for the ray direction.
// it's pointing from the ray position to the ray target.
vec3 rayDir = normalize(rayTarget - rayPosition);
// raytrace for this pixel
vec3 color = vec3(0.0f, 0.0f, 0.0f);
for (int index = 0; index < c_numRendersPerFrame; ++index)
color += GetColorForRay(rayPosition, rayDir, rngState) / float(c_numRendersPerFrame);
// see if space was pressed. if so we want to restart our render.
// This is useful for when we go fullscreen for a bigger image.
bool spacePressed = (texture(iChannel2, vec2(KEY_SPACE,0.25)).x > 0.1);
// average the frames together
vec4 lastFrameColor = texture(iChannel0, fragCoord / iResolution.xy);
float blend = (lastFrameColor.a == 0.0f || spacePressed) ? 1.0f : 1.0f / (1.0f + (1.0f / lastFrameColor.a));
color = mix(lastFrameColor.rgb, color, blend);
// show the result
fragColor = vec4(color, blend);
}
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
Image - iChannel0 = BufferA
---------------------------------------------------------------------------------
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec3 color = texture(iChannel0, fragCoord / iResolution.xy).rgb;
// apply exposure (how long the shutter is open)
color *= c_exposure;
// convert unbounded HDR color range to SDR color range
color = ACESFilm(color);
// convert from linear to sRGB for display
color = LinearToSRGB(color);
fragColor = vec4(color, 1.0f);
}
---------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment